~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
206 lines · 10.6 KB Raw History
# Deploys main to knotless.dev once this commit's checks all pass,
# with deploy/deploy.sh over SSH, as a deploy from one's own checkout
# does. DEPLOY_SSH_KEY is granted to pushes to main alone, in the
# trusted runner's secrets file on its host; the settings below are what
# deploy/local.env holds for a deploy by hand.
#
# It starts only once the seven checks have passed at its commit
# (`after`), and holds no job while it waits for them, so the checks it
# waits for are never kept from one; a check that fails files it failed
# without running. The image is then built as a candidate,
# knotless:build-<commit>, which changes nothing that runs, and goes up.
# Until then a deploy gives way to a newer commit: when main moves past
# it, before or during the build, it stops there, removes its candidate
# and passes, leaving the deploy to the newer one. The next build removes
# any candidate left behind. Once it goes up, it runs to its end: a newer
# commit's deploy waits for this one rather than cutting it off halfway
# (superseded: finish).
#
# Once up, the image is published as the release of the `knotless-server`
# package at main's version (docs/design/packages.md#images), if that
# version has none yet: `docker save` on the host, as an OCI image layout,
# into `knotless release publish --oci` there, as the owner, with RELEASE_PASSWORD,
# an app password of theirs granted to pushes to main as release.yml's is.
# It is then `docker pull knotless.dev/chadtmiller.com/knotless/knotless-server`:
# one image for serve, the pages, the runner and the triager, as here.
#
# Last, the runner host (deploy/runner-host) takes the same image, copied
# from this host only when its runner's build differs, and is restarted
# with the settings below, so the runner that lands the merge queues reads
# their rules as main does. It is reached with RUNNER_HOST_SSH_KEY, granted
# to pushes to main alone, which its root takes only from the trusted
# runner's box.

# Its secrets are on ci.knotless.dev alone, the runner that runs no one
# else's code (deploy/runner-host/README.md, The trusted runner).
runners: [ci.knotless.dev]
on:
  push:
    refs: [refs/heads/main]
