name: Rotate Coder API Token # Keeps the CODER_SESSION_TOKEN secret (used by coder-templates.yml) alive # forever without anyone needing to remember to refresh it. This Coder # deployment caps token lifetime at 168h (7 days), so this runs daily, # mints a fresh 168h token, writes it back into this repo's # CODER_SESSION_TOKEN secret via the Gitea API, then deletes the token(s) # it replaced. # # One-time bootstrap (see README "Auto-provisioning" section): a # ROTATION_PAT secret holding a Gitea personal access token (write:repository # scope, no expiration) with permission to write this repo's Actions secrets. # Not named GITEA_ROTATION_TOKEN because Gitea reserves the GITEA_ prefix for # its own automatic tokens/variables and rejects secrets with that prefix. # Nothing else needs to touch this ever again. on: schedule: - cron: "0 3 * * *" workflow_dispatch: {} jobs: rotate: runs-on: ubuntu-latest env: CODER_URL: ${{ secrets.CODER_URL }} CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }} ROTATION_PAT: ${{ secrets.ROTATION_PAT }} GITEA_API_URL: ${{ github.server_url }}/api/v1 GITEA_REPO_PATH: ${{ github.repository }} steps: - name: Install coder CLI and jq run: | set -e curl -fsSL https://coder.com/install.sh | sh coder version command -v jq >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y -qq jq) - name: Create a new token id: new_token run: | set -e NAME="gitea-ci-$(date -u +%Y%m%dT%H%M%SZ)" TOKEN="$(coder tokens create --name "$NAME" --lifetime 168h)" if [ -z "$TOKEN" ]; then echo "::error::coder tokens create returned no token" exit 1 fi echo "::add-mask::$TOKEN" echo "name=$NAME" >> "$GITHUB_OUTPUT" echo "token=$TOKEN" >> "$GITHUB_OUTPUT" - name: Publish new token to CODER_SESSION_TOKEN secret env: NEW_TOKEN: ${{ steps.new_token.outputs.token }} run: | set -e BODY="$(jq -n --arg data "$NEW_TOKEN" '{data:$data}')" HTTP_STATUS="$(curl -s -o /tmp/put-secret.out -w '%{http_code}' \ -X PUT \ -H "Authorization: token ${ROTATION_PAT}" \ -H "Content-Type: application/json" \ -d "$BODY" \ "${GITEA_API_URL}/repos/${GITEA_REPO_PATH}/actions/secrets/CODER_SESSION_TOKEN")" if [ "$HTTP_STATUS" != "201" ] && [ "$HTTP_STATUS" != "204" ]; then echo "::error::Failed to update CODER_SESSION_TOKEN secret (HTTP $HTTP_STATUS)" cat /tmp/put-secret.out exit 1 fi echo "CODER_SESSION_TOKEN secret updated (HTTP $HTTP_STATUS)." - name: Delete the token(s) this replaced env: KEEP_NAME: ${{ steps.new_token.outputs.name }} run: | set -e coder tokens list -o json \ | jq -r --arg keep "$KEEP_NAME" \ '.[] | select(.token_name | startswith("gitea-ci-")) | select(.token_name != $keep) | .id' \ | while read -r id; do [ -z "$id" ] && continue echo "Removing superseded token $id" coder tokens delete "$id" --delete \ || echo "::warning::Failed to delete superseded token $id" done