daily_sunrise_video.sh: derive CAM_NAME from $1 (defaulting to SUNRISE_CAM) instead of an undefined variable; this was causing empty image/output paths. install.sh: pass $SUNRISE_CAM as argument to daily_sunrise_video.sh so it matches the pattern used by every other per-camera script. sunrise2mm.py: use SUNRISE_CAM config key (not the undefined CAM_NAME) to locate the output directory; fixes silent path mismatch on non-default names. 4-seasons.sh: remove stray `set -x` that was flooding systemd logs with shell trace output. README.md: correct Python dependencies from `ephem` to `suntime pytz` to match what sunrise.py actually imports. https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
214 lines
7.5 KiB
Bash
Executable File
214 lines
7.5 KiB
Bash
Executable File
#!/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, cameras added, schedules changed).
|
|
# 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 — $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 — $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"
|
|
|
|
# ── Sunrise video — SUNRISE_CAM only ─────────────────────────────────────────
|
|
# OnSuccess= triggers the upload service only when video creation succeeds.
|
|
cat > "$UNIT_DIR/sky-cam-sunrise.service" <<EOF
|
|
[Unit]
|
|
Description=Sky-Cam — $SUNRISE_CAM: daily sunrise video
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
OnFailure=sky-cam-notify-failure@%n.service
|
|
OnSuccess=sky-cam-sunrise-upload.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$SCRIPT_DIR/daily_sunrise_video.sh $SUNRISE_CAM
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
EOF
|
|
echo " wrote sky-cam-sunrise.service"
|
|
|
|
cat > "$UNIT_DIR/sky-cam-sunrise-upload.service" <<EOF
|
|
[Unit]
|
|
Description=Sky-Cam — $SUNRISE_CAM: upload daily sunrise to Mattermost
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
OnFailure=sky-cam-notify-failure@%n.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$SCRIPT_DIR/sunrise2mm.py
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
EOF
|
|
echo " wrote sky-cam-sunrise-upload.service"
|
|
|
|
write_timer "sky-cam-sunrise" "$SUNRISE_CAM: daily sunrise video" "$SCHEDULE_SUNRISE"
|
|
|
|
timers=(sky-cam-sunrise)
|
|
|
|
# ── Per-camera continuous capture services ────────────────────────────────────
|
|
# capture.sh replaces MotionEye / any NVR for periodic JPEG capture.
|
|
# Generated when CAM_RTSP_<cam> is set; Type=simple (long-running, not oneshot).
|
|
capture_services=()
|
|
for cam in "${CAMERAS[@]}"; do
|
|
rtsp_var="CAM_RTSP_${cam}"
|
|
if [ -n "${!rtsp_var:-}" ]; then
|
|
cat > "$UNIT_DIR/sky-cam-capture-${cam}.service" <<EOF
|
|
[Unit]
|
|
Description=Sky-Cam — ${cam}: continuous RTSP frame capture
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=$SCRIPT_DIR/capture.sh ${cam}
|
|
Restart=on-failure
|
|
RestartSec=30
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
echo " wrote sky-cam-capture-${cam}.service"
|
|
capture_services+=("sky-cam-capture-${cam}")
|
|
else
|
|
echo " WARNING: CAM_RTSP_${cam} not set — skipping capture service for ${cam}"
|
|
echo " (set CAM_RTSP_${cam}=rtsp://... in sky-cam.conf to enable)"
|
|
fi
|
|
done
|
|
|
|
# ── Sunrise audio capture ─────────────────────────────────────────────────────
|
|
# Triggered at 03:00; waits internally until the right time before recording.
|
|
if [ "${AUDIO_ENABLED:-false}" = "true" ]; then
|
|
write_service "sky-cam-audio-capture" \
|
|
"$SUNRISE_CAM: sunrise audio capture" \
|
|
"sunrise-audio-capture.sh"
|
|
write_timer "sky-cam-audio-capture" \
|
|
"$SUNRISE_CAM: sunrise audio capture" \
|
|
"03:00:00"
|
|
timers+=("sky-cam-audio-capture")
|
|
fi
|
|
|
|
# ── Per-camera Four Seasons and full-day jobs ─────────────────────────────────
|
|
# Reads CAMERAS, SCHEDULE_SEASONS_<cam>, SCHEDULE_FULLDAY_<cam> from sky-cam.conf.
|
|
# Scripts receive the camera name as $1 so they know which camera to process.
|
|
for cam in "${CAMERAS[@]}"; do
|
|
sched_seasons_var="SCHEDULE_SEASONS_${cam}"
|
|
sched_fullday_var="SCHEDULE_FULLDAY_${cam}"
|
|
|
|
if [ -n "${!sched_seasons_var:-}" ]; then
|
|
write_service "sky-cam-seasons-${cam}" \
|
|
"${cam}: Four Seasons daily clip" \
|
|
"4-seasons.sh ${cam}" \
|
|
"After=network-online.target\nWants=network-online.target\n"
|
|
write_timer "sky-cam-seasons-${cam}" "${cam}: Four Seasons daily clip" \
|
|
"${!sched_seasons_var}"
|
|
timers+=("sky-cam-seasons-${cam}")
|
|
else
|
|
echo " WARNING: SCHEDULE_SEASONS_${cam} not set — skipping seasons timer for ${cam}"
|
|
fi
|
|
|
|
if [ -n "${!sched_fullday_var:-}" ]; then
|
|
write_service "sky-cam-fullday-${cam}" \
|
|
"${cam}: full-day timelapse" \
|
|
"fullday-video.sh ${cam}"
|
|
write_timer "sky-cam-fullday-${cam}" "${cam}: full-day timelapse" \
|
|
"${!sched_fullday_var}"
|
|
timers+=("sky-cam-fullday-${cam}")
|
|
else
|
|
echo " WARNING: SCHEDULE_FULLDAY_${cam} not set — skipping fullday timer for ${cam}"
|
|
fi
|
|
done
|
|
|
|
# ── Reload and enable ─────────────────────────────────────────────────────────
|
|
echo ""
|
|
$SC daemon-reload
|
|
|
|
for svc in "${capture_services[@]}"; do
|
|
$SC enable --now "${svc}.service" \
|
|
&& echo " enabled + started ${svc}.service" \
|
|
|| echo " WARNING: could not enable ${svc}.service"
|
|
done
|
|
|
|
for timer in "${timers[@]}"; 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-seasons-${CAMERAS[0]}.service -f"
|
|
else
|
|
echo " systemctl --user list-timers 'sky-cam-*'"
|
|
echo " journalctl --user -u sky-cam-seasons-${CAMERAS[0]}.service -f"
|
|
fi
|