All paths into sky-cam.conf; install.sh generates systemd units

sky-cam.conf
- Add SCRIPT_DIR (install location) and CAM_NAME (camera/folder name)
- These are the only values that change when deploying to a new machine

install.sh (new)
- Sources sky-cam.conf, generates all systemd .service and .timer units
  with correct ExecStart paths, then enables and starts the timers
- Run once after editing sky-cam.conf; run again if paths change
- Supports --system flag for system-wide install

All .sh scripts + sunrise2mm.py
- No hardcoded paths remain
- script_name=$(basename ...) derivation replaced by $CAM_NAME from conf
- sunrise_video.10s.sh now sources sky-cam.conf for BASE_DIR, CAM_NAME,
  and SCRIPT_DIR (replaces all /home/motion/... references)
- sunrise2mm.py builds video path from BASE_DIR + CAM_NAME in conf

systemd/ template files kept as reference but no longer need editing

https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
This commit is contained in:
Claude
2026-04-18 21:31:26 +00:00
parent ac01c05948
commit 5d3ee97df0
8 changed files with 151 additions and 33 deletions
+2 -3
View File
@@ -43,8 +43,7 @@ target_per_clip=$(echo "scale=6; $music_duration / $DAYS_IN_MVT" | bc)
echo "Target clip duration: $target_per_clip s ($DAYS_IN_MVT days in movement)"
# ── Locate yesterday's images ─────────────────────────────────────────────────
script_name=$(basename "$SCRIPT_DIR" | cut -d'-' -f1)
image_dir="$base_dir/$script_name/$yesterday"
image_dir="$base_dir/$CAM_NAME/$yesterday"
if [ ! -d "$image_dir" ]; then
echo "WARNING: Image directory $image_dir not found — skipping $yesterday."
@@ -59,7 +58,7 @@ fi
echo "Images found: $total_images"
# ── Prepare output directory ──────────────────────────────────────────────────
output_dir="$base_dir/movies/$script_name/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM"
output_dir="$base_dir/movies/$CAM_NAME/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM"
mkdir -p "$output_dir"
# ── Build sorted image list ───────────────────────────────────────────────────
+2 -3
View File
@@ -17,9 +17,8 @@ RETENTION_DAYS=10 # full-day videos older than this are deleted
# ── Date / paths ──────────────────────────────────────────────────────────────
yesterday=$(date --date="yesterday" +%Y-%m-%d)
script_name=$(basename "$SCRIPT_DIR" | cut -d'-' -f1)
image_dir="$base_dir/$script_name/$yesterday"
output_dir="$base_dir/movies/$script_name/fullday"
image_dir="$base_dir/$CAM_NAME/$yesterday"
output_dir="$base_dir/movies/$CAM_NAME/fullday"
mkdir -p "$output_dir"
# ── Purge old full-day videos ─────────────────────────────────────────────────
Executable
+115
View File
@@ -0,0 +1,115 @@
#!/bin/bash
# install.sh — generate and install sky-cam systemd units from sky-cam.conf.
#
# Usage:
# ./install.sh # installs to ~/.config/systemd/user (no root needed)
# ./install.sh --system # installs to /etc/systemd/system (needs sudo)
#
# Run this once after editing sky-cam.conf, and again whenever sky-cam.conf
# changes (e.g. SCRIPT_DIR moves). No other file needs editing.
set -euo pipefail
HERE="$(dirname "$(realpath "$0")")"
source "$HERE/sky-cam.conf"
# ── Install target ────────────────────────────────────────────────────────────
SYSTEM_MODE=false
[ "${1:-}" = "--system" ] && SYSTEM_MODE=true
if $SYSTEM_MODE; then
UNIT_DIR="/etc/systemd/system"
SC="sudo systemctl"
else
UNIT_DIR="$HOME/.config/systemd/user"
SC="systemctl --user"
fi
mkdir -p "$UNIT_DIR"
echo "Installing systemd units to $UNIT_DIR ..."
# ── Helper: write a oneshot service unit ─────────────────────────────────────
write_service() {
local unit="$1" description="$2" script="$3" extra="${4:-}"
cat > "$UNIT_DIR/${unit}.service" <<EOF
[Unit]
Description=Sky-Cam ($CAM_NAME) — $description
${extra}OnFailure=sky-cam-notify-failure@%n.service
[Service]
Type=oneshot
ExecStart=$SCRIPT_DIR/$script
StandardOutput=journal
StandardError=journal
EOF
echo " wrote ${unit}.service"
}
# ── Helper: write a timer unit ───────────────────────────────────────────────
write_timer() {
local unit="$1" description="$2" calendar="$3"
cat > "$UNIT_DIR/${unit}.timer" <<EOF
[Unit]
Description=Sky-Cam ($CAM_NAME) — $description
[Timer]
OnCalendar=$calendar
Persistent=true
[Install]
WantedBy=timers.target
EOF
echo " wrote ${unit}.timer"
}
# ── Failure notification template ────────────────────────────────────────────
cat > "$UNIT_DIR/sky-cam-notify-failure@.service" <<EOF
[Unit]
Description=Sky-Cam notify on failure for %i
[Service]
Type=oneshot
ExecStart=$SCRIPT_DIR/notify.sh "sky-cam job failed: %i" "systemd unit %i failed — check: journalctl -u %i"
EOF
echo " wrote sky-cam-notify-failure@.service"
# ── Three daily jobs ──────────────────────────────────────────────────────────
# Sunrise video → Mattermost (run after sunrise window ends)
write_service "sky-cam-sunrise" \
"daily sunrise video → Mattermost" \
"sunrise_video.10s.sh" \
"After=network-online.target\nWants=network-online.target\n"
write_timer "sky-cam-sunrise" "daily sunrise video" "*-*-* 09:00:00"
# Four Seasons daily clip (auto-triggers montage + year-end on last days)
write_service "sky-cam-4seasons" \
"Four Seasons daily clip" \
"4-seasons.sh" \
"After=network-online.target\nWants=network-online.target\n"
write_timer "sky-cam-4seasons" "Four Seasons daily clip" "*-*-* 01:00:00"
# Full-day timelapse with 10-day retention
write_service "sky-cam-fullday" \
"full-day timelapse" \
"fullday-video.sh"
write_timer "sky-cam-fullday" "full-day timelapse" "*-*-* 02:00:00"
# ── Reload and enable ─────────────────────────────────────────────────────────
echo ""
$SC daemon-reload
for timer in sky-cam-sunrise sky-cam-4seasons sky-cam-fullday; do
$SC enable --now "${timer}.timer" \
&& echo " enabled + started ${timer}.timer" \
|| echo " WARNING: could not enable ${timer}.timer"
done
echo ""
echo "Done. Check status with:"
if $SYSTEM_MODE; then
echo " sudo systemctl list-timers 'sky-cam-*'"
echo " sudo journalctl -u sky-cam-4seasons.service -f"
else
echo " systemctl --user list-timers 'sky-cam-*'"
echo " journalctl --user -u sky-cam-4seasons.service -f"
fi
+1 -2
View File
@@ -38,8 +38,7 @@ eval "$(python3 "$SCRIPT_DIR/season_info.py" ${DATE_ARG:+"$DATE_ARG"})"
echo "Season=$SEASON Mvt=$MVT_NUM Year=$ASTRO_YEAR (ref: ${DATE_ARG:-today})"
# ── Locate files ──────────────────────────────────────────────────────────────
script_name=$(basename "$SCRIPT_DIR" | cut -d'-' -f1)
video_dir="$base_dir/movies/$script_name/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM"
video_dir="$base_dir/movies/$CAM_NAME/$ASTRO_YEAR/$SEASON/Mvt$MVT_NUM"
output_dir="$video_dir/montage"
mkdir -p "$output_dir"
+16 -6
View File
@@ -5,12 +5,22 @@
#
# Python reads it with key=value parsing (same format, quotes stripped).
#
# Copy this file to your scripts directory and fill in every value.
# Lines starting with # are comments and are ignored.
# Fill in every value below, then run ./install.sh to deploy systemd timers.
# You should not need to edit any other file.
# ── Paths ─────────────────────────────────────────────────────────────────────
# ── Install location ──────────────────────────────────────────────────────────
# Full path to the directory containing this conf file and all the scripts.
SCRIPT_DIR=/home/motion/drives/local-2tb/sunrise-scripts
# Camera name — used as the subfolder name under BASE_DIR and BASE_DIR/movies.
# E.g. "sunrise" → images at $BASE_DIR/sunrise/YYYY-MM-DD/, videos at $BASE_DIR/movies/sunrise/
CAM_NAME=sunrise
# ── Storage ───────────────────────────────────────────────────────────────────
BASE_DIR=/home/motion/drives/local-2tb
MUSIC_DIR=/home/motion/drives/local-2tb/music/4 Seasons
# ── Timezone (IANA name) ──────────────────────────────────────────────────────
TIMEZONE=America/New_York
# ── Mattermost — daily sunrise upload ────────────────────────────────────────
@@ -18,8 +28,8 @@ mattermost_url=https://your-mattermost-server.example.com
access_token=your-access-token-here
channel_id=your-daily-upload-channel-id-here
# ── Notifications — movement montage + year-end video complete / job failure ──
# ntfy: https://ntfy.sh (cloud) or self-hosted. Set topic URL below.
# ── Notifications — montage/year-end complete + job failure ───────────────────
# ntfy: https://ntfy.sh (cloud) or self-hosted.
NTFY_ENABLED=false
NTFY_URL=https://ntfy.sh/your-topic-here
@@ -28,6 +38,6 @@ EMAIL_ENABLED=false
EMAIL_TO=you@example.com
EMAIL_FROM=skycam@localhost
# Mattermost text post (can be same channel as daily upload or a private one).
# Mattermost text post (can be same or different channel from daily upload).
MM_NOTIFY_ENABLED=false
MM_NOTIFY_CHANNEL_ID=your-notify-channel-id-here
+2 -2
View File
@@ -45,8 +45,8 @@ today_date_str = today.strftime("%Y-%m-%d") # e.g., "2024-11-05"
today_month_str = today.strftime("%Y-%m") # e.g., "2024-11"
# Set the full folder path for today's video
base_video_folder = "/home/motion/drives/local-2tb/movies/sunrise" # Correct base folder for videos
output_dir = os.path.join(base_video_folder, today_month_str, "sunrise-only") # Subfolder for final videos
base_video_folder = os.path.join(config.get('BASE_DIR', ''), 'movies', config.get('CAM_NAME', 'sunrise'))
output_dir = os.path.join(base_video_folder, today_month_str, "sunrise-only")
# Check if the directory exists
if not os.path.isdir(output_dir):
+9 -11
View File
@@ -1,25 +1,23 @@
#!/bin/bash
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
source "$SCRIPT_DIR/sky-cam.conf"
# Get today's date in the format YYYY-MM-DD
current_date=$(date +%Y-%m-%d)
output_date=$(date +%Y-%m)
# Extract the base directory and the first word of the script's folder name
base_dir="/home/motion/drives/local-2tb"
script_dir=$(dirname "$(realpath "$0")") # Get the directory where the script is located
first_word_of_script_folder=$(basename "$script_dir" | cut -d'-' -f1)
# Define the directory where images are stored (using today's date)
image_dir="$base_dir/$first_word_of_script_folder/$current_date"
image_dir="$BASE_DIR/$CAM_NAME/$current_date"
# Set output directory (new structure with 'sunrise-only' subfolder)
output_dir="/home/motion/drives/local-2tb/movies/sunrise/$output_date/sunrise-only"
# Set output directory
output_dir="$BASE_DIR/movies/$CAM_NAME/$output_date/sunrise-only"
# Make sure the output directory exists
mkdir -p "$output_dir"
# Get the sunrise time for today using the sunrise.py script
sunrise_time=$(python3 /home/motion/drives/local-2tb/sunrise-scripts/sunrise.py)
sunrise_time=$(python3 "$SCRIPT_DIR/sunrise.py")
# Check if the sunrise time was fetched successfully
if [[ -z "$sunrise_time" ]]; then
@@ -143,10 +141,10 @@ echo "Video created at $final_video"
rm "$temp_file"
# Run the sunrise_overlay.py script to add the sunrise overlay
python3 /home/motion/drives/local-2tb/sunrise-scripts/sunrise_overlay.py "$final_video"
python3 "$SCRIPT_DIR/sunrise_overlay.py" "$final_video"
# Run the sunrise2mm.py script to upload the video
python3 /home/motion/drives/local-2tb/sunrise-scripts/sunrise2mm.py
python3 "$SCRIPT_DIR/sunrise2mm.py"
# Check if the Python script was successful
if [ $? -eq 0 ]; then
+4 -6
View File
@@ -8,7 +8,7 @@
# The 12 individual movement videos already carry their own attribution overlay
# (first 6 seconds of each), so no extra card is appended here.
#
# Output: $base_dir/movies/$script_name/$ASTRO_YEAR/$script_name-$ASTRO_YEAR.mp4
# Output: $BASE_DIR/movies/$CAM_NAME/$ASTRO_YEAR/$CAM_NAME-$ASTRO_YEAR.mp4
set -euo pipefail
@@ -21,9 +21,7 @@ if [ -z "$ASTRO_YEAR" ]; then
exit 1
fi
base_dir="$BASE_DIR"
script_name=$(basename "$SCRIPT_DIR" | cut -d'-' -f1)
year_dir="$base_dir/movies/$script_name/$ASTRO_YEAR"
year_dir="$BASE_DIR/movies/$CAM_NAME/$ASTRO_YEAR"
echo "Assembling Four Seasons $ASTRO_YEAR from $year_dir ..."
@@ -60,7 +58,7 @@ if [ "$missing" -gt 0 ]; then
fi
# ── Join ──────────────────────────────────────────────────────────────────────
output_file="$year_dir/${script_name}-${ASTRO_YEAR}.mp4"
output_file="$year_dir/${CAM_NAME}-${ASTRO_YEAR}.mp4"
echo "Output: $output_file"
ffmpeg -loglevel warning \
@@ -74,5 +72,5 @@ echo "Done: $output_file (${total_min} min)"
# ── Notify ────────────────────────────────────────────────────────────────────
"$SCRIPT_DIR/notify.sh" \
"Four Seasons $ASTRO_YEAR complete" \
"${script_name}-${ASTRO_YEAR}.mp4 — ${total_min} minutes ($found movements)" \
"${CAM_NAME}-${ASTRO_YEAR}.mp4 — ${total_min} minutes ($found movements)" \
|| true