after: [check, test, web, lexicons, shots, docs, e2e]
superseded: finish
image: docker:28-cli
secrets: [DEPLOY_SSH_KEY, RELEASE_PASSWORD, RUNNER_HOST_SSH_KEY]
env:
  KNOTLESS_HOST: root@knotless.dev
  KNOTLESS_DOMAIN: knotless.dev
  # Repositories' sites (docs/design/pages.md), whose certificate the
  # proxy holds once deploy/setup.sh has run with this and the zone's
  # token.
  KNOTLESS_PAGES_DOMAIN: knotless.page
  # The iOS app's notifications, once the key is on the host as
  # deploy/README.md says. Neither of these is a secret.
  KNOTLESS_APNS_KEY_ID: J53A95HDW7
  KNOTLESS_APNS_TEAM: YN68LN9T7Z
  KNOTLESS_DATA: /mnt/data/knotless
  # Half the host's 8 cores for a build at most, which the host's other
  # sites share (deploy.sh).
  KNOTLESS_BUILD_CPUS: 4
  KNOTLESS_ADMIN_DIDS: did:plc:bcgltzqazw5tb6k2g3ttenbj
  # The runner's account (ci.knotless.dev), the triager's
  # (triage.knotless.dev) and the reviewer's (review.knotless.dev), which
  # log in at /oauth/bot for an OAuth session that reads private
  # repositories.
  KNOTLESS_BOT_DIDS: did:plc:ejh2v3z5ieqhqgz3ybk6r35x,did:plc:hwfywp7r4cfumv54v2xk3jz7,did:plc:kqi2glvzerg4dt66k64co2sl
  # No runner here: knotless's checks and merge queue run on the runner
  # host (runner.knotless.dev), and the workflows that hold a secret, this
  # one among them, on the trusted runner (ci.knotless.dev), neither on
  # this host (deploy/runner-host/README.md). With no KNOTLESS_RUN_REPOS a
  # deploy removes any runner an earlier one left here.
  # The runner host's account (deploy/runner-host), which a new public
  # repository names unless its owner names others: it runs anyone's.
  KNOTLESS_DEFAULT_RUNNER_DID: did:plc:4bocpkn5zkwe4wuu4plrj5kt
  # That host, deployed after this one (deploy/runner-host/deploy.sh), as
  # runner.knotless.dev. It lands the merge queues of the repositories
  # that name it, and runs knotless's pulls alone, under runc for the
  # owner and their organization (README.md there).
  KNOTLESS_RUNNER_HOST: root@runner.knotless.dev
  KNOTLESS_RUNNER_DOMAIN: runner.knotless.dev
  KNOTLESS_RUNNER_DID: did:plc:4bocpkn5zkwe4wuu4plrj5kt
  KNOTLESS_RUNNER_ARGS: --queue --unmetered did:plc:bcgltzqazw5tb6k2g3ttenbj --unmetered did:plc:e7rftrdyz5e2rw4y6ocszew2 --trust did:plc:bcgltzqazw5tb6k2g3ttenbj --trust did:plc:e7rftrdyz5e2rw4y6ocszew2 --pull-jobs 4
  KNOTLESS_RUNNER_CPUS: "4"
  KNOTLESS_RUNNER_JOBS: "4"
  KNOTLESS_RUNNER_MEMORY: "12"
  # The oldest client this asks for: 0.6.0 deletes a deleted ref's record
  # and starts a new ref at the time, and an older client making a branch
  # again under a name deleted within the day can have its pushes refused.
  KNOTLESS_CLIENT_MINIMUM: 0.6.0
  KNOTLESS_CLIENT_MINIMUM_REASON: "knotless 0.6.0 changed how deleted branches are stored, and older clients can have their pushes refused: run `knotless upgrade`."
  # The oldest build of the iOS app to let run, none for now. To raise it,
  # once the newer build is in people's reach (ios/README.md):
  # KNOTLESS_IOS_MINIMUM_BUILD: "5"
  # KNOTLESS_IOS_MINIMUM_REASON: "This version can lose what you write."
  # Labels issues with Clef, as its own account, whose labels count only
  # for the scopes the owner names it a triager of (knotless triager add):
  # here, and in every repository that names it.
  KNOTLESS_TRIAGE_REPOS: chadtmiller.com/knotless
  KNOTLESS_TRIAGE_ARGS: --model clef --scopes kind,area --discover
  # Reviews pulls with Claude Code, as review.knotless.dev, once
  # /etc/knotless/review.env holds its logins (deploy/deploy.sh): every
  # open pull by the owner or a collaborator, asked or not (--all), in
  # every repository the owner holds, and grain.social's (--owner): the
  # public ones, and the private ones shared with review.knotless.dev,
  # whose sharing left it an invite at the appview.
  KNOTLESS_REVIEW_REPOS: --owner chadtmiller.com --owner grain.social
  # Five rounds, where a merge of the base into a pull spends none.
  KNOTLESS_REVIEW_ARGS: --all --rounds 5
  # Brings the pulls the merge queue turned away for a conflict up to
  # date and queues them again, once /etc/knotless/keep.env holds its
  # account's login (deploy/deploy.sh).
  KNOTLESS_KEEP_REPOS: chadtmiller.com/knotless
