sky-cam.conf now sources .env from the same directory at the bottom of the file, so RTSP URLs, Mattermost tokens, and notification credentials can live outside version control. Plaintext credential placeholders removed from conf. install.sh generates .env.example on every run with the correct CAM_RTSP_* variable names derived from the CAMERAS list, so users always know exactly what to put in .env for their specific camera setup. .gitignore: add .env and sunrise-sounds/ (downloaded audio library). https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
237 lines
8.6 KiB
Bash
Executable File
237 lines
8.6 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
|
|
|
|
# ── Generate .env.example ────────────────────────────────────────────────────
|
|
env_example="$HERE/.env.example"
|
|
{
|
|
echo "# sky-cam credentials — copy to .env and fill in real values."
|
|
echo "# .env is gitignored and never checked in."
|
|
echo ""
|
|
echo "# RTSP stream URLs — one line per camera (variable name = CAM_RTSP_<camname>)"
|
|
for cam in "${CAMERAS[@]}"; do
|
|
echo "CAM_RTSP_${cam}=rtsp://admin:PASSWORD@192.168.1.XXX:554/stream1"
|
|
done
|
|
echo ""
|
|
echo "# Mattermost upload"
|
|
echo "mattermost_url=https://your-mattermost-server.example.com"
|
|
echo "access_token=your-access-token-here"
|
|
echo "channel_id=your-daily-upload-channel-id-here"
|
|
echo ""
|
|
echo "# Notifications (uncomment whichever you use)"
|
|
echo "#NTFY_URL=https://ntfy.sh/your-topic"
|
|
echo "#EMAIL_TO=you@example.com"
|
|
echo "#MM_NOTIFY_CHANNEL_ID=your-notify-channel-id-here"
|
|
} > "$env_example"
|
|
echo " wrote .env.example (cp .env.example .env then fill in real values)"
|
|
|
|
# ── 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
|