teslausb/run/archiveloop
Marco Nelissen b9971cfdfb Always remove fsck recovered files on boot
A recent change made the image cleanup conditional on successful archiving,
which had the unwanted side effect of no longer removing files recovered
by fsck when TeslaUSB boots, which apparently confuse the car.
Make it so that those file are removed again when TeslaUSB boots.
2025-02-10 08:50:33 -08:00

925 lines
23 KiB
Bash
Executable File

#!/bin/bash -eu
if [ "${BASH_SOURCE[0]}" != "$0" ]
then
echo "${BASH_SOURCE[0]} must be executed, not sourced"
return 1 # shouldn't use exit when sourced
fi
# Unload the module that was loaded by cmdline.txt
modprobe -r g_ether
export LOG_FILE=/mutable/archiveloop.log
function log () {
echo "$( date ):" "$@" >> "$LOG_FILE"
if [[ "$(stat --format="%s" "$LOG_FILE")" -gt 1000000 ]]
then
local log_file2="${LOG_FILE}.2"
tail -n 5000 "$LOG_FILE" > "${log_file2}"
mv "$log_file2" "$LOG_FILE"
log "(log truncated)"
fi
}
function log_errors_on_exit {
log "archiveloop exited with code $1. Recent errors follow"
journalctl -n 15 -u teslausb >> "$LOG_FILE"
log "end of errors."
exit "$1"
}
if [ "${FLOCKED:-}" != "$0" ]
then
if FLOCKED="$0" flock -en -E 99 "$0" "$0" "$@" || case "$?" in
99) echo already running
exit 99
;;
*) log_errors_on_exit $?
;;
esac
then
# success
log_errors_on_exit 0
fi
fi
export CAM_MOUNT=/mnt/cam
export MUSIC_MOUNT=/mnt/music
export ARCHIVE_MOUNT=/mnt/archive
export MUSIC_ARCHIVE_MOUNT=/mnt/musicarchive
# read the setup variables and define other environment variables and functions
source /root/bin/envsetup.sh
# led triggers may have been built as modules, so try
# to load them
if ! grep -q timer "$STATUSLED/trigger"
then
modprobe ledtrig-timer || log "timer LED trigger unavailable"
fi
if ! grep -q heartbeat "$STATUSLED/trigger"
then
modprobe ledtrig-heartbeat || log "heartbeat LED trigger unavailable"
fi
if [ -z "${ARCHIVE_SERVER+x}" ]
then
case "${ARCHIVE_SYSTEM:-none}" in
rsync)
export ARCHIVE_SERVER="$RSYNC_SERVER"
;;
rclone)
export ARCHIVE_SERVER="8.8.8.8"
;;
none)
export ARCHIVE_SERVER=localhost
;;
*)
log "ARCHIVE_SERVER not set"
exit 1
;;
esac
fi
function timestamp () {
local prefix=${1:-}
while IFS= read -r line
do
echo "$(date): $prefix$line"
done
}
function fix_errors_in_image () {
local image="$1"
log "Running fsck on $image..."
loopback=$(losetup_find_show -P "$image")
# Use -p repair arg. It works with vfat and exfat.
/sbin/fsck "${loopback}p1" -- -p |& timestamp '| ' >> "$LOG_FILE" || echo ""
losetup -d "$loopback"
log "Finished fsck on $image."
}
function archive_is_reachable () {
local reachable=true
/root/bin/archive-is-reachable.sh "$ARCHIVE_SERVER" || reachable=false
if [ "$reachable" = false ]
then
false
return
fi
true
}
function connect_usb_drives_to_host() {
log "Connecting usb to host..."
/root/bin/enable_gadget.sh
log "Connected usb to host."
sleep 5
}
function wait_for_archive_to_be_reachable () {
log "Waiting for archive to be reachable..."
while true
do
if archive_is_reachable
then
log "Archive is reachable."
break
fi
if [ -e /tmp/archive_is_reachable ]
then
log "Simulating archive is reachable"
rm /tmp/archive_is_reachable
break
fi
sleep 1
done
}
function retry () {
local attempts=0
while true
do
if "$@"
then
true
return
fi
if [ "$attempts" -ge 10 ]
then
log "Attempts exhausted."
false
return
fi
log "Sleeping before retry ..."
/bin/sleep 1
attempts=$((attempts + 1))
log "Retrying ($attempts) '$*' ..."
done
false
return
}
function mount_mountpoint () {
local mount_point="$1"
log "Mounting $mount_point..."
local mounted=true
timeout 10 mount "$mount_point" >> "$LOG_FILE" 2>&1 || mounted=false
if [ "$mounted" = true ]
then
log "Mounted $mount_point."
true
return
else
log "Failed to mount $mount_point."
false
return
fi
}
function ensure_mountpoint_is_mounted () {
local mount_point="$1"
local mount_exists=true
findmnt --mountpoint "$mount_point" > /dev/null || mount_exists=false
if [ "$mount_exists" = true ]
then
log "$mount_point is already mounted."
else
mount_mountpoint "$mount_point"
fi
}
function ensure_mountpoint_is_mounted_with_retry () {
retry ensure_mountpoint_is_mounted "$1"
}
function ensure_cam_file_is_mounted () {
log "Ensuring cam file is mounted..."
disconnect_usb_drives_from_host
ensure_mountpoint_is_mounted_with_retry "$CAM_MOUNT"
log "Ensured cam file is mounted."
}
function ensure_music_file_is_mounted () {
log "Ensuring music backing file is mounted..."
disconnect_usb_drives_from_host
ensure_mountpoint_is_mounted_with_retry "$MUSIC_MOUNT"
log "Ensured music drive is mounted."
}
function unmount_mount_point () {
local mount_point="$1"
log "Unmounting $mount_point..."
if umount "$mount_point" >> "$LOG_FILE" 2>&1
then
log "Unmounted $mount_point."
else
log "Failed to unmount $mount_point, trying lazy unmount."
if umount -l "$mount_point" >> "$LOG_FILE" 2>&1
then
log "lazily unmounted $mount_point"
else
log "lazy unmount failed"
fi
fi
}
function unmount_cam_file () {
unmount_mount_point "$CAM_MOUNT"
}
function unmount_music_file () {
unmount_mount_point "$MUSIC_MOUNT"
}
function wait_for_archive_to_be_unreachable () {
log "Waiting for archive to be unreachable..."
while true
do
if ! retry archive_is_reachable
then
log "Archive is unreachable."
break
fi
if [ -e /tmp/archive_is_unreachable ]
then
log "Simulating archive being unreachable."
rm /tmp/archive_is_unreachable
break
fi
sleep 1
done
}
function check_if_usb_gadget_is_mounted () {
LUNFILE=/sys/kernel/config/usb_gadget/teslausb/configs/c.1/mass_storage.0/lun.0/file
if [ -n "$(cat /sys/kernel/config/usb_gadget/teslausb/UDC)" ] &&
[ -e "$LUNFILE" ] &&
[ -n "$(cat $LUNFILE)" ]
then
return
fi
log "USB Gadget not mounted. Fixing files and remounting..."
disconnect_usb_drives_from_host
connect_usb_drives_to_host
}
function trim_free_space() {
local mount_point="$1"
# Make sure the partition is mounted.
if found=$(findmnt -n --mountpoint "$mount_point")
then
loop=$(echo "$found" | awk '{print $2}')
image=$(losetup -l -n --output=BACK-FILE "$loop")
log "Trimming free space in $mount_point, which has $(filefrag "$image" | awk '{print $2}') extents"
if fstrim "$mount_point" >> "$LOG_FILE" 2>&1
then
log "Trim complete, image now has $(filefrag "$image" | awk '{print $2}') extents"
else
log "Trimming free space in $mount_point failed."
fi
else
log "Could not trim free space in $mount_point. Not Mounted."
fi
}
function fix_errors_in_images () {
for i in cam music boombox lightshow
do
img="/backingfiles/${i}_disk.bin"
if [ -e "$img" ]
then
fix_errors_in_image "$img"
fi
done
}
function disconnect_usb_drives_from_host () {
log "Disconnecting usb from host..."
if /root/bin/disable_gadget.sh
then
fix_errors_in_images
fi
log "Disconnected usb from host."
}
function convert_seconds_to_nice_time () {
local -r h=$(($1/3600))
local -r m=$((($1%3600)/60))
local -r s=$(($1%60))
if [ $h -gt 0 ]
then
printf "%dh%dm%ds" $h $m $s
elif [ $m -gt 0 ]
then
printf "%dm%ds" $m $s
else
printf "%ds" $s
fi
}
function filterfile {
if [ -x /root/bin/archive-filter ]
then
/root/bin/archive-filter "$1"
fi
}
function sortfile {
touch "$1"
sort "$1" > /tmp/sort_tmp.$$
mv /tmp/sort_tmp.$$ "$1"
}
function prunefile {
sortfile "$1"
sortfile "$2"
comm -2 -3 "$1" "$2" > /tmp/prune_tmp.$$
mv /tmp/prune_tmp.$$ "$1"
}
function intersect {
sortfile "$1"
sortfile "$2"
comm -1 -2 "$1" "$2" > /tmp/prune_tmp.$$
mv /tmp/prune_tmp.$$ "$1"
}
function clean_cam_mount {
local mode="${1:-}"
if [ "$mode" = "freespace" ]
then
if [ -e /tmp/last_clean_time ]
then
read -r -d . last < /tmp/last_clean_time
read -r -d . now < /proc/uptime
if (( (now - last) < 300 ))
then
log "skipping cleaning cam mount"
return
fi
fi
cat /proc/uptime > /tmp/last_clean_time
fi
log "cleaning cam mount"
ensure_cam_file_is_mounted
# Delete the files that fsck "recovered". These are generally files
# that were deleted previously by the car or teslausb
find "${CAM_MOUNT}" \( \( -type f -name FSCK\*.REC \) -o \( -type f -name "*~[0-9].MP4" \) \) -print0 | xargs -0 rm -rf
# Delete short recordings
(cd "${CAM_MOUNT}"; find . -type f -name \*.mp4 -size -100000c -printf '%P\n' ) | xargs rm -rf
if [ "$mode" = "freespace" ]
then
# Remove files, oldest first, until there is at least 20GB of free space
(cd "${CAM_MOUNT}"; find . -type f -a \( -path './TeslaCam/*' -o -path './TeslaTrackMode/*' \) -printf '%P\0') > /tmp/rmcandidates.txt
if [ -s /tmp/rmcandidates.txt ]
then
(cd "${CAM_MOUNT}"; xargs -a /tmp/rmcandidates.txt -0 ls -cr1) > /tmp/rmcandidatesbytime.txt
else
true > /tmp/rmcandidatesbytime.txt
fi
local freespace
while read -r line
do
freespace=$(eval "$(stat --file-system --format="echo \$((%f*%S))" "${CAM_MOUNT}/.")")
if ((freespace > 20000000000))
then
break;
fi
rm "${CAM_MOUNT}/$line"
done < /tmp/rmcandidatesbytime.txt
fi
# delete directories that are now empty
find "$CAM_MOUNT/TeslaCam/RecentClips" "$CAM_MOUNT/TeslaCam/SavedClips" \
"$CAM_MOUNT/TeslaCam/SentryClips" "$CAM_MOUNT/TeslaTrackMode" -mindepth 1 -type d -empty -delete || true
# Trim the camera archive to reduce the number of blocks in the snapshot.
trim_free_space "$CAM_MOUNT"
unmount_cam_file
log "done cleaning cam mount"
}
# Directory structure car uses:
# TeslaCam/
# RecentClips/
# videos.mp4
# SavedClips/
# datetime/
# videos.mp4
# event.json
# thumb.png
# SentryClips/
# datetime/
# videos.mp4
# event.json
# thumb.png
# TeslaTrackMode/
# lapvideo.mp4
# laptelemetry.csv
function archive_teslacam_clips () {
log "Checking saved folder count..."
local -r overlaylower=/mutable/TeslaCam
local -r overlayupper=/tmp/cam/upper
local -r overlaywork=/tmp/cam/work
local -r overlaymerged=/tmp/cam/merged
rm -rf "$overlayupper" "$overlaywork" "$overlaymerged"
mkdir -p "$overlayupper" "$overlaywork" "$overlaymerged"
mount -t overlay overlay -o "lowerdir=$overlaylower,upperdir=$overlayupper,workdir=$overlaywork" "$overlaymerged"
# Build list of the files to be archived.
local -r sentrylist=/tmp/sentry_files
local -r sentrylist_archived=/tmp/sentry_files_archived
local -r sentrylist_previously_archived=/mutable/sentry_files_archived
local -r ignorelist=/tmp/ignore_files
# Find files by name only. Don't follow the symlinks yet, since that
# would cause all snapshots to be mounted.
local -a savedclipsopt
local -a sentryclipsopt
local -a trackmodeclipsopt
local -a recentclipsopt
if [ "${ARCHIVE_SAVEDCLIPS:-true}" = "true" ]
then
savedclipsopt=("-path" "./SavedClips/*")
else
savedclipsopt=("-false")
fi
if [ "${ARCHIVE_SENTRYCLIPS:-true}" = "true" ]
then
sentryclipsopt=("-o" "-path" "./SentryClips/*")
fi
if [ "${ARCHIVE_TRACKMODECLIPS:-true}" = "true" ]
then
trackmodeclipsopt=("-o" "-path" "./TeslaTrackMode/*")
fi
if [ "${ARCHIVE_RECENTCLIPS:-false}" = "true" ]
then
recentclipsopt=("-o" "-path" "./RecentClips/*")
fi
(cd "$overlaylower"; find . \( \( "${savedclipsopt[@]}" "${sentryclipsopt[@]}" "${trackmodeclipsopt[@]}" "${recentclipsopt[@]}" \) -type l \) \
-a -fprintf "$sentrylist" '%P\n')
# Copy lists of previously-archived files from /mutable
if [ -r "${sentrylist_previously_archived}" ]
then
cp "${sentrylist_previously_archived}" "${sentrylist_archived}"
fi
# Remove files that no longer exist in snapshots from the
# "previously archived" list, so the list doesn't keep growing.
intersect "${sentrylist_archived}" "${sentrylist}"
# Remove previously-archived files from the archive candidate list
prunefile "${sentrylist}" "${sentrylist_archived}"
# ${sentrylist} is now a list of files that haven't been archived yet.
# Remove short recordings from this list. This requires following the
# links and looking at the files themselves.
if [ -s "${sentrylist}" ]
then
(cd "$overlaymerged"; xargs -a "${sentrylist}" echo "find -L" | while read -r line
do
eval "$line \( -name \*.mp4 -a -size -100000c -printf '%p\n' \)"
done) > ${ignorelist}
else
true > "${ignorelist}"
fi
# remove the short files from the list of files to be archived
prunefile "${sentrylist}" "${ignorelist}"
# apply custom removals/additions
filterfile "${sentrylist}"
local -r sentry_count=$(grep -c -v TeslaTrackMode "$sentrylist")
local -r trackmode_count=$(grep -c TeslaTrackMode "$sentrylist")
local -r ignore_count=$(wc -l < "$ignorelist")
local -r total_count=$((sentry_count + trackmode_count))
# extract some noteworthy info from the file list
local -r saved_event_count=$(grep 'SavedClips/' < "$sentrylist" | sed 's/[^\/]\+$//' | sort -u | wc -l)
local -r sentry_event_count=$(grep 'SentryClips/' < "$sentrylist" | sed 's/[^\/]\+$//' | sort -u | wc -l)
local -r event_count=$((saved_event_count + sentry_event_count))
log "There are $event_count event folder(s) with $sentry_count file(s) and $trackmode_count track mode file(s) to move." \
" $ignore_count short recording(s) will be skipped."
local archive_success=true
if [[ "$total_count" -gt 0 ]]
then
log "Starting recording archiving"
local -r start_ts=$(date --utc --date "now" +%s)
local message="Archiving "
if [[ $sentry_count -gt 0 && $trackmode_count -gt 0 ]]
then
message+="$trackmode_count track mode file(s) and $sentry_count file(s) including $event_count event folder(s)"
elif [[ $sentry_count -gt 0 && $event_count -gt 0 ]]
then
message+="$sentry_count file(s) including $event_count event folder(s)"
elif [[ $sentry_count -gt 0 ]]
then
message+="$sentry_count file(s)"
else
message+="$trackmode_count track mode file(s)"
fi
message+=" starting at $(date)"
/root/bin/send-push-message "$NOTIFICATION_TITLE:" "$message" start || log "failed to send push message"
# setup trigger files
local -r triggerdir=/tmp/triggers
local -r triggerlist=/tmp/triggers.txt
rm -rf "${triggerdir}"
mkdir -p "${triggerdir}/SentryClips"
mkdir -p "${triggerdir}/SavedClips"
mkdir -p "${triggerdir}/RecentClips"
true > "${triggerlist}"
if [ -n "${TRIGGER_FILE_SAVED+x}" ]
then
touch "${triggerdir}/SavedClips/${TRIGGER_FILE_SAVED}"
echo "SavedClips/${TRIGGER_FILE_SAVED}" >> "${triggerlist}"
fi
if [ -n "${TRIGGER_FILE_SENTRY+x}" ]
then
touch "${triggerdir}/SentryClips/${TRIGGER_FILE_SENTRY}"
echo "SentryClips/${TRIGGER_FILE_SENTRY}" >> "${triggerlist}"
fi
if [ -n "${TRIGGER_FILE_RECENT+x}" ]
then
touch "${triggerdir}/RecentClips/${TRIGGER_FILE_RECENT}"
echo "RecentClips/${TRIGGER_FILE_RECENT}" >> "${triggerlist}"
fi
if [ -n "${TRIGGER_FILE_ANY+x}" ]
then
touch "${triggerdir}/${TRIGGER_FILE_ANY}"
echo "${TRIGGER_FILE_ANY}" >> "${triggerlist}"
fi
if /root/bin/archive-clips.sh "$overlaymerged" "${sentrylist}" "${triggerdir}" "${triggerlist}"
then
message="Archiving completed successfully. "
else
message="Error during archiving. "
archive_success=false
fi
local -i sentry_archived=0
local -i trackmode_archived=0
while read -r line
do
if [[ ! -e "$overlaymerged/$line" ]]
then
case $line in
TeslaTrackMode*)
trackmode_archived=$((trackmode_archived + 1))
;;
*)
sentry_archived=$((sentry_archived + 1))
;;
esac
echo "$line" >> "${sentrylist_archived}"
fi
done < ${sentrylist}
# copy the list of archived files back to /mutable if it has changed
if ! diff -q -N "${sentrylist_archived}" "${sentrylist_previously_archived}" > /dev/null
then
cp "${sentrylist_archived}" "${sentrylist_previously_archived}.new"
mv "${sentrylist_previously_archived}.new" "${sentrylist_previously_archived}"
fi
message+="Archived "
if [[ $sentry_count -gt 0 && $trackmode_count -gt 0 ]]
then
message+="${trackmode_archived} trackmode files and ${sentry_archived} other files"
elif [[ $sentry_count -gt 0 ]]
then
message+="${sentry_archived} files"
else
message+="${trackmode_archived} trackmode files"
fi
local -r end_ts=$(date --utc --date "now" +%s)
local -r delta=$((end_ts - start_ts))
message+=" in $(convert_seconds_to_nice_time $delta)"
/root/bin/send-push-message "$NOTIFICATION_TITLE:" "${message}" finish
if [ -e /tmp/archive-error.log ]
then
cat /tmp/archive-error.log >> ${LOG_FILE}
rm /tmp/archive-error.log
fi
fi
# overlayfs behavior is undefined if the lower is changed while the overlay is active, so umount first
umount "$overlaymerged"
if archive_is_reachable
then
if [ "$archive_success" = "true" ]
then
clean_cam_mount freespace
else
log "Skipping cleaning step after archive error"
fi
else
log "Archive not reachable, assuming user drove away, skipping cleaning step"
fi
}
function copy_music_files () {
log "Starting music sync..."
ensure_music_file_is_mounted
/root/bin/copy-music.sh
# Trim the empty space from the music archive.
trim_free_space "$MUSIC_MOUNT"
unmount_music_file
}
function has_cam_disk () {
[ -f /backingfiles/cam_disk.bin ]
}
function archive_clips () {
log "Archiving..."
if ! /root/bin/connect-archive.sh
then
log "Couldn't connect archive, skipping archive step"
return
fi
if ! has_cam_disk
then
log "Skipping archiving (no cam disk)"
elif archive_teslacam_clips
then
log "Finished archiving."
else
log "Archiving failed."
fi
if timeout 5 [ -d "$MUSIC_ARCHIVE_MOUNT" -a -d "$MUSIC_MOUNT" ]
then
log "Copying music..."
if copy_music_files
then
log "Finished copying music."
else
log "Copying music failed."
fi
else
log "Music archive not configured or unreachable"
fi
/root/bin/disconnect-archive.sh
}
function slowblink () {
echo timer > "$STATUSLED/trigger" || return 0
echo 900 > "$STATUSLED/delay_off"
echo 100 > "$STATUSLED/delay_on"
}
function fastblink () {
echo timer > "$STATUSLED/trigger" || return 0
echo 150 > "$STATUSLED/delay_off"
echo 50 > "$STATUSLED/delay_on"
}
function doubleblink () {
echo heartbeat > "$STATUSLED/trigger" || return 0
echo 0 > "$STATUSLED/invert"
}
function set_time () {
log "Trying to set time..."
local -r uptime_start=$(awk '{print $1}' /proc/uptime)
local -r clocktime_start=$(date +%s.%N)
for _ in {1..5}
do
if sntp -S time.google.com
then
local -r uptime_end=$(awk '{print $1}' /proc/uptime)
local -r clocktime_end=$(date +%s.%N)
log "$(awk "BEGIN {printf \"Time adjusted by %f seconds after %f seconds\", $clocktime_end-$clocktime_start, $uptime_end-$uptime_start}")"
return
fi
log "sntp failed, retrying..."
sleep 2
done
log "Failed to set time"
}
function snapshotloop {
echo -n snapshotloop > /proc/self/comm
while true
do
sleep "${SNAPSHOT_INTERVAL:-3480}"
/root/bin/waitforidle || true
/root/bin/make_snapshot.sh
done
}
function freespacemanager {
echo -n freespacemanager > /proc/self/comm
# 10G
local reserve=10737418240
local threepctoftotalspace
threepctoftotalspace=$(eval "$(stat --file-system --format="echo \$((%b*%S/33))" /backingfiles/cam_disk.bin)")
reserve=$((reserve+threepctoftotalspace))
while true
do
local freespace
freespace=$(eval "$(stat --file-system --format="echo \$((%f*%S))" /backingfiles/cam_disk.bin)")
if [ "$freespace" -lt "$reserve" ]
then
/root/bin/manage_free_space.sh "$reserve" || sleep 30
fi
sleep 30
done
}
function logrotator {
echo -n logrotator > /proc/self/comm
while true
do
if [ -s /var/log/nginx/access.log ]
then
mv -f /var/log/nginx/access.log /var/log/nginx/access.log.prev || true
fi
if [ -s /var/log/nginx/error.log ]
then
mv -f /var/log/nginx/error.log /var/log/nginx/error.log.prev || true
fi
if [ -e /var/run/nginx.pid ]
then
kill -USR1 "$(cat /var/run/nginx.pid)"
fi
sleep 1800
done
}
function wifichecker {
echo -n wifichecker > /proc/self/comm
dmesg -w | {
while TMOUT=1 read -r line
do
true
done
wifi=working
while read -r line
do
case $line in
*"failed to enable fw supplicant")
if [ "$wifi" = "working" ]
then
wifi="notworking"
else
log "restarting wifi because of: $line"
modprobe -r brcmfmac cfg80211 brcmutil || true
modprobe brcmfmac || true
while TMOUT=1 read -r line
do
true
done
wifi="working"
fi
;;
*)
wifi=working
;;
esac
done
}
}
function set_sys_param() {
local -r parampath="$1"
local -r var="$2"
local -r defaultval="$3"
local -r val=${!var:-$defaultval}
if [[ ${val} = "default" ]]
then
log "not setting $parampath"
else
echo "$val" > "$parampath" || true
fi
}
export -f mount_mountpoint
export -f ensure_mountpoint_is_mounted
export -f retry
export -f ensure_mountpoint_is_mounted_with_retry
export -f log
echo "==============================================" >> "$LOG_FILE"
log "Starting archiveloop at $(awk '{print $1}' /proc/uptime) seconds uptime..."
set_sys_param /proc/sys/vm/dirty_background_bytes DIRTY_BACKGROUND_BYTES 65536
set_sys_param /proc/sys/vm/dirty_ratio DIRTY_RATIO 80
set_sys_param /sys/devices/system/cpu/cpufreq/policy0/scaling_governor CPU_GOVERNOR conservative
if has_cam_disk
then
snapshotloop &
freespacemanager &
fi
logrotator &
wifichecker &
fix_errors_in_images
# always remove files that fsck recoverd
clean_cam_mount boot
if has_cam_disk
then
# don't need another fsck because fix_errors_in_images already did that
/root/bin/make_snapshot.sh nofsck
fi
connect_usb_drives_to_host
if archive_is_reachable
then
fastblink
set_time
/root/bin/awake_start || true
archive_clips
/root/bin/awake_stop || true
doubleblink
connect_usb_drives_to_host
wait_for_archive_to_be_unreachable
fi
while true
do
slowblink
wait_for_archive_to_be_reachable
fastblink
set_time
/root/bin/awake_start || true
sleep "${ARCHIVE_DELAY:-20}"
# take a snapshot before archive_clips starts deleting files
if has_cam_disk
then
/root/bin/make_snapshot.sh
fi
archive_clips
/root/bin/awake_stop || true
doubleblink
connect_usb_drives_to_host
wait_for_archive_to_be_unreachable
check_if_usb_gadget_is_mounted
done