From 296ee62d87afeb81383a86e5fb8a2bc6b8c6578b Mon Sep 17 00:00:00 2001 From: Ismael Leon Date: Wed, 14 Oct 2026 19:15:53 -0600 Subject: [PATCH] Deploy main to ilserver4 with ilserver4-deploy deploy/manifest lists one auto installer whose inputs are what the running bot depends on. The installer runs update.sh from the fetched commit as the checkout's owner, and update.sh fast-forwards to exactly that commit. --- deploy/install-ilserver4.sh | 22 ++++++++++++++++++++++ deploy/manifest | 11 +++++++++++ deploy/update.sh | 16 +++++++++++++--- tests/test_deploy_manifest.py | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100755 deploy/install-ilserver4.sh create mode 100644 deploy/manifest create mode 100644 tests/test_deploy_manifest.py diff --git a/deploy/install-ilserver4.sh b/deploy/install-ilserver4.sh new file mode 100755 index 0000000..6085f75 --- /dev/null +++ b/deploy/install-ilserver4.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# +# Deploy one commit of the bot to ilserver4. Not run by hand: ilserver4-deploy +# (Isma-L154/Project_ilserver4) runs it as root, from its own export of that +# commit, whenever main changes one of its inputs in deploy/manifest. +# +# update.sh does the work, as the checkout's owner, so git and pip never leave +# root-owned files behind in it. It runs from this export rather than from the +# checkout, so a change to update.sh applies in the same deployment, and from a +# root-owned copy because the export is only readable by root. +# +set -euo pipefail + +APP_DIR=/home/ashesofil/LoopifyBot +COMMIT="${DEPLOY_COMMIT:?set by ilserver4-deploy: the commit being deployed}" +APP_USER="$(stat -c '%U' "$APP_DIR")" + +script="$(mktemp)" +trap 'rm -f "$script"' EXIT +install -m 0644 deploy/update.sh "$script" + +runuser -u "$APP_USER" -- env APP_DIR="$APP_DIR" bash "$script" "$COMMIT" diff --git a/deploy/manifest b/deploy/manifest new file mode 100644 index 0000000..8352c15 --- /dev/null +++ b/deploy/manifest @@ -0,0 +1,11 @@ +# Read by ilserver4-deploy (Isma-L154/Project_ilserver4), which deploys main of +# this repository to ilserver4. +# +# kind path inputs: files, or directories ending in / +# +# auto runs when an input or the installer itself changes. ignore maps files no +# installer reads. Tests, docs and site/ are not inputs: changing them must not +# restart the bot. + +auto deploy/install-ilserver4.sh cogs/ services/ utils/ main.py config.py requirements.txt deploy/install-units.sh deploy/update.sh deploy/update-ytdlp.sh +ignore deploy/manifest deploy/README.md deploy/setup.sh deploy/launch_ec2.sh diff --git a/deploy/update.sh b/deploy/update.sh index fe997e1..e8eb00d 100755 --- a/deploy/update.sh +++ b/deploy/update.sh @@ -3,11 +3,16 @@ # Update a deployed bot to the latest committed code and restart it. # # Usage, on the host: -# cd ~/LoopifyBot && bash deploy/update.sh +# cd ~/LoopifyBot && bash deploy/update.sh # pull main +# cd ~/LoopifyBot && bash deploy/update.sh COMMIT # fast-forward to COMMIT +# +# On ilserver4 it is not run by hand: deploy/install-ilserver4.sh feeds it to +# bash on stdin with APP_DIR set, which is why APP_DIR can come from outside. # set -euo pipefail -APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TARGET="${1:-}" +APP_DIR="${APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" VENV_DIR="$APP_DIR/.venv" SERVICE_NAME="loopify-bot" @@ -50,7 +55,12 @@ fi previous="$(git rev-parse HEAD)" echo "==> Fetching..." -git pull --ff-only +if [[ -n "$TARGET" ]]; then + git fetch --quiet origin main + git merge --quiet --ff-only "$TARGET" +else + git pull --ff-only +fi if [[ "$previous" == "$(git rev-parse HEAD)" ]]; then echo "==> Already up to date." diff --git a/tests/test_deploy_manifest.py b/tests/test_deploy_manifest.py new file mode 100644 index 0000000..0af4b13 --- /dev/null +++ b/tests/test_deploy_manifest.py @@ -0,0 +1,35 @@ +""" +deploy/manifest must cover every file under deploy/. + +ilserver4-deploy refuses to deploy a commit with an unmapped file there, so a +new script that is not listed would stop every deployment of the bot rather +than be skipped. This catches it in the pull request instead. +""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +DEPLOY = ROOT / "deploy" + + +def entries() -> list[tuple[str, str, list[str]]]: + lines = (DEPLOY / "manifest").read_text(encoding="utf-8").splitlines() + rows = [line.split("#", 1)[0].split() for line in lines] + return [(row[0], row[1], row[2:]) for row in rows if row] + + +def test_every_file_under_deploy_is_mapped(): + mapped = [path for _, first, rest in entries() for path in [first, *rest]] + for file in DEPLOY.rglob("*"): + if not file.is_file(): + continue + name = file.relative_to(ROOT).as_posix() + assert any(name == p or (p.endswith("/") and name.startswith(p)) for p in mapped), \ + f"{name} is not in deploy/manifest" + + +def test_every_input_exists(): + """A misspelt input would never trigger a deployment.""" + for _kind, installer, inputs in entries(): + for path in [installer, *inputs]: + assert (ROOT / path).exists(), f"{path} in deploy/manifest does not exist"