diff options
| -rwxr-xr-x | deploy.sh | 40 | ||||
| -rw-r--r-- | doc/bom-radar-spec.md | 249 | ||||
| -rw-r--r-- | nginx/radar.pestrel.com.conf | 23 | ||||
| -rw-r--r-- | radar.index.html | 12 | ||||
| -rwxr-xr-x | radarFetch.sh | 190 | ||||
| -rwxr-xr-x | setup.sh | 24 | ||||
| -rw-r--r-- | systemd/radar-retry.service | 12 | ||||
| -rw-r--r-- | systemd/radar-retry.timer | 8 | ||||
| -rw-r--r-- | systemd/radar.service | 13 | ||||
| -rw-r--r-- | systemd/radar.timer | 11 |
10 files changed, 569 insertions, 13 deletions
@@ -4,13 +4,35 @@ set -euo pipefail REMOTE=st33v@cremonde PORT=40022 -echo "Deploying synopticChart.sh to ${REMOTE}..." -scp -P "$PORT" synopticChart.sh "${REMOTE}:/opt/synoptic/synopticChart.sh" +TARGET="${1:-synoptic}" -echo "Restarting synoptic.service..." -if ssh -t -p "$PORT" "$REMOTE" "sudo systemctl restart synoptic.service"; then - echo "SUCCESS: synoptic.service ran cleanly." -else - echo "FAILURE: synoptic.service exited non-zero." >&2 - exit 1 -fi +case "$TARGET" in + synoptic) + echo "Deploying synopticChart.sh to ${REMOTE}..." + scp -P "$PORT" synopticChart.sh "${REMOTE}:/opt/synoptic/synopticChart.sh" + + echo "Restarting synoptic.service..." + if ssh -t -p "$PORT" "$REMOTE" "sudo systemctl restart synoptic.service"; then + echo "SUCCESS: synoptic.service ran cleanly." + else + echo "FAILURE: synoptic.service exited non-zero." >&2 + exit 1 + fi + ;; + radar) + echo "Deploying radarFetch.sh to ${REMOTE}..." + scp -P "$PORT" radarFetch.sh "${REMOTE}:/opt/radar/radarFetch.sh" + + echo "Restarting radar.service..." + if ssh -t -p "$PORT" "$REMOTE" "sudo systemctl restart radar.service"; then + echo "SUCCESS: radar.service ran cleanly." + else + echo "FAILURE: radar.service exited non-zero." >&2 + exit 1 + fi + ;; + *) + echo "Usage: $0 [synoptic|radar]" >&2 + exit 2 + ;; +esac diff --git a/doc/bom-radar-spec.md b/doc/bom-radar-spec.md new file mode 100644 index 0000000..3123a16 --- /dev/null +++ b/doc/bom-radar-spec.md @@ -0,0 +1,249 @@ +--- +title: "BOM Radar Display — Sydney (IDR713)" +date-created: "2026-06-09" +version: "0.1" +type: "spec" +author: + - "st33v" + - "claude-opus-4-8" +model: + - "claude-opus-4-8" +tldr: "Extend the existing bomsynoptic app to fetch, composite and serve Sydney radar (IDR713) from BOM anonymous FTP. Layer model: static transparencies (cached) plus a rolling buffer of the last six 6-minute rain-echo frames, served as a loopable sequence alongside the synoptic chart." +status: "draft" +spec-kind: "feature" +target-repo: "bomsynoptic" +target-machine: "cremonde" +dependencies: [] +design-chat: "https://claude.ai/chat/..." +phase: "1" +phases-planned: 1 +implementing-agent: "claude-code" +reader-targets: + - "claude-code" + - "st33v" +tags: + - "bom" + - "radar" + - "ftp" +--- + +# BOM Radar Display — Sydney (IDR713) + +## 1. Purpose + +The `bomsynoptic` app currently fetches and serves the BOM MSLP synoptic +chart on a six-hourly cadence. This spec adds a second product class: +**rain radar**, beginning with the **128 km Sydney (Terrey Hills)** radar, +product code **`IDR713`**. + +Unlike the synoptic chart — a single self-contained image — a radar view is a +*composite* of several PNG layers that must be stacked in the correct order, +and the rain-echo layer refreshes roughly every six minutes. The deliverable +is the current radar image plus a short loop of the last half-dozen frames, +served in the same manner as the existing synoptic chart. + +Brisbane (Mt Stapylton) and other radars are explicitly **out of scope** for +this phase; the design must make duplication to a new product code trivial +(§9). + +## 2. How BOM radar works (background for the implementer) + +BOM does not publish a single ready-made radar image. It publishes a set of +**transparent PNG layers**, all sharing identical pixel dimensions, intended +to be alpha-composited in a fixed order. Two distinct cadences apply: + +- **Static layers** (basemap, topography, range rings, place labels, feature + overlays, legend) change only rarely — when BOM revises the basemap. Fetch + once, cache, refresh occasionally (e.g. daily). +- **Dynamic layers** (the rain-echo frames) are emitted **every ~6 minutes**. + Each frame is a separate timestamped file. Several recent frames are present + on the server at any moment; we take the most recent *N*. + +All files live under BOM's **anonymous FTP** service. Availability is +best-effort — BOM explicitly does not guarantee it — so resilience matters +(§8). + +## 3. FTP source + +Anonymous FTP, no credentials beyond user `anonymous` (empty / any password). + +| Layer class | Directory | +|---|---| +| Rain-echo frames (dynamic) | `ftp://ftp.bom.gov.au/anon/gen/radar/` | +| Transparencies (static) | `ftp://ftp.bom.gov.au/anon/gen/radar_transparencies/` | + +### 3.1 Dynamic frames + +Filename pattern in `/anon/gen/radar/`: + +``` +IDR713.T.<YYYYMMDDHHMM>.png +``` + +- The timestamp is **UTC**. +- Because the timestamp is fixed-width and big-endian, a plain lexical sort of + the matching filenames is also a chronological sort. +- Selection: list the directory, filter to the `IDR713.T.` prefix, sort, take + the last **6** (configurable as `FRAME_COUNT`, default 6). + +### 3.2 Static transparencies + +Files in `/anon/gen/radar_transparencies/`, all prefixed `IDR713.`: + +| File | Role | +|---|---| +| `IDR713.background.png` | base map: land/sea, coastline | +| `IDR713.topography.png` | terrain shading | +| `IDR713.range.png` | range rings | +| `IDR713.locations.png` | town / city labels | +| `IDR713.roads.png` | roads (optional overlay) | +| `IDR713.rail.png` | rail (optional overlay) | +| `IDR713.waterways.png` | rivers (optional overlay) | +| `IDR713.catchments.png` | catchment boundaries (optional) | +| `IDR713.wthrDistricts.png`| weather district boundaries (optional) | +| `IDR713.legend.0.png` | colour-scale legend | + +Not every transparency exists for every radar. The implementer should fetch +the directory listing once and treat any missing optional layer as absent +rather than failing. + +## 4. Compositing model + +Alpha-composite **bottom → top** in this order. Optional feature overlays sit +between topography and the radar echoes or above them per taste; the +constraint that matters is that **labels and range rings render on top of the +rain** so they stay legible. + +``` +1. IDR713.background.png ← bottom (opaque base) +2. IDR713.topography.png +3. (optional) catchments / waterways / roads / rail / wthrDistricts +4. IDR713.T.<timestamp>.png ← the rain echoes (per frame) +5. IDR713.range.png +6. IDR713.locations.png ← top (labels) +``` + +The **legend** (`IDR713.legend.0.png`) is a separate strip. Either composite +it into a fixed corner/margin of the output, or serve it as a sibling asset +the front-end positions beside the loop. Recommend compositing it in so the +served image is self-describing. + +Implementation note: build the **static base** (layers 1–3 + the on-top +overlays 5–6 are split around the echo, so cache them as two precomposited +plates — a *lower plate* under the echo and an *upper plate* over it). For +each frame: `lower_plate → echo → upper_plate`. This reduces per-frame work to +two composites instead of seven. + +Suggested tooling: Pillow (`PIL.Image.alpha_composite`) keeps the dependency +footprint small and matches the likely existing stack; ImageMagick is an +acceptable alternative if already present. + +## 5. Fetch cadence & rolling buffer + +- **Echo frames:** poll every **6 minutes** (`POLL_INTERVAL`, default 360 s). + On each poll, fetch only frames not already held locally (compare against + the buffer). Maintain a rolling buffer of the last `FRAME_COUNT` (6) frames; + evict older raw frames and their composites. +- **Transparencies:** refresh on startup and then daily (`TRANSPARENCY_TTL`, + default 24 h). Survive a failed refresh by keeping the cached plates. +- Do not hammer the server: the 6-minute poll matches BOM's emission rate. + Skip a poll cleanly if the previous one is still running. + +Note the contrast with the existing synoptic job: synoptic is six-**hourly**; +radar is six-**minute**. The scheduler must accommodate both — reuse the +existing scheduling mechanism if it generalises, otherwise run radar on its +own timer. + +## 6. Outputs + +Produce two things per cycle: + +1. **Latest still** — the most recent frame composited over the plates. + Stable filename (e.g. `idr713-latest.png`) so the served URL never + changes. +2. **Loop** — the last 6 composited frames. Serve either as: + - an **animated APNG/GIF** (`idr713-loop.png` / `.gif`) — simplest for the + front-end, single asset, frame timing baked in; or + - a **numbered sequence** (`idr713.0.png … idr713.5.png`) plus a small JSON + manifest of frame timestamps, leaving animation to client-side JS. + +Recommend the **sequence + manifest** route: it mirrors how BOM's own viewer +works, lets the front-end control frame rate and pausing, and avoids +re-encoding a whole animation every six minutes. The manifest also lets the UI +print the real observation time (converted from UTC to the user's / Sydney +local time) under each frame. + +## 7. Storage layout (suggested) + +``` +<data-root>/radar/idr713/ + transparencies/ # cached static PNGs, refreshed daily + frames/ # raw echo PNGs, rolling buffer of 6 + out/ + idr713-latest.png + idr713.0.png … idr713.5.png + manifest.json # [{seq, utc, local, src_filename}, …] +``` + +Mirror whatever convention the synoptic side already uses for its served +directory; the above is a fallback if there's no established pattern. + +## 8. Resilience + +- **Missing / partial frames:** if a poll returns fewer than 6 frames, serve + what exists; never blank the display. +- **FTP unreachable:** retain and keep serving the last good composites; log + and retry next cycle. BOM does not guarantee availability. +- **Stale data:** stamp the manifest with the newest frame's UTC time so the + UI can grey out / warn if the latest echo is older than, say, 20 minutes. +- **Corrupt download:** validate each PNG opens before it enters the buffer; + discard and retry otherwise. +- **Dimension mismatch:** if a transparency and the echo frames disagree on + size (can happen across a BOM basemap revision), refetch transparencies + before compositing. + +## 9. Generalising to other radars + +Parameterise on the product code. A single config entry should be enough to +add Brisbane (Mt Stapylton) later: + +```yaml +radars: + - id: "IDR713" # Sydney (Terrey Hills) 128 km + enabled: true + - id: "IDR663" # Brisbane (Mt Stapylton) 128 km — confirm code before enabling + enabled: false +``` + +Everything else — directories, filename patterns, layer set, compositing +order — is identical across radars, differing only by the `IDRnnn` stem. +**Do not hardcode `IDR713`.** (The Brisbane code above is a placeholder; +st33v will confirm the exact product before that radar is switched on.) + +## 10. Copyright / terms of use + +BOM anonymous-FTP products are licensed for personal or in-organisation use, +**not** commercial use, and republishing is meant to go through Registered +User services. Since `st33v.com` is a public-facing surface, this is worth a +deliberate decision rather than an oversight. Options: attribute BOM clearly +and treat the personal/non-commercial display as within bounds, gate the radar +behind the internal (WireGuard) surface only, or register. **Flag for st33v — +do not silently publish.** + +## 11. Out of scope (this phase) + +- Brisbane and all radars other than IDR713 (config-ready, disabled). +- Doppler wind / velocity products and the rainfall-accumulation products. +- Historical / archived frames beyond the rolling 6-frame buffer. +- Any front-end beyond serving the still, the sequence, and the manifest in + the same idiom as the existing synoptic chart. + +## 12. Open questions for Claude Code + +1. Does the existing scheduler generalise to a 6-minute timer, or does radar + need its own? +2. What's the established served-assets directory and URL pattern on the + synoptic side — reuse it. +3. Pillow already a dependency, or introduce it? +4. Loop-as-sequence (recommended) vs animated single asset — confirm against + the existing front-end's capabilities. diff --git a/nginx/radar.pestrel.com.conf b/nginx/radar.pestrel.com.conf new file mode 100644 index 0000000..b7d9c08 --- /dev/null +++ b/nginx/radar.pestrel.com.conf @@ -0,0 +1,23 @@ +# BOM radar loop — radar.pestrel.com +# Installed to /etc/nginx/conf.d/radar.conf by setup.sh +# +# Requires /etc/nginx/nginx.conf to include: +# include /etc/nginx/conf.d/*.conf; + +server { + listen 80; + listen [::]:80; + server_name radar.pestrel.com; + + root /srv/www/radar; + index index.html; + + # APNG refreshes every ~6 minutes; let clients revalidate often. + location ~ \.apng$ { + add_header Cache-Control "no-cache"; + } + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/radar.index.html b/radar.index.html new file mode 100644 index 0000000..63ecec6 --- /dev/null +++ b/radar.index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<title>Radar — Sydney</title> +<style> + body { margin: 0; background: #000; display: flex; justify-content: center; align-items: center; height: 100vh; } + img { max-width: 100%; max-height: 100vh; } +</style> +</head> +<body><img src="/idr713-loop.apng" alt="Sydney radar loop"></body> +</html> diff --git a/radarFetch.sh b/radarFetch.sh new file mode 100755 index 0000000..0dcc457 --- /dev/null +++ b/radarFetch.sh @@ -0,0 +1,190 @@ +#!/bin/bash +set -euo pipefail +shopt -s nullglob + +# SJP 2026-06-09 +# Fetch BOM radar layers via anonymous FTP, alpha-composite them, +# publish the last FRAME_COUNT frames as an APNG loop. +# Parameterised on product code; default IDR713 (Sydney 128 km). +# Deployed to /opt/radar/radarFetch.sh on cremonde for radar.pestrel.com. + +RADAR_ID="${1:-IDR713}" +FRAME_COUNT="${FRAME_COUNT:-6}" +FRAME_DELAY="${FRAME_DELAY:-50}" # centiseconds per frame +END_PAUSE="${END_PAUSE:-150}" # centiseconds, pause on last frame +TRANSPARENCY_TTL_HOURS="${TRANSPARENCY_TTL_HOURS:-24}" + +DATA_ROOT="/var/lib/radar/${RADAR_ID,,}" +TRANS_DIR="${DATA_ROOT}/transparencies" +FRAMES_DIR="${DATA_ROOT}/frames" +PLATES_DIR="${DATA_ROOT}/plates" +OUT_DIR="${DATA_ROOT}/out" +PUBLISH_DIR="/srv/www/radar" +PUBLISH_PATH="${PUBLISH_DIR}/${RADAR_ID,,}-loop.apng" + +FTP_DYNAMIC="ftp://ftp.bom.gov.au/anon/gen/radar/" +FTP_STATIC="ftp://ftp.bom.gov.au/anon/gen/radar_transparencies/" + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || { echo "Required command not found: $1" >&2; exit 127; } +} +require_cmd curl +require_cmd magick + +mkdir -p "$TRANS_DIR" "$FRAMES_DIR" "$PLATES_DIR" "$OUT_DIR" + +fetch_validated() { + # $1 url $2 dest + local url="$1" dest="$2" tmp="${2}.tmp" + curl -q -fs -o "$tmp" "$url" || { rm -f "$tmp"; return 1; } + if magick identify "$tmp" >/dev/null 2>&1; then + mv "$tmp" "$dest" + else + rm -f "$tmp" + return 1 + fi +} + +# --- 1. Refresh transparencies if stale ---------------------------------- +TRANS_MARKER="${TRANS_DIR}/.last_refreshed" +needs_refresh=1 +if [ -f "$TRANS_MARKER" ]; then + age_h=$(( ( $(date +%s) - $(stat -c %Y "$TRANS_MARKER") ) / 3600 )) + [ "$age_h" -lt "$TRANSPARENCY_TTL_HOURS" ] && needs_refresh=0 +fi + +if [ "$needs_refresh" -eq 1 ]; then + echo "Refreshing transparencies for ${RADAR_ID}..." + if listing=$(curl -q -fs "$FTP_STATIC"); then + # ls -l style listing; filename is the last whitespace-delimited field + while read -r fname; do + [ -n "$fname" ] || continue + fetch_validated "${FTP_STATIC}${fname}" "${TRANS_DIR}/${fname}" \ + || echo " failed: ${fname}" >&2 + done < <(echo "$listing" | awk '{print $NF}' | grep "^${RADAR_ID}\." || true) + touch "$TRANS_MARKER" + # invalidate precomposited plates + rm -f "${PLATES_DIR}/lower.png" "${PLATES_DIR}/upper.png" + else + echo "Transparency listing failed; keeping cached copies." >&2 + fi +fi + +# --- 2. Build precomposited plates --------------------------------------- +BACKGROUND="${TRANS_DIR}/${RADAR_ID}.background.png" +if [ ! -f "$BACKGROUND" ]; then + echo "No background transparency cached; cannot composite." >&2 + exit 1 +fi + +existing_layers() { + # echo paths that exist, in given order + local base p + for base in "$@"; do + p="${TRANS_DIR}/${RADAR_ID}.${base}.png" + [ -f "$p" ] && echo "$p" + done +} + +if [ ! -f "${PLATES_DIR}/lower.png" ]; then + # background (opaque) + topography + optional feature overlays under the echo + readarray -t lower_layers < <(existing_layers \ + background topography catchments waterways wthrDistricts roads rail) + magick "${lower_layers[@]}" -background none -layers flatten "${PLATES_DIR}/lower.png" +fi + +if [ ! -f "${PLATES_DIR}/upper.png" ]; then + # range rings + place labels go over the echo so they stay legible + readarray -t upper_layers < <(existing_layers range locations) + if [ "${#upper_layers[@]}" -gt 0 ]; then + magick "${upper_layers[@]}" -background none -layers flatten "${PLATES_DIR}/upper.png" + fi +fi + +# --- 3. Determine current top-N frames on the FTP ------------------------ +echo "Listing dynamic frames..." +if ! listing=$(curl -q -fs "$FTP_DYNAMIC"); then + echo "Dynamic listing failed; serving last good loop." >&2 + exit 0 +fi + +mapfile -t remote_frames < <(echo "$listing" \ + | awk '{print $NF}' \ + | grep "^${RADAR_ID}\.T\.[0-9]\{12\}\.png$" \ + | sort \ + | tail -n "$FRAME_COUNT") + +if [ "${#remote_frames[@]}" -eq 0 ]; then + echo "No remote frames for ${RADAR_ID}; serving last good loop." >&2 + exit 0 +fi + +# --- 4. Fetch missing frames; evict frames outside the rolling buffer ---- +for fname in "${remote_frames[@]}"; do + if [ ! -f "${FRAMES_DIR}/${fname}" ]; then + echo " fetching ${fname}" + fetch_validated "${FTP_DYNAMIC}${fname}" "${FRAMES_DIR}/${fname}" \ + || echo " failed: ${fname}" >&2 + fi +done + +keep_set=" $(printf '%s ' "${remote_frames[@]}")" +for existing in "$FRAMES_DIR"/${RADAR_ID}.T.*.png; do + bn=$(basename "$existing") + case "$keep_set" in + *" $bn "*) ;; + *) rm -f "$existing" ;; + esac +done + +# --- 5. Composite each held frame ---------------------------------------- +LEGEND="${TRANS_DIR}/${RADAR_ID}.legend.0.png" +HAVE_UPPER=0; [ -f "${PLATES_DIR}/upper.png" ] && HAVE_UPPER=1 +HAVE_LEGEND=0; [ -f "$LEGEND" ] && HAVE_LEGEND=1 + +# clear stale composited frames +rm -f "${OUT_DIR}"/frame.*.png + +composited=() +seq=0 +for fname in "${remote_frames[@]}"; do + src="${FRAMES_DIR}/${fname}" + [ -f "$src" ] || continue + out="${OUT_DIR}/frame.$(printf '%02d' "$seq").png" + + cmd=(magick "${PLATES_DIR}/lower.png" "$src" -composite) + [ "$HAVE_UPPER" -eq 1 ] && cmd+=("${PLATES_DIR}/upper.png" -composite) + [ "$HAVE_LEGEND" -eq 1 ] && cmd+=(-gravity southeast "$LEGEND" -composite +gravity) + cmd+=("$out") + "${cmd[@]}" + + composited+=("$out") + seq=$((seq + 1)) +done + +if [ "${#composited[@]}" -eq 0 ]; then + echo "No frames composited; serving last good loop." >&2 + exit 0 +fi + +# --- 6. Assemble APNG loop ----------------------------------------------- +# Per-frame delays: last frame gets END_PAUSE so the eye can rest on "now". +mkdir -p "$PUBLISH_DIR" +TMP_APNG="${OUT_DIR}/loop.apng.tmp" + +build_cmd=(magick -loop 0) +last_idx=$(( ${#composited[@]} - 1 )) +for i in "${!composited[@]}"; do + if [ "$i" -eq "$last_idx" ]; then + build_cmd+=(-delay "$END_PAUSE" "${composited[$i]}") + else + build_cmd+=(-delay "$FRAME_DELAY" "${composited[$i]}") + fi +done +build_cmd+=("$TMP_APNG") +"${build_cmd[@]}" + +install -m 644 "$TMP_APNG" "$PUBLISH_PATH" +rm -f "$TMP_APNG" + +echo "Published ${PUBLISH_PATH} (frames=${#composited[@]}, latest=${remote_frames[-1]})" @@ -30,6 +30,11 @@ if grep -rl "server_name.*pestrel\.com" /etc/nginx/ 2>/dev/null; then CONFLICT=1 fi +if grep -rl "server_name.*radar\.pestrel\.com" /etc/nginx/ 2>/dev/null; then + echo "WARNING: The above nginx config(s) already reference radar.pestrel.com — check for duplicate server blocks." + CONFLICT=1 +fi + if grep -rl "listen.*80.*default_server" /etc/nginx/ 2>/dev/null; then echo "WARNING: The above nginx config(s) define a default_server on port 80 — may intercept traffic intended for this site." CONFLICT=1 @@ -55,30 +60,41 @@ install -d /srv/www install -d -o "$OWNER" -g "$OWNER" /srv/www/pestrel chown "$OWNER:$OWNER" /srv/www/pestrel +install -d -o "$OWNER" -g "$OWNER" /opt/radar +install -d -o "$OWNER" -g "$OWNER" /var/lib/radar +install -d -o "$OWNER" -g "$OWNER" /srv/www/radar + # --------------------------------------------------------------------------- # Web content # --------------------------------------------------------------------------- echo "==> Writing index.html..." install -o "$OWNER" -g "$OWNER" -m 644 "$SCRIPT_DIR/index.html" /srv/www/pestrel/index.html +install -o "$OWNER" -g "$OWNER" -m 644 "$SCRIPT_DIR/radar.index.html" /srv/www/radar/index.html + +echo "==> Installing radarFetch.sh..." +install -o "$OWNER" -g "$OWNER" -m 755 "$SCRIPT_DIR/radarFetch.sh" /opt/radar/radarFetch.sh # --------------------------------------------------------------------------- # Systemd units # --------------------------------------------------------------------------- echo "==> Installing systemd unit files..." -for unit in synoptic.service synoptic.timer synoptic-retry.service synoptic-retry.timer; do +for unit in synoptic.service synoptic.timer synoptic-retry.service synoptic-retry.timer \ + radar.service radar.timer radar-retry.service radar-retry.timer; do install -m 644 "$SCRIPT_DIR/systemd/${unit}" "/etc/systemd/system/${unit}" echo " installed ${unit}" done -echo "==> Reloading systemd and enabling timer..." +echo "==> Reloading systemd and enabling timers..." systemctl daemon-reload systemctl enable --now synoptic.timer +systemctl enable --now radar.timer # --------------------------------------------------------------------------- # Nginx # --------------------------------------------------------------------------- -#echo "==> Installing nginx config..." -#install -m 644 "$SCRIPT_DIR/nginx/pestrel.com.conf" /etc/nginx/conf.d/synoptic.conf +#echo "==> Installing nginx configs..." +#install -m 644 "$SCRIPT_DIR/nginx/pestrel.com.conf" /etc/nginx/conf.d/synoptic.conf +#install -m 644 "$SCRIPT_DIR/nginx/radar.pestrel.com.conf" /etc/nginx/conf.d/radar.conf #echo "==> Testing nginx config..." #nginx -t diff --git a/systemd/radar-retry.service b/systemd/radar-retry.service new file mode 100644 index 0000000..b878303 --- /dev/null +++ b/systemd/radar-retry.service @@ -0,0 +1,12 @@ +[Unit] +Description=Retry BOM radar fetch (after failure) + +[Service] +Type=oneshot +User=st33v +WorkingDirectory=/var/lib/radar +ExecStart=/opt/radar/radarFetch.sh +ExecStopPost=/bin/sh -c 'STATUS=SUCCESS; [ "$$EXIT_STATUS" != "0" ] && STATUS=FAILURE; logger -t radar-retry -p user.err "radar-retry $$STATUS exit=$$EXIT_STATUS"' +SyslogIdentifier=radar-retry +StandardOutput=journal +StandardError=journal diff --git a/systemd/radar-retry.timer b/systemd/radar-retry.timer new file mode 100644 index 0000000..d7c8d9b --- /dev/null +++ b/systemd/radar-retry.timer @@ -0,0 +1,8 @@ +[Unit] +Description=Retry radar fetch 2 minutes after failure + +[Timer] +OnActiveSec=2min + +[Install] +WantedBy=timers.target diff --git a/systemd/radar.service b/systemd/radar.service new file mode 100644 index 0000000..8e9dd40 --- /dev/null +++ b/systemd/radar.service @@ -0,0 +1,13 @@ +[Unit] +Description=Fetch BOM radar and publish loop APNG +OnFailure=radar-retry.timer + +[Service] +Type=oneshot +User=st33v +WorkingDirectory=/var/lib/radar +ExecStart=/opt/radar/radarFetch.sh +ExecStopPost=/bin/sh -c 'STATUS=SUCCESS; [ "$$EXIT_STATUS" != "0" ] && STATUS=FAILURE; logger -t radar -p user.err "radar $$STATUS exit=$$EXIT_STATUS"' +SyslogIdentifier=radar +StandardOutput=journal +StandardError=journal diff --git a/systemd/radar.timer b/systemd/radar.timer new file mode 100644 index 0000000..c147481 --- /dev/null +++ b/systemd/radar.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run BOM radar fetch every 6 minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=6min +AccuracySec=15s +Persistent=true + +[Install] +WantedBy=timers.target |
