Match the deploy system to hops and greatbear
menu-admin was the odd one out: it built from the working directory with a date tag and a multi-arch manifest, deployed with `-e tag=`, and kept the Docker-era container name. The other two apps build from `git archive HEAD` and tag with the commit, so what runs on the host is exactly what git knows about; build only for the architecture emily actually runs; and use one slug for the container, its network and its data directory. The build is now their three lines: `podman build --platform linux/arm64` from `git archive HEAD`, tagged `:<commit>` and `:latest`, pushed with `podman push` rather than a manifest. `deploy.sh` pushes the commit first, then refuses a dirty tracked tree or a HEAD that is not origin/main — which is what makes the commit tag trustworthy — takes the same `--force` and `--no-push`, and ends in the same `-u tanshu ... -e image_tag=<commit>`. Its own lint/typecheck/test gate stays, but behind `--checks`, so a plain deploy behaves as theirs does. The registry credential moves into vars/default.yml and the role logs in, pulls and logs out around it, as their roles do. The image is only pulled at deploy time, so the unit keeps podman's default `Pull=missing` and a reboot never needs the registry. This is the same plaintext credential the other two repos carry. One real bug goes with it: the healthcheck gate ran before the handler that restarts a changed unit, so a run that only changed the unit checked the stale container. `meta: flush_handlers` before the gate is the fix the sibling repos already made. The container, network and data directory are all menu-<site> now. The dotted Docker-era name and the NetworkAlias it needed are gone, so `podman logs menu-<site>` and `systemctl --user status menu-<site>` are the same two commands across all three apps. The Dockerfile keeps its name for the same reason: podman reads it as readily as a Containerfile, and the siblings use that name too.
This commit is contained in:
1 parent
e68f8ab00d
commit
537a4f1e46
21 files changed
+407
-307
No files matched your search
@@ -1,57 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Builds the image with podman, pushes it to the private registry, then deploys
|
||||
# the Site with the playbook that lives in ansible/. The playbook runs rootless
|
||||
# Podman Quadlet units on emily, behind the shared Caddy unit from
|
||||
# ansible-playbooks/caddy.
|
||||
# menu-admin deploy — push the current commit, build it with podman, and install
|
||||
# the Quadlet units on emily with ansible.
|
||||
#
|
||||
# ./deploy.sh 2026-09-22 deploy the default Site
|
||||
# SITE=greatbear ./deploy.sh 2026-09-22 deploy another Site
|
||||
# SKIP_DEPLOY=1 ./deploy.sh 2026-09-22 build and push only
|
||||
# SKIP_CHECKS=1 ./deploy.sh 2026-09-22 skip format, typecheck and tests
|
||||
# The image is built from the committed tree (`git archive HEAD`) and tagged
|
||||
# with its commit id, so what runs on the host is exactly what git knows about.
|
||||
#
|
||||
# ./deploy.sh deploy HEAD (tracked tree must be clean and pushed)
|
||||
# ./deploy.sh --force skip the guards (know what you are doing)
|
||||
# ./deploy.sh --no-push skip the push, if you already pushed
|
||||
# ./deploy.sh --checks lint, format, typecheck and test first
|
||||
#
|
||||
# SITE=<name> deploys one Site instead of every Site.
|
||||
#
|
||||
# Log in to the registry once beforehand: podman login registry.tanshu.com
|
||||
set -euo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
TAG="${1:-}"
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "usage: $0 <tag> (e.g. $(date +%Y-%m-%d) or a git short sha)" >&2
|
||||
exit 1
|
||||
BRANCH="${BRANCH:-main}"
|
||||
|
||||
FORCE=false
|
||||
NO_PUSH=false
|
||||
CHECKS=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force) FORCE=true ;;
|
||||
--no-push) NO_PUSH=true ;;
|
||||
--checks) CHECKS=true ;;
|
||||
*)
|
||||
echo "Usage: $0 [--force] [--no-push] [--checks]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# The build takes HEAD, so push first: otherwise the registry collects an image
|
||||
# for a commit that exists only on this machine.
|
||||
if [ "$NO_PUSH" != true ]; then
|
||||
echo "==> pushing"
|
||||
git push
|
||||
fi
|
||||
|
||||
parent_path=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
cd "$parent_path"
|
||||
|
||||
if ! command -v podman >/dev/null 2>&1; then
|
||||
echo "podman is required to build and push the image" >&2
|
||||
exit 1
|
||||
# Guards. Only tracked-file state matters — untracked and ignored files never
|
||||
# enter a `git archive HEAD` build. HEAD must match origin/$BRANCH so the commit
|
||||
# the image is tagged with is one the remote actually has.
|
||||
if [ "$FORCE" != true ]; then
|
||||
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
|
||||
echo "x tracked files modified - commit or stash first (or --force)" >&2
|
||||
exit 1
|
||||
fi
|
||||
git fetch origin --quiet
|
||||
if [ "$(git rev-parse HEAD)" != "$(git rev-parse "origin/$BRANCH")" ]; then
|
||||
echo "x HEAD != origin/$BRANCH - push or pull first (or --force)" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${SKIP_DEPLOY:-}" ]] && ! command -v ansible-playbook >/dev/null 2>&1; then
|
||||
echo "ansible-playbook is required to deploy; SKIP_DEPLOY=1 builds and pushes only" >&2
|
||||
exit 1
|
||||
# Opt-in rather than part of the deploy path, so a deploy behaves the same here
|
||||
# as it does in the sibling apps. Run it before building when the change touches
|
||||
# the app rather than only the deployment.
|
||||
if [ "$CHECKS" = true ]; then
|
||||
echo "==> lint, format, types and tests"
|
||||
npm run lint
|
||||
npm run format:check
|
||||
npm run typecheck
|
||||
npm test
|
||||
fi
|
||||
|
||||
if [[ -z "${SKIP_CHECKS:-}" ]]; then
|
||||
echo "==> lint, format, types and tests"
|
||||
npm run lint
|
||||
npm run format:check
|
||||
npm run typecheck
|
||||
npm test
|
||||
fi
|
||||
TAG=$(git rev-parse --short HEAD)
|
||||
echo "==> deploying commit $TAG"
|
||||
|
||||
echo "==> building and pushing registry.tanshu.com/menu-admin:$TAG"
|
||||
make build-production TAG="$TAG"
|
||||
echo "========================================================"
|
||||
echo " Building & Uploading Container Image (Podman)"
|
||||
echo "========================================================"
|
||||
make build-production COMMIT="$TAG"
|
||||
|
||||
if [[ -n "${SKIP_DEPLOY:-}" ]]; then
|
||||
echo "SKIP_DEPLOY is set, so the playbook was not run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd ansible
|
||||
if [[ -n "${SITE:-}" ]]; then
|
||||
echo "==> deploying $SITE"
|
||||
ansible-playbook --inventory=hosts playbook.yml --limit "$SITE" -e "tag=$TAG"
|
||||
echo ""
|
||||
echo "========================================================"
|
||||
echo " Executing Ansible Deployment"
|
||||
echo "========================================================"
|
||||
# The play configures rootless Podman Quadlet units that belong to the
|
||||
# connecting user, so it must connect as that user and must NOT become root.
|
||||
# `-u` is the account that owns ~/.config/containers/systemd on the target.
|
||||
deploy_user="${ANSIBLE_USER:-tanshu}"
|
||||
cd ansible || exit
|
||||
# image_tag pins the exact commit; rollback is -e image_tag=<older-commit>.
|
||||
# The inventory is the one difference from the sibling apps, which deploy a
|
||||
# single app: this play carries three Sites and a var_file per Site.
|
||||
if [ -n "${SITE:-}" ]; then
|
||||
ansible-playbook -u "$deploy_user" --inventory=hosts playbook.yml --limit "$SITE" -e "image_tag=$TAG"
|
||||
else
|
||||
echo "==> deploying every Site"
|
||||
ansible-playbook --inventory=hosts playbook.yml -e "tag=$TAG"
|
||||
ansible-playbook -u "$deploy_user" --inventory=hosts playbook.yml -e "image_tag=$TAG"
|
||||
fi
|
||||
Reference in new issue
Block a user