#!/usr/bin/env bash # # menu-admin deploy — push the current commit, build it with podman, and install # the Quadlet units on emily with ansible. # # 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= 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]}")" 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 # 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 # 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 TAG=$(git rev-parse --short HEAD) echo "==> deploying commit $TAG" echo "========================================================" echo " Building & Uploading Container Image (Podman)" echo "========================================================" make build-production COMMIT="$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=. # 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 ansible-playbook -u "$deploy_user" --inventory=hosts playbook.yml -e "image_tag=$TAG" fi