#!/usr/bin/env bash # plex-claim-ship.sh — run this IMMEDIATELY AFTER editing the SOPS secret with a fresh # plex.tv claim token. It drives the whole CI/CD loop end to end and prints a running # stopwatch so you can see the budget being spent. # # branch -> commit -> push -> PR -> gate -> merge -> reconcile -> pod roll -> verify # # It never sees, prints, or handles the token: the file is already encrypted by sops. set -euo pipefail HOST=gitea.home.keithjackson.dev OWNER=Home-Network REPO=home-network-k3s-impl SECRET=apps/wave-0/plex-claim-secret.sops.yaml T0=$(date +%s) el() { printf '\n[T+%02d:%02d] %s\n' $(( ($(date +%s)-T0)/60 )) $(( ($(date +%s)-T0)%60 )) "$1"; } cd "$(git rev-parse --show-toplevel)" # --- preflight: refuse to ship a file that is not what we think it is ------------- git diff --quiet -- "$SECRET" && { echo "ERROR: $SECRET is unchanged. Edit it first:"; \ echo " infra/devcontainer/dev.sh run sops $SECRET"; exit 1; } grep -q 'PLEX_CLAIM: ENC\[' "$SECRET" || { echo "ERROR: $SECRET is not encrypted. REFUSING."; exit 1; } [ -z "$(git status --porcelain | grep -v " $SECRET\$")" ] || echo "NOTE: other files are dirty; only $SECRET will be committed." creds="$(printf 'protocol=https\nhost=%s\n\n' "$HOST" | git credential fill)" U="$(sed -n 's/^username=//p' <<<"$creds")"; P="$(sed -n 's/^password=//p' <<<"$creds")" api() { curl -sS --config <(printf 'user = "%s:%s"\n' "$U" "$P") "$@"; } # feat/ prefix ON PURPOSE. act_runner capacity is 1, and gate.yml's PUSH trigger covers # feature/** fix/** docs/** chore/** but NOT feat/** — so a chore/ branch would fire a # second, redundant run that SERIALISES ahead of the PR run and doubles the wall clock. BRANCH="feat/plex-claim-token-$(date +%H%M%S)" el "committing on $BRANCH" git checkout -q -b "$BRANCH" git add "$SECRET" git commit -q -m 'chore(plex): fresh plex.tv claim token Injected inside the plex.tv claim window per runbooks/plex-claim.md.' SHA=$(git rev-parse HEAD) el "pushing" git push -q -u origin "$BRANCH" el "opening PR" PR=$(jq -n --arg h "$BRANCH" --arg b main --arg t 'chore(plex): fresh plex.tv claim token' \ '{head:$h,base:$b,title:$t,body:"Token injection inside the claim window. Wiring already reviewed in #1."}' \ | api -X POST -H 'Content-Type: application/json' --data @- \ "https://${HOST}/api/v1/repos/${OWNER}/${REPO}/pulls" | jq -r '.number') [ -n "$PR" ] && [ "$PR" != "null" ] || { echo "PR creation failed"; exit 1; } echo " PR #$PR" el "waiting for the gate (expect ~1m40s)" for i in $(seq 1 60); do st=$(api "https://${HOST}/api/v1/repos/${OWNER}/${REPO}/commits/${SHA}/status" | jq -r '.state') printf '\r gate=%s [T+%ss] ' "$st" "$(( $(date +%s)-T0 ))" case "$st" in success) echo; break;; failure|error) echo; echo "GATE FAILED — stopping."; exit 1;; esac sleep 5 done [ "$st" = "success" ] || { echo; echo "gate did not finish in time"; exit 1; } el "merging PR #$PR" api -X POST -H 'Content-Type: application/json' -d '{"Do":"merge"}' \ "https://${HOST}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR}/merge" >/dev/null git checkout -q main && git pull -q --ff-only origin main el "forcing the Flux reconcile (--with-source, or it applies the OLD revision)" ./infra/devcontainer/dev.sh run bash -c \ 'flux --context k3s-prod -n flux-system reconcile kustomization apps-wave-0 --with-source' # MANDATORY here. Only the Secret ciphertext changed, so the Deployment pod template is # byte-identical and NOTHING restarts on its own. PLEX_CLAIM is read once, at container # start. Skipping this is failure mode #2 in runbooks/plex-claim.md. el "rolling the pod (mandatory — a changed Secret restarts nothing)" ./infra/devcontainer/dev.sh run bash -c \ 'kubectl --context k3s-prod -n plex rollout restart deployment/plex && \ kubectl --context k3s-prod -n plex rollout status deployment/plex --timeout=120s' el "verdict" ./infra/devcontainer/dev.sh run bash -c \ 'kubectl --context k3s-prod -n plex logs deploy/plex --since=3m | grep -i -e claim -e token || true' cat <<'NOTE' "Attempting to obtain server token..." + "Token obtained successfully" -> CLAIMED "Attempting..." with no success line -> token expired; retry, nothing is broken neither line -> already claimed, or env did not reach the container Confirm it persisted (prints the attribute NAME only, never its value): infra/devcontainer/dev.sh run bash -c \ 'kubectl --context k3s-prod -n plex exec deploy/plex -- sh -c \ "grep -o PlexOnlineToken= \"/config/Library/Application Support/Plex Media Server/Preferences.xml\" | head -1"' NOTE