Hand cron over from youtube-automate, and script the rest
Cron now calls `ytstream run` hourly and ytstream's update-ytdlp.sh weekly. Both inherit youtube-automate's healthchecks UUIDs and keep its schedules unchanged: a check may be configured with a cron expression rather than a simple period, so moving to the :23/04:50 slots the fragment proposed could have alerted on a job that ran fine. Inheriting also means the placeholder UUIDs never needed filling in. The old service turned out to track only 2 channels, and one of them -- The Pyramid Podcast -- was sitting unresolved in ytstream's approval queue. Decommissioning without checking would have silently dropped half of what the old service existed to follow. Approved and backfilled. decommission.sh does the two steps needing root (nginx repoint, disable the unit) and refuses until an admin password is set, because the UI fails closed and the hostname would otherwise serve a login nobody can pass. Deleting the 2.0 GB of old downloads and touching the Jellyfin libraries are left out on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+19
-7
@@ -1,15 +1,27 @@
|
||||
# ytstream — add these to susan's crontab (`crontab -e`).
|
||||
# ytstream — these are INSTALLED in susan's crontab as of 2026-08-13.
|
||||
# Kept here as the record of what should be there (`crontab -l` to confirm).
|
||||
#
|
||||
# HC_API_URL is already set at the top of susan's crontab; these follow the
|
||||
# existing one-UUID-per-job convention. `sg mediaserver` guarantees new files are
|
||||
# group-owned by mediaserver even if the invoking shell's primary group differs.
|
||||
#
|
||||
# Get fresh UUIDs from hc.jihakuz.xyz before enabling these.
|
||||
# The two UUIDs are INHERITED from youtube-automate's entries rather than freshly
|
||||
# created, and the schedules are unchanged for the same reason: a healthchecks
|
||||
# check may be configured with a cron expression rather than a simple period, so
|
||||
# moving :17 to :23 could have alerted on a job that ran perfectly. Inheriting
|
||||
# also keeps the ping history continuous across the handover. The checks are still
|
||||
# *named* after youtube-automate in the hc.jihakuz.xyz UI — rename them there, it
|
||||
# has no effect on anything here.
|
||||
|
||||
# Sync subscriptions, poll, materialise, reap. Hourly. Runs at :23 to stay clear
|
||||
# of youtube-automate's :17 entry during the overlap period.
|
||||
23 * * * * runitor -uuid REPLACE-WITH-UUID -- sg mediaserver "/usr/local/bin/ytstream run"
|
||||
# Sync subscriptions, poll, materialise, reap. Hourly at :17.
|
||||
# Measured 2026-08-13: a full run over 11 channels takes ~8s.
|
||||
17 * * * * runitor -uuid 41a4d61a-7743-43d9-9b5d-d37d536e4726 -- sg mediaserver "/usr/local/bin/ytstream run"
|
||||
|
||||
# Keep yt-dlp current — this is the thing that keeps playback working as YouTube
|
||||
# changes. Mondays 04:50, after youtube-automate's 04:40 slot.
|
||||
50 4 * * 1 runitor -uuid REPLACE-WITH-UUID -- /opt/ytstream/deploy/update-ytdlp.sh
|
||||
# changes. Mondays 04:40.
|
||||
#
|
||||
# The script tries to restart ytstream-proxy and cannot, because cron runs it as
|
||||
# susan. That is harmless: the proxy shells out to `yt-dlp` per request, so a pip
|
||||
# upgrade takes effect on the next fetch with no restart. Leaving the attempt in
|
||||
# means the message shows up in the healthchecks output if it ever does matter.
|
||||
40 4 * * 1 runitor -uuid 721e4cf0-d796-48e7-a184-79d21e1ba373 -- /opt/ytstream/deploy/update-ytdlp.sh
|
||||
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
# Retire youtube-automate and hand tube.jihakuz.xyz to ytstream.
|
||||
#
|
||||
# sudo /opt/ytstream/deploy/decommission.sh
|
||||
#
|
||||
# This is plan.md §12 steps 4 and 5 — the two that need root. Everything it does
|
||||
# is reversible: the nginx change is backed up next to the original, and the
|
||||
# service is disabled rather than removed.
|
||||
#
|
||||
# Deliberately NOT done here, because each one destroys something:
|
||||
# * deleting /disks/Plex/YouTube (§12 step 6)
|
||||
# * removing or renaming the Jellyfin libraries (§12 step 3)
|
||||
# * removing /opt/youtube-automate, its repo, subs.db or specs.md (§12 step 7)
|
||||
set -euo pipefail
|
||||
|
||||
CONF=/etc/nginx/sites-available/jihakuz.xyz
|
||||
OLD_PORT=8085 # youtube-automate
|
||||
NEW_PORT=8086 # ytstream admin
|
||||
DB=/var/lib/ytstream/ytstream.db
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "This script needs root. Run: sudo $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- preflight
|
||||
# Repointing a public hostname at a service that cannot be logged into wastes an
|
||||
# evening working out why. The admin UI fails closed with no password set: every
|
||||
# login attempt is rejected, including your brother's.
|
||||
if ! sqlite3 "$DB" \
|
||||
"SELECT 1 FROM setting WHERE key='admin_password_hash' AND value <> '';" \
|
||||
2>/dev/null | grep -q 1; then
|
||||
echo "!! No admin password is set, so nobody can log in to the UI this would" >&2
|
||||
echo " expose. Run this first, then re-run me:" >&2
|
||||
echo " sudo -u susan /var/lib/ytstream/venv/bin/ytstream set-password" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# And do not hand the hostname to a port nothing is listening on.
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' -m 10 "http://127.0.0.1:${NEW_PORT}/" || true)
|
||||
if [[ "$code" != "200" && "$code" != "303" && "$code" != "302" ]]; then
|
||||
echo "!! ytstream-admin is not answering on ${NEW_PORT} (got '${code}')." >&2
|
||||
echo " systemctl status ytstream-admin" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> ytstream-admin answers on ${NEW_PORT} (HTTP ${code}) and has a password set"
|
||||
|
||||
# ------------------------------------------------------------------- nginx
|
||||
if grep -q "127.0.0.1:${NEW_PORT}" "$CONF"; then
|
||||
echo "==> nginx already points tube.jihakuz.xyz at ${NEW_PORT}; skipping"
|
||||
elif ! grep -q "127.0.0.1:${OLD_PORT}" "$CONF"; then
|
||||
echo "!! Found neither ${OLD_PORT} nor ${NEW_PORT} in $CONF. Not guessing." >&2
|
||||
exit 1
|
||||
else
|
||||
BACKUP="${CONF}.bak-$(date +%Y%m%d%H%M%S)"
|
||||
cp -a "$CONF" "$BACKUP"
|
||||
echo "==> Backed up $CONF to $BACKUP"
|
||||
|
||||
# One line. The proxy_set_header block the youtube-automate fix added is
|
||||
# already there and still correct — X-Forwarded-For in particular, because
|
||||
# the login throttle keys on it and without it one attacker locks out all.
|
||||
sed -i "s|proxy_pass http://127.0.0.1:${OLD_PORT};|proxy_pass http://127.0.0.1:${NEW_PORT};|" "$CONF"
|
||||
echo "==> Repointed tube.jihakuz.xyz at 127.0.0.1:${NEW_PORT}"
|
||||
|
||||
if ! nginx -t; then
|
||||
echo "!! nginx config test failed — restoring the backup" >&2
|
||||
cp -a "$BACKUP" "$CONF"
|
||||
nginx -t
|
||||
exit 1
|
||||
fi
|
||||
systemctl reload nginx
|
||||
echo "==> nginx reloaded"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- the service
|
||||
if systemctl is-enabled --quiet youtube-automate.service 2>/dev/null \
|
||||
|| systemctl is-active --quiet youtube-automate.service 2>/dev/null; then
|
||||
systemctl disable --now youtube-automate.service
|
||||
echo "==> Stopped and disabled youtube-automate.service"
|
||||
echo " Unit left in place at /etc/systemd/system/ — 'systemctl enable --now"
|
||||
echo " youtube-automate' brings it back if this turns out to be premature."
|
||||
else
|
||||
echo "==> youtube-automate.service already stopped and disabled"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Done. tube.jihakuz.xyz now serves ytstream's admin UI."
|
||||
echo "Still holding, on purpose:"
|
||||
echo " * /disks/Plex/YouTube ($(du -sh /disks/Plex/YouTube 2>/dev/null | cut -f1)) — the old downloads"
|
||||
echo " * both Jellyfin libraries — remove/rename by hand when you are ready"
|
||||
echo " * /opt/youtube-automate, its repo, subs.db and specs.md — keep these"
|
||||
@@ -1146,3 +1146,58 @@ budget only sums tracked sessions). The work root is a tmpfs, so that is leaked
|
||||
until the next reboot — the restart that shipped the fix above would have stranded
|
||||
1.56 GB. `reset_work_root()` now clears it at startup and logs what it reclaimed.
|
||||
Session directories only; a stray file in the work root is left alone.
|
||||
|
||||
## 20. Decommissioning, done and outstanding — 2026-08-13
|
||||
|
||||
Started the same day the TTFB bug (§19) was fixed, which is earlier than §12 step 2
|
||||
intended: that step says run both for a week, precisely so a bug like §19 surfaces
|
||||
while the old service is still there to fall back on. Everything below is therefore
|
||||
reversible, and the two irreversible steps are deliberately left undone.
|
||||
|
||||
### The old service was smaller than assumed
|
||||
|
||||
It tracked **2 channels** (Pitch Side, The Pyramid Podcast), 32 video rows, 9 files
|
||||
on disk, 2.0 GB — not the 5–10 GB §12 step 6 estimated. All 32 fall inside
|
||||
ytstream's 30-day window, so nothing in the old library is content ytstream cannot
|
||||
reach.
|
||||
|
||||
**Pitch Side was already mirrored; The Pyramid Podcast was not** — it sat unresolved
|
||||
in the approval queue, so decommissioning without checking would have silently
|
||||
dropped one of the two channels the old service existed to follow. Approved, and it
|
||||
backfilled 4 episodes. 11 channels now.
|
||||
|
||||
### Done
|
||||
|
||||
| step | what |
|
||||
|---|---|
|
||||
| §12.1 | Cron handed over: `youtube-automate run` → `ytstream run`, and `update-ytdlp.sh` repointed |
|
||||
| §12.7 | `subs.db` copied to `/var/lib/ytstream/youtube-automate-subs.db.archived-20260813` |
|
||||
| — | The Pyramid Podcast carried over |
|
||||
|
||||
**The two healthchecks UUIDs are inherited, not new,** and the schedules are
|
||||
unchanged (`:17` hourly, Mondays `04:40`). A check may be configured with a cron
|
||||
expression rather than a simple period, so moving to the `:23`/`04:50` slots the old
|
||||
fragment proposed could have alerted on a job that ran fine. This also means no new
|
||||
UUIDs were needed — the placeholder problem from §18 is gone. The checks are still
|
||||
*named* after youtube-automate in the hc UI; renaming them there changes nothing.
|
||||
|
||||
First real proof of the rolling window, from that first run: **5 videos uploaded
|
||||
2026-07-13 aged out** at 31 days, with 7 new ones discovered and materialised, in
|
||||
7.5 s.
|
||||
|
||||
### Left for a human
|
||||
|
||||
`deploy/decommission.sh` does §12 steps 4 and 5 (nginx repoint to 8086, disable the
|
||||
service) and **refuses to run until an admin password is set** — repointing a public
|
||||
hostname at a UI that fails closed, as this one does with no password, produces a
|
||||
site nobody can log into and an evening spent working out why.
|
||||
|
||||
Not scripted, because each destroys something:
|
||||
|
||||
* **`ytstream set-password`** — interactive, and blocks the above.
|
||||
* **Jellyfin** (§12.3): remove *YouTube*, rename *YouTube (stream)* → *YouTube*.
|
||||
Nothing in the code matches on the library *name* — `find_library()` matches on
|
||||
path and `LIBRARY_NAME` is only `create_library`'s default — so the rename is safe
|
||||
and the constant can stay as it is.
|
||||
* **`/disks/Plex/YouTube`** (§12.6), 2.0 GB.
|
||||
* **`/opt/youtube-automate`, its repo, `subs.db`, `specs.md`** — keep (§12.7).
|
||||
|
||||
Reference in New Issue
Block a user