steps:
  - name: tools
    run: |
      apk add --no-cache bash
      # A script run with the knotless CLI, in one container on the host
      # from the image running there, with the repository, ref and commit,
      # and labeled with the commit for unless-moved to end.
      cat > /usr/local/bin/on-host <<'EOF'
      #!/bin/sh
      exec env DOCKER_HOST="ssh://$KNOTLESS_HOST" docker run --rm \
        --label "dev.knotless.deploy=$KNOTLESS_RUN_SHA" \
        -e KNOTLESS_APPVIEW="https://$KNOTLESS_DOMAIN" \
        -e REPO="knotless://$KNOTLESS_RUN_REPO" -e SHA="$KNOTLESS_RUN_SHA" \
        -e REF="$KNOTLESS_RUN_REF" \
        knotless:current sh -c "$1"
      EOF
      # Whether main has moved past this commit: 0 if it has, leaving
      # ~/.moved for the steps after, 1 if not, 2 if it cannot tell.
      cat > /usr/local/bin/moved <<'EOF'
      #!/bin/sh
      set -o pipefail
      tip=$(on-host 'knotless repo refs "$REPO"' | awk -F '\t' -v ref="$KNOTLESS_RUN_REF" '$2 == ref { print $1 }') || exit 2
      [ "$tip" != "$KNOTLESS_RUN_SHA" ] || exit 1
      echo "$KNOTLESS_RUN_REF is at ${tip:-nothing} now, not $KNOTLESS_RUN_SHA: a newer commit will deploy"
      touch ~/.moved
      EOF
      # Runs a command until it ends, as it ends, or until main moves past
      # this commit, asked every 30s: then it is ended, with what it ran on
      # the host (a build is cancelled with its client), and this passes.
      cat > /usr/local/bin/unless-moved <<'EOF'
      #!/bin/sh
      setsid "$@" &
      pid=$!
      while :; do
        i=0
        while [ $i -lt 30 ] && kill -0 $pid 2>/dev/null; do sleep 1; i=$((i + 1)); done
        kill -0 $pid 2>/dev/null || break
        if moved; then
          kill -TERM -$pid 2>/dev/null
          wait $pid
          ids=$(DOCKER_HOST="ssh://$KNOTLESS_HOST" docker ps -q --filter "label=dev.knotless.deploy=$KNOTLESS_RUN_SHA")
          [ -z "$ids" ] || DOCKER_HOST="ssh://$KNOTLESS_HOST" docker rm -f $ids >/dev/null
          exit 0
        fi
      done
      wait $pid
      EOF
      chmod +x /usr/local/bin/on-host /usr/local/bin/moved /usr/local/bin/unless-moved
  - name: key
    uses: chadtmiller.com/steps/ssh-key@1
    secrets: {SSH_KEY: DEPLOY_SSH_KEY}
  - name: build
    run: |
      # Unsure where main is, it builds: the deploy step asks again.
      moved && exit 0
      unless-moved deploy/deploy.sh build
      [ ! -e ~/.moved ] || deploy/deploy.sh drop
  - name: deploy
    run: |
      [ ! -e ~/.moved ] || exit 0
      at=0
      moved || at=$?
      case $at in
        0) deploy/deploy.sh drop && exit 0 ;;
        2) echo "cannot tell where $KNOTLESS_RUN_REF is: not deploying"; deploy/deploy.sh drop; exit 1 ;;
      esac
      deploy/deploy.sh up
      touch ~/.up
  - name: image
    run: |
      [ ! -e ~/.moved ] && [ -e ~/.up ] || exit 0
      [ -n "$RELEASE_PASSWORD" ] || { echo "no RELEASE_PASSWORD: not publishing the image"; exit 0; }
      version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
      if on-host "knotless release list \"\$REPO\" knotless-server | cut -f2 | grep -qxF '$version'"; then
        echo "knotless-server $version is published already"
        exit 0
      fi
      # The image as it runs, saved on the host and read there, in a
      # container of it, given the password from this one's environment.
      DOCKER_HOST="ssh://$KNOTLESS_HOST" docker save knotless:current |
        KNOTLESS_PASSWORD="$RELEASE_PASSWORD" DOCKER_HOST="ssh://$KNOTLESS_HOST" docker run --rm -i \
          -e KNOTLESS_APPVIEW="https://$KNOTLESS_DOMAIN" -e REPO="knotless://$KNOTLESS_RUN_REPO" \
          -e SHA="$KNOTLESS_RUN_SHA" -e VERSION="$version" -e KNOTLESS_PASSWORD \
          knotless:current sh -c '
            set -e
            dir=$(mktemp -d)
            tar -x -C "$dir"
            knotless release publish "$REPO" knotless-server "$VERSION" --oci "$dir" --commit "$SHA"
          '
  - name: runner host
    run: |
      [ ! -e ~/.moved ] && [ -e ~/.up ] || exit 0
      [ -n "$RUNNER_HOST_SSH_KEY" ] || { echo "no RUNNER_HOST_SSH_KEY: not deploying the runner host"; exit 0; }
      printf '%s\n' "$RUNNER_HOST_SSH_KEY" > ~/.ssh/runner_host && chmod 600 ~/.ssh/runner_host
      printf 'Host %s\n  IdentityFile ~/.ssh/runner_host\n' "${KNOTLESS_RUNNER_HOST#*@}" >> ~/.ssh/config
      KNOTLESS_RUNNER_IMAGE_FROM="$KNOTLESS_HOST" deploy/runner-host/deploy.sh