diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 1dc5958..c813654 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -33,41 +33,59 @@ jobs: steps: - uses: actions/checkout@v4 + # Gitea's registry, not registry.netork.io. + # + # registry.netork.io is plain registry:2 with htpasswd auth, which knows + # nothing about repositories: every account that can log in reads and + # writes everything on it, including the accounts issued to customer + # instances. It keeps the images those instances are meant to pull + # (netork/engine, netork/ui, netork/satellite); the marketing site is not + # one of them. Gitea scopes packages to their owning account, and no + # customer has one. netOrk issue #172. + # + # REGISTRY_TOKEN is a Gitea access token with write:package — the token + # Actions injects per run is scoped to the repository API and the package + # registry rejects it outright. - name: Login to registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.netork.io -u "${{ secrets.REGISTRY_USER }}" --password-stdin + run: | + set -euo pipefail + if [ -z "${{ secrets.REGISTRY_TOKEN }}" ]; then + echo "::error::REGISTRY_TOKEN is not set (Gitea token with write:package)." + exit 1 + fi + LOGIN_USER="${{ secrets.REGISTRY_USER }}" + [ -n "$LOGIN_USER" ] || LOGIN_USER="${{ github.actor }}" + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.netork.io -u "$LOGIN_USER" --password-stdin - name: Build & push run: | + set -euo pipefail SHA=$(git rev-parse --short HEAD) docker build \ - -t registry.netork.io/netork/website:latest \ - -t registry.netork.io/netork/website:main-${SHA} \ + -t git.netork.io/netork/website:latest \ + -t git.netork.io/netork/website:main-${SHA} \ . - docker push registry.netork.io/netork/website:latest - docker push registry.netork.io/netork/website:main-${SHA} + docker push git.netork.io/netork/website:latest + docker push git.netork.io/netork/website:main-${SHA} - name: Logout if: always() - run: docker logout registry.netork.io + run: docker logout git.netork.io - deploy: - name: Deploy — pull & restart on host - runs-on: ubuntu-latest - needs: [publish] - steps: - - name: Login to registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.netork.io -u "${{ secrets.REGISTRY_USER }}" --password-stdin - - - name: Pull & restart - run: | - docker pull registry.netork.io/netork/website:latest - docker rm -f netork-website 2>/dev/null || true - docker run -d \ - --name netork-website \ - --restart unless-stopped \ - --network proxy-net \ - registry.netork.io/netork/website:latest - - - name: Logout - if: always() - run: docker logout registry.netork.io +# The deploy job that used to live here has been removed, deliberately. +# +# It ran `docker run` against whatever runner picked the job up, which worked +# while exactly one runner existed. There are now several (netork-runner-12 on +# .12, netork-runner-13 on .13, and the original netork-runner) and none of them +# is on 10.7.224.11, where this site actually runs and where the proxy-net it +# attaches to lives. The job would therefore have started a second website +# container on the wrong host and reported success, while netork.io went on +# serving the old one. +# +# Deployment is now an explicit step: scripts/deploy.sh, run from a workstation, +# which targets .11 by name and verifies afterwards that the container really is +# on the image that was pulled. +# +# To get push-to-deploy back, either register a runner on .11 with a label of its +# own and pin `runs-on:` to it, or give CI an ssh key for .11. Both are choices +# about where a credential lives, so neither was made here. diff --git a/.gitignore b/.gitignore index fa7b7e4..8154ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ memory/ .env.local *.local .DS_Store + +# Deploy target + registry token +deploy.env diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..1b81fed --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Deploy the marketing site to the host that actually serves it. +# +# ./scripts/deploy.sh [--version=] [server] +# +# This used to be a CI job. It ran `docker run` on whichever runner picked the +# job up, which was correct while exactly one runner existed — there are now +# several, none of them on the host this site runs on, so the job would have +# started a second container in the wrong place and reported success. Naming the +# target is the whole point of this script. +set -euo pipefail + +SERVER="${DEPLOY_SERVER:-10.7.224.11}" +REGISTRY="${REGISTRY:-git.netork.io/netork}" +VERSION="${VERSION:-latest}" +NAME="${CONTAINER_NAME:-netork-website}" + +ENV_FILE="$(cd "$(dirname "$0")/.." && pwd)/deploy.env" +# shellcheck source=/dev/null +[[ -f "$ENV_FILE" ]] && source "$ENV_FILE" + +for arg in "$@"; do + case "$arg" in + --version=*) VERSION="${arg#--version=}" ;; + *) SERVER="$arg" ;; + esac +done + +IMAGE="${REGISTRY}/website:${VERSION}" +echo "[${SERVER}] Deploying ${IMAGE}" + +if [[ -n "${REGISTRY_TOKEN:-}" ]]; then + ssh -n "$SERVER" "echo '${REGISTRY_TOKEN}' | docker login git.netork.io -u '${REGISTRY_USER:-christianmanivong}' --password-stdin" \ + | sed "s/^/[${SERVER}] /" +fi + +# Pull first, and let a failure stop the script here: the container is only +# removed once there is something to replace it with. +echo "[${SERVER}] Pulling..." +ssh -n "$SERVER" "docker pull '${IMAGE}'" | tail -2 | sed "s/^/[${SERVER}] /" + +echo "[${SERVER}] Recreating..." +ssh -n "$SERVER" "docker rm -f '${NAME}' >/dev/null 2>&1 || true; \ + docker run -d --name '${NAME}' --restart unless-stopped --network proxy-net '${IMAGE}' >/dev/null && echo started" \ + | sed "s/^/[${SERVER}] /" + +# `docker run` cannot silently reuse an old container the way `compose up -d` +# can, but the tag it resolved might still not be the one that was just pulled. +# Compare, rather than trust. +echo "[${SERVER}] Verifying..." +WANT=$(ssh -n "$SERVER" "docker image inspect --format '{{.Id}}' '${IMAGE}'") +GOT=$(ssh -n "$SERVER" "docker inspect --format '{{.Image}}' '${NAME}'") +if [[ "$WANT" != "$GOT" ]]; then + echo "[${SERVER}] ERROR: container runs ${GOT}, expected ${WANT}" >&2 + exit 1 +fi +echo "[${SERVER}] Verified: ${NAME} runs ${IMAGE}." + +echo "[${SERVER}] Checking the site answers..." +CODE=$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 https://netork.io/ || echo 000) +echo "[${SERVER}] https://netork.io -> ${CODE}" +[[ "$CODE" == "200" ]] || { echo "[${SERVER}] ERROR: site is not answering 200" >&2; exit 1; }