From 66f05cf9a71b5e5f64ca23c59d482b840b314358 Mon Sep 17 00:00:00 2001 From: Brandyn / Techy Date: Sun, 1 Nov 2026 01:08:43 -0500 Subject: [PATCH] Push-to-deploy (OFF by default): vanguard-deploy applies only changed parts with a live-stream guard for MediaMTX, rollback, pending timer; dumpd /deploy webhook gated by DEPLOY_AUTO; configure --apply; switchToClone.sh; draft CI workflow; committed plugin zip Co-Authored-By: Claude Fable 5.1 --- .gitea/draft-workflows/ci.yml | 18 +++ .gitignore | 1 + CommunityDiscordMedia.md | 11 ++ README.md | 31 +++++ STATUS.md | 2 + bin/dumpd.py | 50 ++++++++ check.sh | 2 +- configure.py | 16 +++ deploy.sh | 113 ++++++++++++++++++ install.sh | 2 +- jellyfin-plugin-vanguard/.gitignore | 3 +- .../dist/VanguardMetadata_0.1.3.0.zip | Bin 0 -> 43730 bytes switchToClone.sh | 33 +++++ systemd/deploy-pending.service | 7 ++ systemd/deploy-pending.timer | 9 ++ 15 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 .gitea/draft-workflows/ci.yml create mode 100755 deploy.sh create mode 100644 jellyfin-plugin-vanguard/dist/VanguardMetadata_0.1.3.0.zip create mode 100755 switchToClone.sh create mode 100644 systemd/deploy-pending.service create mode 100644 systemd/deploy-pending.timer diff --git a/.gitea/draft-workflows/ci.yml b/.gitea/draft-workflows/ci.yml new file mode 100644 index 0000000..5512b73 --- /dev/null +++ b/.gitea/draft-workflows/ci.yml @@ -0,0 +1,18 @@ +# DRAFT — inert while it lives in .gitea/draft-workflows/. Activate with: git mv .gitea/draft-workflows/ci.yml .gitea/workflows/ci.yml +# Checks every push; builds + tests the Jellyfin plugin. Deploying is NOT done here (the runner must not touch +# the host): the Gitea push webhook -> dumpd /deploy -> vanguard-deploy handles that, when DEPLOY_AUTO=1. +name: ci +on: [push, pull_request] +jobs: + checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: sudo apt-get update -q && sudo apt-get install -y -q python3 python3-yaml nodejs npm ffmpeg jq + - run: bash check.sh + plugin: + runs-on: dotnet + steps: + - uses: actions/checkout@v4 + - run: cd jellyfin-plugin-vanguard && dotnet test Jellyfin.Plugin.Vanguard.Tests --nologo -v q + - run: cd jellyfin-plugin-vanguard && dotnet build Jellyfin.Plugin.Vanguard -c Release --nologo -v q diff --git a/.gitignore b/.gitignore index f11fe10..e854d15 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ mediamtx.yml copyparty.conf Caddyfile stream-token.secret +deploy.env diff --git a/CommunityDiscordMedia.md b/CommunityDiscordMedia.md index b81b011..796a65b 100644 --- a/CommunityDiscordMedia.md +++ b/CommunityDiscordMedia.md @@ -318,6 +318,17 @@ permanent reference is needed for long sessions, not just a start burst. export at 2560×1440) - rejected: LTC audio (SRT multitrack + per-PC generator); clock inside the picture; cropping at remux (re-encode) +### Phase 13 — push-to-deploy (built; OFF — `DEPLOY_AUTO=0`, CI workflow in draft-workflows/) +- server folder = git clone (`switchToClone.sh`); generated files gitignored and preserved +- `vanguard-deploy pull|apply|rollback|pending|status` applies only the changed parts; `check.sh` gates it; + MediaMTX restarts are deferred while anything is live (`deploy-pending.timer`) +- Gitea push webhook → dumpd `/deploy` (bridge port, HMAC) → `vanguard-deploy pull` when `DEPLOY_AUTO=1` +- CI (draft): `check.sh` + plugin tests/build on the runner; the runner never touches the host +- [ ] run `switchToClone.sh key` / add deploy key / `switchToClone.sh clone` on the server +- [ ] `./configure.py --apply` once (installs vanguard-deploy, deploy.env, the pending timer) +- [ ] add the Gitea webhook (secret from `.env`); watch `journalctl -u dumpd | grep deploy` for a few pushes +- [ ] when trusted: `DEPLOY_AUTO=1` in `.env` + `./configure.py --apply`; move `ci.yml` into `.gitea/workflows/` + ### Option — more clip sources (Medal, Twitch/YouTube/Kick clips, Discord uploads) Today the only clip intake is a local clipper folder synced by Syncthing. Members without that diff --git a/README.md b/README.md index 85dfbc7..dd09ee6 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,37 @@ files are not revisited. Clips are always converted to CFR regardless. Planned alternative: re-encode flagged streams off-site on a machine with a hardware encoder (home box NVENC or a Spot GPU) and push the result back — see CommunityDiscordMedia.md → Later. +## Deployment (push to deploy — built, OFF by default) + +The server folder is a git clone of this repo (`./switchToClone.sh key` → add the printed deploy key in Gitea → +`./switchToClone.sh clone`). Generated files (`.env`, `Caddyfile`, `mediamtx.yml`, `copyparty.conf`, the token +secret) are gitignored and survive pulls; static pages are bind-mounted from the checkout. + +`sudo vanguard-deploy pull` fetches `main` and applies **only what changed**, after `check.sh`: + +| changed | action | interruption | +|---|---|---| +| `configure.py`, `dock/`, `clips/`, `about/`, stignore | `configure.py --apply` → Caddy reload, copyparty restart | none / a second | +| `bin/*`, `systemd/*` | install; dumpd restart when it changed; timers pick up next run | a second | +| `firewall/nftables.conf` | `nft -f` | none | +| `docker-compose.yml`, bot image | `compose up -d --build` for changed services | that service | +| anything that changes `mediamtx.yml` or the mediamtx service | **restart only when no stream is live**; otherwise deferred, `deploy-pending.timer` retries every 5 min | none while live | +| `jellyfin-plugin-vanguard/dist/*.zip` (committed) | `installJellyfinPlugin.sh` | Jellyfin restart | +| `live/` | `npm run build` on the server → `/srv/live` | none | + +`sudo vanguard-deploy rollback` checks out the previously deployed revision and applies the reverse diff. +`sudo vanguard-deploy status` shows revisions, auto mode, pending restarts, live streams. + +**Automatic**: Gitea → repo → Settings → Webhooks → Gitea, URL `http://:8081/deploy` +(`docker network inspect vanguard-stream_default -f '{{(index .IPAM.Config 0).Gateway}}'`, usually `172.18.0.1`), +secret = `DEPLOY_WEBHOOK_SECRET` from `.env`, push events, branch `main`. dumpd verifies the HMAC and runs +`vanguard-deploy pull` **only when `DEPLOY_AUTO=1`** in `.env` (then `./configure.py --apply` to push the setting +to `/etc/vanguard/deploy.env`). Until then every push is logged (`journalctl -u dumpd | grep deploy`) and ignored. +Optional `DEPLOY_NOTIFY_WEBHOOK_URL` posts deploy results to Discord. + +CI: `.gitea/draft-workflows/ci.yml` (inert until moved to `.gitea/workflows/`) runs `check.sh` and the plugin +tests/build on the Actions runner. The runner never deploys — it must not touch the host. + ## Layout on the server /srv/media/streams///.live/<ts>.mp4 recording in progress (hidden from Jellyfin/copyparty) diff --git a/STATUS.md b/STATUS.md index 149b6b7..aa2e3ae 100644 --- a/STATUS.md +++ b/STATUS.md @@ -18,6 +18,8 @@ Source of truth: this repo, `techy/Media-Server-Setup` on the stack's own Gitea | Every 5 min | Each user's clips folder is measured against their quota (100 GB default, per-user overrides); over quota → their Syncthing folder is paused, under 90 % → resumed; the clips page shows usage | dumpd quota loop — **pending deploy** | | Daily 05:00 ET | Dumped nights older than 3 days deleted for good | trash purge (systemd timer) | | On boot | Everything restarts itself after the media mount is up; interrupted recordings stay playable | systemd + Docker | +| Every 5 min | If a deploy deferred a MediaMTX restart, retry when no stream is live | `deploy-pending.timer` — **pending first apply** | +| On push to `main` | (OFF: `DEPLOY_AUTO=0`) Gitea webhook → dumpd → `vanguard-deploy pull` applies only what changed | **built, disabled** | | Continuous | Active streams are watchable live (WebRTC, HLS fallback) by Discord-server members; optional "is live" Discord post when a stream starts | MediaMTX + live portal (page deployed; MediaMTX port fix pending apply) | | **Not yet running** | Pull of files older than 15 days to the home HDD | `home/rclone-archive.sh`, needs the home box | | **Not yet running** | Nightly backup of the Authentik database (Postgres) | planned | diff --git a/bin/dumpd.py b/bin/dumpd.py index 6f19a3d..c1cac8d 100755 --- a/bin/dumpd.py +++ b/bin/dumpd.py @@ -9,6 +9,9 @@ GET /me/stream-token -> short-lived publish token for the caller (member of str GET /me/clips -> Syncthing self-service status for the caller: our device id, their folder/device state POST /me/clips -> {"device_id": "..."}: create clips/<user> (+ .stignore), add their device to our Syncthing, create the receive-only folder clips-<user> shared with them (min free 300 GB). Idempotent. +POST /deploy -> Gitea push webhook (bridge port only, HMAC-verified with DEPLOY_WEBHOOK_SECRET from + /etc/vanguard/deploy.env). Runs /usr/local/bin/vanguard-deploy pull ONLY when DEPLOY_AUTO=1; + otherwise logs the push and answers 202 "auto-deploy disabled". POST /mtx-auth -> MediaMTX authHTTP hook: allows read/playback; allows publish iff user/pass carries a valid token whose user owns the path prefix. Tokens are HMAC-signed with /etc/vanguard/stream-token.secret. @@ -244,6 +247,34 @@ def clips_setup(user, device_id, device_name=None): print(f"{datetime.now(TZ):%F %T} clips setup user={user} device={device_id[:7]}… folder={fid}", flush=True) return clips_status(user) +DEPLOY_ENV = Path(os.environ.get("DEPLOY_ENV_FILE", "/etc/vanguard/deploy.env")) +def deploy_env(): + d = {} + try: + for line in DEPLOY_ENV.read_text().splitlines(): + if "=" in line and not line.startswith("#"): k, v = line.split("=", 1); d[k.strip()] = v.strip() + except OSError: + pass + return d + +_deploy_lock = {"running": False} +def run_deploy(reason): + import subprocess, threading + if _deploy_lock["running"]: return False + _deploy_lock["running"] = True + def go(): + try: + print(f"{datetime.now(TZ):%F %T} deploy: start ({reason})", flush=True) + r = subprocess.run(["/usr/local/bin/vanguard-deploy", "pull"], capture_output=True, text=True, timeout=1800) + for line in (r.stdout + r.stderr).splitlines()[-40:]: print(f"deploy| {line}", flush=True) + print(f"{datetime.now(TZ):%F %T} deploy: exit {r.returncode}", flush=True) + except Exception as e: + print(f"{datetime.now(TZ):%F %T} deploy: error {e}", flush=True) + finally: + _deploy_lock["running"] = False + threading.Thread(target=go, daemon=True).start() + return True + def token_secret(): try: sec = TOKEN_SECRET_FILE.read_bytes().strip() @@ -354,6 +385,25 @@ class H(BaseHTTPRequestHandler): "files": [str(f.relative_to(ROOT)) for f in files], "gb": round(sum(f.stat().st_size for f in files) / 1e9, 2)}) def do_POST(self): + if self.server.server_port == AUTH_LISTEN[1] and self.path.startswith("/deploy"): + # Gitea (container) -> host gateway -> here. Verify HMAC-SHA256 of the raw body with the webhook secret. + n = int(self.headers.get("Content-Length") or 0); raw = self.rfile.read(n) + env = deploy_env(); secret = env.get("DEPLOY_WEBHOOK_SECRET", "") + sig = self.headers.get("X-Gitea-Signature") or self.headers.get("X-Hub-Signature-256", "").replace("sha256=", "") + if not secret or not sig or not hmac.compare_digest(hmac.new(secret.encode(), raw, hashlib.sha256).hexdigest(), sig): + print(f"{datetime.now(TZ):%F %T} deploy webhook: bad signature from {self.client_address[0]}", flush=True) + return self._json(401, {"error": "bad signature"}) + try: payload = json.loads(raw or b"{}") + except json.JSONDecodeError: return self._json(400, {"error": "bad json"}) + ref = payload.get("ref", ""); branch = env.get("DEPLOY_BRANCH", "main") + after = (payload.get("after") or "")[:10]; pusher = (payload.get("pusher") or {}).get("username", "?") + if ref != f"refs/heads/{branch}": + return self._json(200, {"ignored": f"ref {ref} is not {branch}"}) + if env.get("DEPLOY_AUTO", "0") != "1": + print(f"{datetime.now(TZ):%F %T} deploy webhook: push {after} by {pusher} on {branch} — auto-deploy disabled (DEPLOY_AUTO=0)", flush=True) + return self._json(202, {"queued": False, "note": "auto-deploy disabled (DEPLOY_AUTO=0); run vanguard-deploy pull by hand"}) + started = run_deploy(f"push {after} by {pusher}") + return self._json(202, {"queued": started, "note": "deploy started" if started else "a deploy is already running"}) if self.server.server_port == AUTH_LISTEN[1] and not self.path.startswith("/mtx-auth"): return self._json(404, {"error": "not found"}) if self.path.startswith("/mtx-auth"): diff --git a/check.sh b/check.sh index 5379877..6308956 100755 --- a/check.sh +++ b/check.sh @@ -5,7 +5,7 @@ cd "$(dirname "$0")" fail=0 say() { printf '%-28s %s\n' "$1" "$2"; } -for f in install.sh addCommunityMediaServer.sh installJellyfinPlugin.sh registerGiteaRunner.sh createGiteaDb.sh deployLive.sh addClipUser.sh firewall/firewall.sh bin/*.sh home/*.sh bot/entrypoint.sh mariadb/init/*.sh; do +for f in install.sh addCommunityMediaServer.sh installJellyfinPlugin.sh registerGiteaRunner.sh createGiteaDb.sh deployLive.sh addClipUser.sh deploy.sh switchToClone.sh firewall/firewall.sh bin/*.sh home/*.sh bot/entrypoint.sh mariadb/init/*.sh; do [[ -f $f ]] || continue bash -n "$f" && say "$f" ok || { say "$f" "BASH SYNTAX"; fail=1; } done diff --git a/configure.py b/configure.py index a6e59a0..67aaa4e 100755 --- a/configure.py +++ b/configure.py @@ -4,6 +4,8 @@ ./configure.py --full first-time setup: asks every question (current values as defaults) ./configure.py manage: skips what is already set; add/remove/reset streamers & editors ./configure.py --show print current streamers, editors, domain and the OBS settings + ./configure.py --apply regenerate everything from the values already on disk and apply (non-interactive; + used by deploy.sh). Fails if the main hostname was never set. ./configure.py --hosts AUTH WATCH [GIT [BOT [LIVE]]] set the Authentik/Jellyfin/Gitea/bot/live hostnames and apply (non-interactive; used by addCommunityMediaServer.sh) @@ -316,6 +318,10 @@ def write_env(watch_host, discord_token=None, discord_guild=None, git_host="", b env.setdefault("APEX_REDIRECT", "") # where the bare domain sends people, e.g. https://watch.HOST/ env.setdefault("SYNC_REQUIRE_GROUP", "media-admins") # Caddy gate for /sync on the main host env.setdefault("STREAM_TOKEN_SECRET", secrets.token_urlsafe(48)) # signs publish tokens (dumpd) + env.setdefault("DEPLOY_AUTO", "0") # 1 = Gitea push webhook runs deploy.sh (off until trusted) + env.setdefault("DEPLOY_WEBHOOK_SECRET", secrets.token_urlsafe(32)) # Gitea webhook secret (HMAC) + env.setdefault("DEPLOY_BRANCH", "main") + env.setdefault("DEPLOY_NOTIFY_WEBHOOK_URL", "") # optional Discord webhook for deploy notices env.setdefault("COPYPARTY_IDP_KEY", "".join(c for c in secrets.token_urlsafe(40) if c.isalnum())[:32]) # Caddy->copyparty idp-h-key env.setdefault("BOT_REQUIRE_GROUP", "bot-users") # Caddy 403s anyone not in this Authentik group; empty = any logged-in member env.setdefault("LIVE_REQUIRE_GROUP", "") @@ -489,6 +495,12 @@ def apply(domain, admin_pw, streamers, editors, auth_host="", watch_host="", dis run(["sudo", "install", "-d", "-m", "0750", "/etc/vanguard"]) run(["sudo", "install", "-m", "0600", "stream-token.secret", "/etc/vanguard/stream-token.secret"]) run(["sudo", "install", "-m", "0644", "syncthing/stignore", "/etc/vanguard/stignore"]) # dumpd copies it into new clips folders + e = read_env() + Path("deploy.env").write_text("".join(f"{k}={e.get(k, '')}\n" for k in ("DEPLOY_AUTO", "DEPLOY_WEBHOOK_SECRET", "DEPLOY_BRANCH", "DEPLOY_NOTIFY_WEBHOOK_URL")) + f"DEPLOY_REPO_DIR={Path.cwd()}\n"); os.chmod("deploy.env", 0o600) + run(["sudo", "install", "-m", "0600", "deploy.env", "/etc/vanguard/deploy.env"]) # read by dumpd (/deploy webhook) and deploy.sh + run(["sudo", "install", "-m", "0755", "deploy.sh", "/usr/local/bin/vanguard-deploy"]) + run(["sudo", "install", "-m", "0644", "systemd/deploy-pending.service", "systemd/deploy-pending.timer", "/etc/systemd/system/"]) + run(["sudo", "systemctl", "daemon-reload"]); run(["sudo", "systemctl", "enable", "--now", "deploy-pending.timer"]) run(["sudo", "systemctl", "restart", "dumpd"]) print("== containers") dc = ["docker", "compose"] if (DRY or subprocess.call(["docker", "info"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0) else ["sudo", "docker", "compose"] @@ -613,6 +625,10 @@ def main(): editors = accounts if mode == "--show": show(domain, admin_pw, streamers, editors, auth_host, watch_host, git_host, bot_host, live_host); return + if mode == "--apply": + if not domain: print("domain not set yet — run ./configure.py --full first"); sys.exit(1) + apply(domain, admin_pw, streamers, editors, auth_host, watch_host, git_host=git_host, bot_host=bot_host, live_host=live_host) + show(domain, admin_pw, streamers, editors, auth_host, watch_host, git_host, bot_host, live_host); return if mode == "--hosts": if len(sys.argv) not in (4, 5, 6, 7): print("usage: configure.py --hosts AUTH_HOST WATCH_HOST [GIT_HOST [BOT_HOST [LIVE_HOST]]]"); sys.exit(2) if not domain: print("domain not set yet — run ./configure.py --full first"); sys.exit(1) diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..d742dd4 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# Minimal-impact deployer for the server checkout. Run as root (sudo) — installed as /usr/local/bin/vanguard-deploy. +# vanguard-deploy pull fetch + fast-forward DEPLOY_BRANCH, then apply only what changed +# vanguard-deploy apply [OLDREV] apply changes between OLDREV (default: last deployed rev) and HEAD +# vanguard-deploy rollback check out the previously deployed rev and apply the reverse diff +# vanguard-deploy pending restart MediaMTX if a restart is pending and no stream is live (timer runs this) +# vanguard-deploy status +# Env (from /etc/vanguard/deploy.env): DEPLOY_REPO_DIR, DEPLOY_BRANCH, DEPLOY_NOTIFY_WEBHOOK_URL. VG_DRY=1 prints only. +set -uo pipefail +ENVF=/etc/vanguard/deploy.env; [[ -f $ENVF ]] && set -a && . "$ENVF" && set +a +REPO="${DEPLOY_REPO_DIR:-/home/debian/vanguard-stream}"; BRANCH="${DEPLOY_BRANCH:-main}" +STATE=/var/lib/vanguard; mkdir -p "$STATE" 2>/dev/null || true +LASTF="$STATE/deployed-rev"; PREVF="$STATE/previous-rev"; PENDING="$STATE/mediamtx-restart-pending" +DRY="${VG_DRY:-0}"; OWNER=$(stat -c %U "$REPO" 2>/dev/null || echo debian) +log() { echo "$(date '+%F %T') deploy: $*"; } +run() { log "\$ $*"; [[ $DRY == 1 ]] || "$@"; } +asowner() { log "\$ (as $OWNER) $*"; [[ $DRY == 1 ]] || sudo -u "$OWNER" -H "$@"; } +notify() { + [[ -n ${DEPLOY_NOTIFY_WEBHOOK_URL:-} && $DRY != 1 ]] || return 0 + curl -s -m 10 -H 'Content-Type: application/json' -d "$(python3 -c 'import json,sys; print(json.dumps({"content": sys.argv[1][:1900]}))' "$1")" "$DEPLOY_NOTIFY_WEBHOOK_URL" >/dev/null || true +} +cd "$REPO" || { log "repo dir $REPO missing"; exit 1; } +git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { log "$REPO is not a git checkout (run switchToClone.sh once)"; exit 1; } + +live_streams() { curl -s -m 3 'http://127.0.0.1:9997/v3/paths/list' 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print(sum(1 for p in d.get("items",[]) if p.get("ready")))' 2>/dev/null || echo 0; } + +restart_mediamtx_or_defer() { + local n; n=$(live_streams) + if [[ $n == 0 ]]; then + run docker compose restart mediamtx; rm -f "$PENDING"; log "mediamtx restarted" + else + [[ $DRY == 1 ]] || touch "$PENDING"; log "mediamtx restart DEFERRED: $n live stream(s); deploy-pending.timer retries every 5 min" + notify "⏳ deploy: MediaMTX restart deferred ($n live stream(s)); will restart when idle" + fi +} + +apply_changes() { + local old="$1" new="$2" + local changed; changed=$(git diff --name-only "$old" "$new" 2>/dev/null || git ls-files) + [[ -n $changed ]] || { log "nothing changed between $old and $new"; return 0; } + log "changed files:"; echo "$changed" | sed 's/^/ /' + has() { echo "$changed" | grep -qE "$1"; } + + # 0. static checks on the new tree + if [[ -x check.sh ]]; then asowner bash check.sh >/tmp/vg-check.log 2>&1 || { log "check.sh FAILED — aborting"; tail -20 /tmp/vg-check.log; notify "❌ deploy $new: check.sh failed"; return 1; }; fi + + # 1. host scripts + units + if has '^bin/'; then run install -m 0755 bin/normalize-sweep.sh bin/trash-purge.sh bin/dumpd.py bin/vgstrip.py /usr/local/bin/; fi + if has '^systemd/'; then run install -m 0644 systemd/*.service systemd/*.timer /etc/systemd/system/; run systemctl daemon-reload; fi + if has '^deploy\.sh$'; then run install -m 0755 deploy.sh /usr/local/bin/vanguard-deploy; fi + if has '^firewall/nftables\.conf$'; then run install -m 0644 firewall/nftables.conf /etc/nftables.conf; run nft -f /etc/nftables.conf; fi + + # 2. regenerate configs (Caddy reload, copyparty restart) — configure.py decides; MediaMTX is handled below + if has '^configure\.py$|^syncthing/stignore$|^dock/|^clips/|^about/'; then + local before_mtx; before_mtx=$(md5sum mediamtx.yml 2>/dev/null | cut -d' ' -f1) + if [[ $DRY == 1 ]]; then log "\$ (as $OWNER) VG_DRY=1 python3 configure.py --apply"; else sudo -u "$OWNER" -H python3 configure.py --apply >/tmp/vg-configure.log 2>&1 || { log "configure --apply FAILED"; tail -20 /tmp/vg-configure.log; return 1; }; fi + local after_mtx; after_mtx=$(md5sum mediamtx.yml 2>/dev/null | cut -d' ' -f1) + [[ $before_mtx == "$after_mtx" ]] || { log "mediamtx.yml changed"; restart_mediamtx_or_defer; } + fi + if has '^bin/dumpd\.py$|^systemd/dumpd\.service$'; then run systemctl restart dumpd; fi + + # 3. compose: recreate changed services, but never MediaMTX while live + if has '^docker-compose\.yml$|^bot/Dockerfile$|^bot/app/requirements\.txt$'; then + local mtx_changed=0 + git diff "$old" "$new" -- docker-compose.yml | grep -qE '^[+-]' && git diff -U0 "$old" "$new" -- docker-compose.yml | grep -qiE '^[+-].*(mediamtx|8189|8890|1935)' && mtx_changed=1 + if [[ $mtx_changed == 1 && $(live_streams) != 0 ]]; then + log "compose touches mediamtx and streams are live: recreating everything except mediamtx" + run docker compose up -d --build --no-deps $(docker compose config --services | grep -v '^mediamtx$' | tr '\n' ' ') + [[ $DRY == 1 ]] || touch "$PENDING"; notify "⏳ deploy: MediaMTX recreate deferred (live streams)" + else + run docker compose up -d --build + fi + fi + + # 4. Jellyfin plugin (committed zip) + if has '^jellyfin-plugin-vanguard/dist/.*\.zip$'; then run bash installJellyfinPlugin.sh; fi + + # 5. live portal + if has '^live/'; then + if command -v npm >/dev/null; then + asowner bash -c 'cd live && npm install --no-audit --no-fund --loglevel=error && npm run build' && { run install -d -o "$OWNER" /srv/live; run rsync -a --delete live/dist/ /srv/live/; } + else log "npm not installed on the server: live/ changed but not built (apt-get install nodejs npm)"; fi + fi + log "applied $old..$new" +} + +case "${1:-}" in + pull) + old=$(git rev-parse HEAD) + asowner git fetch --quiet origin "$BRANCH" || { log "fetch failed"; exit 1; } + asowner git merge --ff-only "origin/$BRANCH" || { log "fast-forward failed (local changes?)"; exit 1; } + new=$(git rev-parse HEAD) + [[ $old == "$new" ]] && { log "already at $new"; exit 0; } + if apply_changes "$old" "$new"; then + [[ $DRY == 1 ]] || { cp -f "$LASTF" "$PREVF" 2>/dev/null; echo "$new" > "$LASTF"; } + notify "✅ deploy $(git log -1 --format='%h %s' "$new")" + else notify "❌ deploy $new failed — see journalctl -u dumpd"; exit 1; fi ;; + apply) + old="${2:-$(cat "$LASTF" 2>/dev/null || git rev-parse HEAD~1)}"; new=$(git rev-parse HEAD) + apply_changes "$old" "$new" && { [[ $DRY == 1 ]] || { cp -f "$LASTF" "$PREVF" 2>/dev/null; echo "$new" > "$LASTF"; }; } ;; + rollback) + prev=$(cat "$PREVF" 2>/dev/null) || { log "no previous rev recorded"; exit 1; } + cur=$(git rev-parse HEAD); log "rolling back $cur -> $prev" + asowner git checkout --quiet "$prev" && apply_changes "$cur" "$prev" && { [[ $DRY == 1 ]] || { echo "$cur" > "$PREVF"; echo "$prev" > "$LASTF"; }; notify "↩️ rolled back to $prev"; } ;; + pending) + [[ -f $PENDING ]] || exit 0 + restart_mediamtx_or_defer ;; + status) + echo "repo: $REPO ($(git rev-parse --abbrev-ref HEAD) @ $(git rev-parse --short HEAD))" + echo "deployed: $(cat "$LASTF" 2>/dev/null || echo '-') previous: $(cat "$PREVF" 2>/dev/null || echo '-')" + echo "auto: DEPLOY_AUTO=${DEPLOY_AUTO:-0} branch: $BRANCH pending mediamtx restart: $([[ -f $PENDING ]] && echo yes || echo no) live streams: $(live_streams)" ;; + *) sed -n '2,9p' "$0"; exit 2 ;; +esac diff --git a/install.sh b/install.sh index 8c317bb..20f2722 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ OWNER="${SUDO_USER:-debian}" # the login user; owns /srv/media and may run echo "== packages" apt-get update -q -apt-get install -y -q ca-certificates curl gnupg ffmpeg jq psmisc rclone python3 xfsprogs +apt-get install -y -q ca-certificates curl gnupg ffmpeg jq psmisc rclone python3 python3-yaml xfsprogs git nodejs npm rsync if ! command -v docker >/dev/null; then echo "== docker" diff --git a/jellyfin-plugin-vanguard/.gitignore b/jellyfin-plugin-vanguard/.gitignore index bafb0f8..7e45de4 100644 --- a/jellyfin-plugin-vanguard/.gitignore +++ b/jellyfin-plugin-vanguard/.gitignore @@ -1,4 +1,5 @@ bin/ obj/ *.user -dist/ +dist/* +!dist/VanguardMetadata_*.zip diff --git a/jellyfin-plugin-vanguard/dist/VanguardMetadata_0.1.3.0.zip b/jellyfin-plugin-vanguard/dist/VanguardMetadata_0.1.3.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..4bf41c03d806466210bf21863bc9cc8bc43c7502 GIT binary patch literal 43730 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 F{udx33c&yX literal 0 HcmV?d00001 diff --git a/switchToClone.sh b/switchToClone.sh new file mode 100755 index 0000000..ed43ec1 --- /dev/null +++ b/switchToClone.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# One-time: turn ~/vanguard-stream (an scp copy) into a git clone of the Gitea repo, keeping the generated files. +# ./switchToClone.sh key -> creates a deploy key and prints the public half (add it in Gitea: repo → Settings → Deploy keys, read-only) +# ./switchToClone.sh clone -> clones over ssh (port 2222), moves .env + generated files + plugin zips into it +set -euo pipefail +REPO_SSH="${REPO_SSH:-ssh://git@git.dumpstervigilantes.rip:2222/techy/Media-Server-Setup.git}" +DIR="$HOME/vanguard-stream"; KEY="$HOME/.ssh/vanguard-deploy" +case "${1:-}" in + key) + [[ -f $KEY ]] || ssh-keygen -t ed25519 -N "" -C "vanguard-deploy@$(hostname)" -f "$KEY" >/dev/null + cat > "$HOME/.ssh/config.vanguard" <<CFG +Host git.dumpstervigilantes.rip + Port 2222 + User git + IdentityFile $KEY + IdentitiesOnly yes +CFG + grep -q 'config.vanguard' "$HOME/.ssh/config" 2>/dev/null || { echo "Include config.vanguard" | cat - "$HOME/.ssh/config" 2>/dev/null > "$HOME/.ssh/config.new" || echo "Include config.vanguard" > "$HOME/.ssh/config.new"; mv "$HOME/.ssh/config.new" "$HOME/.ssh/config"; chmod 600 "$HOME/.ssh/config"; } + echo "Add this as a READ-ONLY deploy key on the repo (Gitea → repo → Settings → Deploy Keys):"; echo; cat "$KEY.pub" ;; + clone) + [[ -d $DIR/.git ]] && { echo "$DIR is already a clone"; exit 0; } + ssh -o StrictHostKeyChecking=accept-new -p 2222 git@git.dumpstervigilantes.rip 2>&1 | grep -qi "successfully authenticated\|Hi there" || { echo "deploy key not accepted yet — run './switchToClone.sh key' and add it in Gitea"; exit 1; } + mv "$DIR" "$DIR.old" + git clone --quiet "$REPO_SSH" "$DIR" + for f in .env Caddyfile mediamtx.yml copyparty.conf stream-token.secret deploy.env; do [[ -f $DIR.old/$f ]] && cp -a "$DIR.old/$f" "$DIR/$f"; done + mkdir -p "$DIR/jellyfin-plugin-vanguard/dist"; cp -a "$DIR.old"/jellyfin-plugin-vanguard/dist/*.zip "$DIR/jellyfin-plugin-vanguard/dist/" 2>/dev/null || true + [[ -d $DIR.old/bot/app ]] && cp -a "$DIR.old/bot/app/." "$DIR/bot/app/" 2>/dev/null || true + ( cd "$DIR" && sudo -n true 2>/dev/null && sudo install -m 0600 -o root -g root .env /dev/null >/dev/null 2>&1 || true ) + echo "cloned. kept: .env, generated configs, plugin zips, bot/app. Old copy at $DIR.old (delete when happy)." + echo "next: cd $DIR && ./configure.py --apply (records nothing new; just confirms generation works from the clone)" + echo " then: sudo vanguard-deploy status" ;; + *) sed -n '2,5p' "$0"; exit 2 ;; +esac diff --git a/systemd/deploy-pending.service b/systemd/deploy-pending.service new file mode 100644 index 0000000..8b80ab3 --- /dev/null +++ b/systemd/deploy-pending.service @@ -0,0 +1,7 @@ +[Unit] +Description=Restart MediaMTX if a deploy deferred it and no stream is live +After=docker.service + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/vanguard-deploy pending diff --git a/systemd/deploy-pending.timer b/systemd/deploy-pending.timer new file mode 100644 index 0000000..21c18d1 --- /dev/null +++ b/systemd/deploy-pending.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Retry deferred MediaMTX restart every 5 minutes + +[Timer] +OnBootSec=3min +OnUnitActiveSec=5min + +[Install] +WantedBy=timers.target