feat(deploy): production always runs a numbered version
Policy set by _null. The stack pins :vX.Y.Z — never :dev, never :latest. The reason is what a pointer cannot do. A stack file naming :dev says "dev" today and will still say "dev" after the image underneath it changes, so the file records a preference rather than a fact and "what is running?" has no answer that survives the next push. Rollback is worse: there is nothing to roll back TO, because a pointer only ever names now. deploy.sh: with no --tag it now resolves the newest published NUMBERED version from the registry and says which it picked, instead of defaulting to :dev. A floating tag is refused outright, with --allow-floating as the loud escape hatch for the one case that is not a mistake — reproducing a fault on whatever a pointer currently is. release.sh: no longer moves :dev. It published both tags until now because the stack followed the pointer; under this policy nothing deploys :dev, so moving it would only publish something that misrepresents what is running. scripts/docker-push.sh deleted, with its npm script. Its entire job was building and pushing an unnumbered :dev with no version bump and no guards, which is now the exact thing the policy exists to prevent. Keeping a command that quietly violates a policy is how the policy stops being true. :dev and :latest stay in the registry, frozen. Not deleted: the running container was created from :dev, and removing the tag an existing deployment names is how a recreate fails to pull. Stack 58 still pins :dev as this lands. Correcting that is the first deploy made under the policy, and it is why the default resolves a version rather than reusing whatever the stack already names. Guards proven: default resolves v0.8.3; --tag dev refused; --tag latest refused; --allow-floating warns and proceeds; unpublished tag still refused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6a890ad8bc
commit
1a3ba305a7
|
|
@ -249,20 +249,46 @@ There is no non-production environment to rehearse against. `deploy.sh` takes a
|
||||||
verified backup before the irreversible part for that reason, and refuses to
|
verified backup before the irreversible part for that reason, and refuses to
|
||||||
continue if the backup fails.
|
continue if the backup fails.
|
||||||
|
|
||||||
**Deploy a version, not the pointer, when it matters.** `npm run deploy` follows
|
### Production always runs a numbered version
|
||||||
`:dev`, which is a moving tag: afterwards nothing in the stack file says which
|
|
||||||
image that was. `npm run deploy -- --tag v0.9.4` pins an immutable version, which
|
**Policy, set 2026-08-18.** The stack pins `:vX.Y.Z`. Never `:dev`, never
|
||||||
is what you want when somebody asks what is running, and what you need to roll
|
`:latest`, never any other pointer.
|
||||||
back:
|
|
||||||
|
The reason is what a pointer cannot do. A stack file naming `:dev` says `dev`
|
||||||
|
today and will still say `dev` after the image underneath it changes — so the
|
||||||
|
file records a preference rather than a fact, and "what is running?" has no
|
||||||
|
answer that survives the next push. Rollback is worse: there is nothing to roll
|
||||||
|
back *to*, because a pointer only ever names now.
|
||||||
|
|
||||||
|
`deploy.sh` enforces it. With no `--tag` it resolves the **newest published
|
||||||
|
numbered version** from the registry and says which it picked; given a floating
|
||||||
|
tag it refuses:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash scripts/deploy.sh --tag v0.8.3 --dry-run # see the change first
|
npm run deploy # newest published vX.Y.Z
|
||||||
bash scripts/deploy.sh --tag v0.8.3 # roll back to a known image
|
npm run deploy -- --tag v0.9.4 --dry-run # a specific version, previewed
|
||||||
|
npm run deploy -- --tag v0.9.4 # deploy it
|
||||||
|
npm run deploy -- --tag v0.8.3 # roll back to a known image
|
||||||
```
|
```
|
||||||
|
|
||||||
It refuses a tag the registry does not hold — a missing tag would otherwise
|
`--allow-floating` exists for the one case that is not a mistake — reproducing a
|
||||||
become an outage, since Portainer accepts the file, fails to pull, and leaves
|
fault on whatever a pointer currently is — and warns every time.
|
||||||
the container stopped.
|
|
||||||
|
It also refuses a tag the registry does not hold. A missing tag would otherwise
|
||||||
|
become an outage: Portainer accepts the file, fails to pull, and leaves the
|
||||||
|
container stopped.
|
||||||
|
|
||||||
|
**Stack 58 pins `:dev` as this is written**, which is the state the policy
|
||||||
|
corrects. The first deploy made under it repoints the stack at a version, and
|
||||||
|
after that the stack file answers the question on its own.
|
||||||
|
|
||||||
|
`:dev` and `:latest` remain in the registry, frozen. They are not deleted
|
||||||
|
because the running container was created from `:dev`, and removing the tag an
|
||||||
|
existing deployment names is how a recreate fails to pull. Nothing publishes to
|
||||||
|
them any more — `release.sh` stopped moving `:dev` when this policy was set, and
|
||||||
|
scripts/docker-push.sh — named without backticks because it is gone — was
|
||||||
|
deleted, its only job having been to push an unnumbered `:dev` with no version
|
||||||
|
bump and no guards.
|
||||||
|
|
||||||
**What `deploy.sh` preserves, and why that is the dangerous part.** Portainer
|
**What `deploy.sh` preserves, and why that is the dangerous part.** Portainer
|
||||||
treats a stack `PUT` as the whole desired state. Sending it without the stack's
|
treats a stack `PUT` as the whole desired state. Sending it without the stack's
|
||||||
|
|
|
||||||
|
|
@ -127,9 +127,9 @@ Run from the repository root.
|
||||||
| `npm start` / `npm run server` | the Express server alone, serving `dist/` |
|
| `npm start` / `npm run server` | the Express server alone, serving `dist/` |
|
||||||
| `npm run docker:build` / `docker:run` | build and run the image locally |
|
| `npm run docker:build` / `docker:run` | build and run the image locally |
|
||||||
| `npm run docker:compose:up` / `:down` / `:logs` | the compose stack |
|
| `npm run docker:compose:up` / `:down` / `:logs` | the compose stack |
|
||||||
| `npm run release` | **publish an image.** Bump, guards, build, verify its version label, push `:vX.Y.Z` and move `:dev`, commit last, tag. `-- --dry-run` first |
|
| `npm run release` | **publish an image.** Bump, guards, build, verify its version label, push `:vX.Y.Z`, commit last, tag. `-- --dry-run` first |
|
||||||
| `npm run deploy` | **move the running stack** to a published image. Backs up first. `-- --dry-run` first, `-- --tag vX.Y.Z` to pin or roll back |
|
| `npm run deploy` | **move the running stack** to a published **numbered** version — the newest by default, `-- --tag vX.Y.Z` to pin or roll back. Backs up first, and refuses a floating tag. `-- --dry-run` first |
|
||||||
| `npm run docker:push` | build, tag and push `queue-north-website:dev` with no bump and no guards. Superseded by `npm run release` |
|
|
||||||
| `npm run docker:test` | build the image and smoke-test it on 3001 |
|
| `npm run docker:test` | build the image and smoke-test it on 3001 |
|
||||||
|
|
||||||
**There is no `npm test`, and that is not an omission in this table.** There is
|
**There is no `npm test`, and that is not an omission in this table.** There is
|
||||||
|
|
|
||||||
|
|
@ -304,9 +304,9 @@ one, and never report progress the API did not return.
|
||||||
|
|
||||||
- **Not the work list.** That is the tracker: milestones are batches, issues are
|
- **Not the work list.** That is the tracker: milestones are batches, issues are
|
||||||
deliverables.
|
deliverables.
|
||||||
- **Not the release procedure.** This project has no `release.sh` — publishing
|
- **Not the release procedure.** That is `npm run release` to publish and
|
||||||
is `npm run docker:push`, and how nebula moves to a new image is not yet
|
`npm run deploy` to move the stack — two commands on purpose. `scripts/release.sh`
|
||||||
written down. `docs/OPERATIONS.md` records that gap;
|
and `docs/OPERATIONS.md` carry the detail, and
|
||||||
`docs/security/SECURITY_CHECKLIST.md` is the list to work through first.
|
`docs/security/SECURITY_CHECKLIST.md` is the list to work through first.
|
||||||
- **Not the QA procedure.** That is `docs/qa/ClaudeQAPlan.md`, which ends in its
|
- **Not the QA procedure.** That is `docs/qa/ClaudeQAPlan.md`, which ends in its
|
||||||
own version of step 7.
|
own version of step 7.
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,9 @@ each row says what it does *here*.
|
||||||
| `scripts/preflight.sh` | headers and TLS against the live origin. No `--auth` checks: there are no accounts |
|
| `scripts/preflight.sh` | headers and TLS against the live origin. No `--auth` checks: there are no accounts |
|
||||||
| `scripts/backup.sh` | a verified SQLite dump. Its ENGINE block was rewritten for better-sqlite3's online `.backup()` — see below |
|
| `scripts/backup.sh` | a verified SQLite dump. Its ENGINE block was rewritten for better-sqlite3's online `.backup()` — see below |
|
||||||
| `scripts/restore-check.sh` | restores the newest dump into a scratch file, runs `PRAGMA integrity_check`, counts tables, and **times it**. A backup nobody has restored is a guess |
|
| `scripts/restore-check.sh` | restores the newest dump into a scratch file, runs `PRAGMA integrity_check`, counts tables, and **times it**. A backup nobody has restored is a guess |
|
||||||
| `scripts/release.sh` | **publishes.** Bump, guard, build, verify the image's own version label, push `:vX.Y.Z` **and** move `:dev`, commit last, tag. Refuses to overwrite a published tag, to build on a dirty tree, or to build when the three hard-coded copies of the public origin disagree. Adapted from PrivacyLLC-Web's — see below |
|
| `scripts/release.sh` | **publishes.** Bump, guard, build, verify the image's own version label, push `:vX.Y.Z`, commit last, tag. Refuses to overwrite a published tag, to build on a dirty tree, or to build when the three hard-coded copies of the public origin disagree. Adapted from PrivacyLLC-Web's — see below |
|
||||||
| `scripts/deploy.sh` | **deploys, and does not build.** Points Portainer stack 58 at an already-published tag, takes a verified backup first, preserves the stack's twelve environment variables, then waits for health and checks both public origins. Reports the digest before and after |
|
| `scripts/deploy.sh` | **deploys, and does not build.** Points Portainer stack 58 at an already-published **numbered** version — it refuses a floating tag — taking a verified backup first and preserving the stack's twelve environment variables, then waits for health and checks both public origins. Reports the digest before and after |
|
||||||
| `scripts/docker-push.sh` | builds and pushes `queue-north-website:dev` only, with no version bump and no guards. Predates the template and is **superseded by `release.sh`** — kept for now because it is what the muscle memory reaches for |
|
|
||||||
| `scripts/docker-test.sh` | builds the image and runs it locally on 3001. Predates the template |
|
| `scripts/docker-test.sh` | builds the image and runs it locally on 3001. Predates the template |
|
||||||
| `scripts/prerender.js` | the build step that emits static HTML for every route. Predates the template |
|
| `scripts/prerender.js` | the build step that emits static HTML for every route. Predates the template |
|
||||||
|
|
||||||
|
|
@ -171,9 +171,9 @@ each is a fact about this project rather than a preference:
|
||||||
The original refuses to release on a half-run 1,600-test run; this one says out
|
The original refuses to release on a half-run 1,600-test run; this one says out
|
||||||
loud that a build, a secret scan and a doc-header check are not tests and that
|
loud that a build, a secret scan and a doc-header check are not tests and that
|
||||||
nothing in the gate exercised a route, a form or an API response.
|
nothing in the gate exercised a route, a form or an API response.
|
||||||
- **It moves `:dev` as well as publishing `:vX.Y.Z`,** because stack 58 follows
|
- **It publishes one tag and does not move `:dev`.** Both scripts follow the
|
||||||
`:dev`. That makes `:dev` a pointer and never evidence — `status.sh` reads the
|
policy below: production always runs a numbered version, so nothing deploys a
|
||||||
digest and the version label for that.
|
pointer and publishing one would only misrepresent what is running.
|
||||||
- **It checks the public origin in three files rather than one.** The original
|
- **It checks the public origin in three files rather than one.** The original
|
||||||
passes its origin in as a build arg, so it has one copy to validate. Here
|
passes its origin in as a build arg, so it has one copy to validate. Here
|
||||||
`https://queuenorth.com` is written out in `src/lib/seo.js`,
|
`https://queuenorth.com` is written out in `src/lib/seo.js`,
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ Observable, in this order:
|
||||||
- **No automated tests at all.** No test runner, no typecheck. `npm run build` is
|
- **No automated tests at all.** No test runner, no typecheck. `npm run build` is
|
||||||
the only gate, and it catches syntax and imports rather than behaviour. Recorded
|
the only gate, and it catches syntax and imports rather than behaviour. Recorded
|
||||||
as a standing gap in `docs/qa/ClaudeQACoverage.md`.
|
as a standing gap in `docs/qa/ClaudeQACoverage.md`.
|
||||||
- **The roll-forward procedure is not written down.** Publishing is
|
- **Deployment is scripted but has never been run end to end.** `npm run
|
||||||
`scripts/docker-push.sh`; how nebula moves to a new image is currently in
|
release` publishes and `npm run deploy` moves Portainer stack 58, both added
|
||||||
somebody's head. Noted in `docs/OPERATIONS.md`.
|
2026-08-18 and both exercised only in `--dry-run`. The first real run of either
|
||||||
|
is still ahead.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
"docker:compose:up": "docker-compose up -d",
|
"docker:compose:up": "docker-compose up -d",
|
||||||
"docker:compose:down": "docker-compose down",
|
"docker:compose:down": "docker-compose down",
|
||||||
"docker:compose:logs": "docker-compose logs -f",
|
"docker:compose:logs": "docker-compose logs -f",
|
||||||
"docker:push": "bash scripts/docker-push.sh",
|
|
||||||
"docker:test": "bash scripts/docker-test.sh"
|
"docker:test": "bash scripts/docker-test.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
#
|
#
|
||||||
# Move the running stack to an image that is already published.
|
# Move the running stack to an image that is already published.
|
||||||
#
|
#
|
||||||
# bash scripts/deploy.sh # deploy whatever :dev points at
|
# bash scripts/deploy.sh # newest published vX.Y.Z
|
||||||
# bash scripts/deploy.sh --tag v0.9.4 # pin a specific published version
|
# bash scripts/deploy.sh --tag v0.9.4 # a specific published version
|
||||||
|
# bash scripts/deploy.sh --allow-floating --tag dev # chase a pointer, loudly
|
||||||
# bash scripts/deploy.sh --dry-run # show the change, touch nothing
|
# bash scripts/deploy.sh --dry-run # show the change, touch nothing
|
||||||
# bash scripts/deploy.sh --no-backup # skip the pre-deploy dump (say why)
|
# bash scripts/deploy.sh --no-backup # skip the pre-deploy dump (say why)
|
||||||
#
|
#
|
||||||
|
|
@ -36,15 +37,25 @@
|
||||||
# stack update does not remove one, but "does not normally" is not a reason to
|
# stack update does not remove one, but "does not normally" is not a reason to
|
||||||
# skip the cheap thing before the irreversible one.
|
# skip the cheap thing before the irreversible one.
|
||||||
#
|
#
|
||||||
# ## Why :dev is the default and a version tag is better
|
# ## THE POLICY: production always runs a numbered version
|
||||||
#
|
#
|
||||||
# Stack 58 pins `:dev`, which is a moving pointer. Deploying it means "whatever
|
# Set 2026-08-18. The stack pins `:vX.Y.Z` and never a floating tag.
|
||||||
# was published most recently", and after the fact nothing in the stack file
|
|
||||||
# says which image that was. Passing --tag pins an immutable version instead,
|
|
||||||
# which is what you want the day somebody asks what is running — and the day you
|
|
||||||
# need to roll back to something specific.
|
|
||||||
#
|
#
|
||||||
# Either way the digest is reported before and after, because that is the only
|
# `:dev` and `:latest` are pointers. A stack that follows one has no answer to
|
||||||
|
# "what is running" that survives the next push — the tag in the stack file says
|
||||||
|
# `dev` today and will still say `dev` after the image underneath it changes, so
|
||||||
|
# the file records a preference rather than a fact. Rollback is worse: there is
|
||||||
|
# nothing to roll back *to*, because the pointer only ever names now.
|
||||||
|
#
|
||||||
|
# So this refuses a floating tag. `--allow-floating` exists for the one case
|
||||||
|
# that is not a mistake — reproducing a fault on whatever `:dev` happens to be —
|
||||||
|
# and it says so loudly every time.
|
||||||
|
#
|
||||||
|
# **Stack 58 pins `:dev` as this is written.** Correcting that is the first
|
||||||
|
# deploy made under this policy, and it is why the default below resolves a
|
||||||
|
# version rather than reusing whatever the stack already names.
|
||||||
|
#
|
||||||
|
# The digest is reported before and after either way, because that is the only
|
||||||
# thing that cannot be moved underneath you.
|
# thing that cannot be moved underneath you.
|
||||||
|
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
@ -64,9 +75,10 @@ say() { printf '\033[1mdeploy:\033[0m %s\n' "$*" >&2; }
|
||||||
die() { printf '\033[1mdeploy:\033[0m %s\n' "$*" >&2; exit 1; }
|
die() { printf '\033[1mdeploy:\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
stop() { printf '\033[1mdeploy:\033[0m %s\n' "$*" >&2; exit 2; }
|
stop() { printf '\033[1mdeploy:\033[0m %s\n' "$*" >&2; exit 2; }
|
||||||
|
|
||||||
TAG="${DEPLOY_DEFAULT_TAG:-dev}"
|
TAG=""
|
||||||
DRY_RUN=""
|
DRY_RUN=""
|
||||||
NO_BACKUP=""
|
NO_BACKUP=""
|
||||||
|
ALLOW_FLOATING=""
|
||||||
|
|
||||||
while [ "$#" -gt 0 ]; do
|
while [ "$#" -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
@ -74,7 +86,8 @@ while [ "$#" -gt 0 ]; do
|
||||||
--tag=*) TAG="${1#--tag=}" ;;
|
--tag=*) TAG="${1#--tag=}" ;;
|
||||||
--dry-run) DRY_RUN="yes" ;;
|
--dry-run) DRY_RUN="yes" ;;
|
||||||
--no-backup) NO_BACKUP="yes" ;;
|
--no-backup) NO_BACKUP="yes" ;;
|
||||||
-h|--help) say "usage: bash scripts/deploy.sh [--tag <tag>] [--dry-run] [--no-backup]"; exit 0 ;;
|
--allow-floating) ALLOW_FLOATING="yes" ;;
|
||||||
|
-h|--help) say "usage: bash scripts/deploy.sh [--tag vX.Y.Z] [--dry-run] [--no-backup] [--allow-floating]"; exit 0 ;;
|
||||||
*) stop "unknown argument '$1'. Run --help." ;;
|
*) stop "unknown argument '$1'. Run --help." ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
|
|
@ -102,18 +115,66 @@ api() { curl -sk --max-time 30 -H "X-API-Key: $KEY" "$@"; }
|
||||||
REG_ENV="${RELEASE_REGISTRY_ENV:-$HOME/.openclaw/docker-registry.env}"
|
REG_ENV="${RELEASE_REGISTRY_ENV:-$HOME/.openclaw/docker-registry.env}"
|
||||||
if [ -r "$REG_ENV" ]; then set -a; . "$REG_ENV"; set +a; fi
|
if [ -r "$REG_ENV" ]; then set -a; . "$REG_ENV"; set +a; fi
|
||||||
|
|
||||||
|
tags_json=""
|
||||||
if [ -n "${FORGEJO_REGISTRY:-}" ] && [ -n "${FORGEJO_REGISTRY_TOKEN:-}" ]; then
|
if [ -n "${FORGEJO_REGISTRY:-}" ] && [ -n "${FORGEJO_REGISTRY_TOKEN:-}" ]; then
|
||||||
repo="${IMAGE#*/}"
|
repo="${IMAGE#*/}"
|
||||||
tags=$(curl -sS --max-time 20 -u "${FORGEJO_REGISTRY_USER}:${FORGEJO_REGISTRY_TOKEN}" \
|
tags_json=$(curl -sS --max-time 20 -u "${FORGEJO_REGISTRY_USER}:${FORGEJO_REGISTRY_TOKEN}" \
|
||||||
"https://${FORGEJO_REGISTRY}/v2/${repo}/tags/list" 2>/dev/null)
|
"https://${FORGEJO_REGISTRY}/v2/${repo}/tags/list" 2>/dev/null)
|
||||||
if [ -n "$tags" ]; then
|
fi
|
||||||
printf '%s' "$tags" | grep -q "\"${TAG}\"" \
|
|
||||||
|| die "${IMAGE}:${TAG} is not published. Nothing was changed.
|
# ---------------------------------------------------------------------------
|
||||||
Published tags: $(printf '%s' "$tags" | python3 -c 'import sys,json;print(", ".join(json.load(sys.stdin).get("tags") or []))' 2>/dev/null)"
|
# Resolve the tag. With none named, this is the newest published NUMBERED
|
||||||
else
|
# version — never `:dev`, and never "whatever the stack already says", because
|
||||||
say "WARNING: could not read the registry tag list, so it is not known whether"
|
# both of those are how a deployment stops being able to name itself.
|
||||||
say " ${TAG} is published. A missing tag becomes an outage here."
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ -z "$TAG" ]; then
|
||||||
|
[ -n "$tags_json" ] || stop "no tag given and the registry tag list could not be read, so
|
||||||
|
the newest published version is unknown. Name one explicitly:
|
||||||
|
bash scripts/deploy.sh --tag vX.Y.Z
|
||||||
|
Nothing was attempted."
|
||||||
|
|
||||||
|
TAG=$(printf '%s' "$tags_json" | python3 -c "
|
||||||
|
import sys, json, re
|
||||||
|
tags = json.load(sys.stdin).get('tags') or []
|
||||||
|
def key(t):
|
||||||
|
m = re.fullmatch(r'v(\d+)\.(\d+)\.(\d+)', t)
|
||||||
|
return tuple(int(x) for x in m.groups()) if m else None
|
||||||
|
vers = sorted([t for t in tags if key(t)], key=key)
|
||||||
|
print(vers[-1] if vers else '')
|
||||||
|
")
|
||||||
|
[ -n "$TAG" ] || stop "the registry holds no numbered vX.Y.Z tag to deploy — only
|
||||||
|
floating ones. Publish a version first: npm run release. Nothing was attempted."
|
||||||
|
say "no tag given; newest published version is ${TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Refuse a floating tag. See THE POLICY at the top.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if ! printf '%s' "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
if [ -z "$ALLOW_FLOATING" ]; then
|
||||||
|
die "'${TAG}' is not a numbered version, and production always runs one.
|
||||||
|
A stack pinned to a moving tag cannot say what it is running and cannot be
|
||||||
|
rolled back to anything, because the pointer only ever names now.
|
||||||
|
Deploy a version: bash scripts/deploy.sh --tag vX.Y.Z
|
||||||
|
Publish one first: npm run release
|
||||||
|
If you genuinely mean to chase a pointer, --allow-floating."
|
||||||
fi
|
fi
|
||||||
|
say "WARNING: --allow-floating. Deploying the moving tag '${TAG}'."
|
||||||
|
say " After this, the stack file will not record what is running."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Refuse to deploy a tag that is not published. Portainer would accept the
|
||||||
|
# stack file, fail to pull, and leave the container stopped — an outage caused
|
||||||
|
# by a typo, discovered by the site going down.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [ -n "$tags_json" ]; then
|
||||||
|
printf '%s' "$tags_json" | grep -q "\"${TAG}\"" \
|
||||||
|
|| die "${IMAGE}:${TAG} is not published. Nothing was changed.
|
||||||
|
Published tags: $(printf '%s' "$tags_json" | python3 -c 'import sys,json;print(", ".join(json.load(sys.stdin).get("tags") or []))' 2>/dev/null)"
|
||||||
|
else
|
||||||
|
say "WARNING: could not read the registry tag list, so it is not known whether"
|
||||||
|
say " ${TAG} is published. A missing tag becomes an outage here."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# docker-push.sh — Tag and push dev image to Forgejo registry
|
|
||||||
# Usage: ./scripts/docker-push.sh
|
|
||||||
# Requires: ~/.openclaw/docker-registry.env (chmod 600)
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
source ~/.openclaw/docker-registry.env
|
|
||||||
|
|
||||||
# Build image via docker compose
|
|
||||||
DOCKER_API_VERSION=1.44 docker compose build
|
|
||||||
|
|
||||||
# Tag and push dev
|
|
||||||
IMAGE_NAME="queue-north-website-queuenorth"
|
|
||||||
docker tag "${IMAGE_NAME}:latest" "${FORGEJO_REGISTRY}/null/queue-north-website:dev"
|
|
||||||
|
|
||||||
echo "$FORGEJO_REGISTRY_TOKEN" | docker login "$FORGEJO_REGISTRY" -u "$FORGEJO_REGISTRY_USER" --password-stdin
|
|
||||||
docker push "${FORGEJO_REGISTRY}/null/queue-north-website:dev"
|
|
||||||
|
|
||||||
docker logout "$FORGEJO_REGISTRY"
|
|
||||||
echo "✓ Pushed dev image"
|
|
||||||
|
|
@ -53,16 +53,21 @@
|
||||||
# would leave `dev` carrying a commit announcing a release that was never
|
# would leave `dev` carrying a commit announcing a release that was never
|
||||||
# published, and `.githooks/post-commit` would already have pushed it.
|
# published, and `.githooks/post-commit` would already have pushed it.
|
||||||
#
|
#
|
||||||
# ## Two tags, and only one of them is a record
|
# ## One tag, and it is a record
|
||||||
#
|
#
|
||||||
# `:vX.Y.Z` is immutable. Publishing over one is refused, because a running
|
# `:vX.Y.Z`, immutable. Publishing over one is refused, because a running stack
|
||||||
# stack would silently pull different code on its next recreate while the tag
|
# would silently pull different code on its next recreate while the tag said
|
||||||
# said nothing had changed.
|
# nothing had changed.
|
||||||
#
|
#
|
||||||
# `:dev` is a **moving pointer**, and Portainer stack 58 follows it. Moving it
|
# **It does not move `:dev`, deliberately.** An earlier version of this script
|
||||||
# is what makes a release deployable here at all, so this script moves it on
|
# did, because stack 58 followed that pointer. The policy changed on 2026-08-18:
|
||||||
# purpose — but that also means `:dev` is never evidence of what is running.
|
# production always runs a numbered version, so nothing deploys `:dev` any more
|
||||||
# `scripts/status.sh` reads the digest, and now the version label, for that.
|
# and moving it would publish a pointer that misrepresents what is running to
|
||||||
|
# anybody who reads it. `scripts/deploy.sh` refuses a floating tag outright.
|
||||||
|
#
|
||||||
|
# The `:dev` and `:latest` tags already in this registry are frozen where they
|
||||||
|
# are rather than deleted — the running container was created from `:dev`, and
|
||||||
|
# deleting the tag an existing deployment names is how a recreate fails to pull.
|
||||||
#
|
#
|
||||||
# ## What it deliberately does not do
|
# ## What it deliberately does not do
|
||||||
#
|
#
|
||||||
|
|
@ -85,7 +90,6 @@ cd "$(git rev-parse --show-toplevel)" || exit 1
|
||||||
|
|
||||||
IMAGE="${RELEASE_IMAGE:-dream.scheller.ltd/null/queue-north-website}"
|
IMAGE="${RELEASE_IMAGE:-dream.scheller.ltd/null/queue-north-website}"
|
||||||
SITE_URL="${RELEASE_SITE_URL:-https://queuenorth.com}"
|
SITE_URL="${RELEASE_SITE_URL:-https://queuenorth.com}"
|
||||||
DEV_TAG="${RELEASE_DEV_TAG:-dev}"
|
|
||||||
REGISTRY_ENV="${RELEASE_REGISTRY_ENV:-$HOME/.openclaw/docker-registry.env}"
|
REGISTRY_ENV="${RELEASE_REGISTRY_ENV:-$HOME/.openclaw/docker-registry.env}"
|
||||||
|
|
||||||
# Files carrying a version string. The Dockerfile is here because its
|
# Files carrying a version string. The Dockerfile is here because its
|
||||||
|
|
@ -178,7 +182,7 @@ next=$(cd "$work" && npm version --no-git-tag-version "$BUMP" 2>/dev/null | tr -
|
||||||
[ -n "$next" ] || die "npm rejected '$BUMP' as a version bump."
|
[ -n "$next" ] || die "npm rejected '$BUMP' as a version bump."
|
||||||
|
|
||||||
TAG="v${next}"
|
TAG="v${next}"
|
||||||
say "$current -> $next (publishing ${IMAGE}:${TAG}, and moving :${DEV_TAG})"
|
say "$current -> $next (publishing ${IMAGE}:${TAG})"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Refuse to move a tag that is already published.
|
# Refuse to move a tag that is already published.
|
||||||
|
|
@ -217,13 +221,12 @@ if [ -n "$DRY_RUN" ]; then
|
||||||
say " bash scripts/verify.sh"
|
say " bash scripts/verify.sh"
|
||||||
say " docker build --build-arg APP_VERSION=${next} -t ${IMAGE}:${TAG} ."
|
say " docker build --build-arg APP_VERSION=${next} -t ${IMAGE}:${TAG} ."
|
||||||
say " verify the image's org.opencontainers.image.version label reads ${next}"
|
say " verify the image's org.opencontainers.image.version label reads ${next}"
|
||||||
say " docker tag ${IMAGE}:${TAG} ${IMAGE}:${DEV_TAG}"
|
say " docker push ${IMAGE}:${TAG}"
|
||||||
say " docker push ${IMAGE}:${TAG} and ${IMAGE}:${DEV_TAG}"
|
|
||||||
say " git commit -m 'chore(release): ${TAG}' (post-commit then pushes)"
|
say " git commit -m 'chore(release): ${TAG}' (post-commit then pushes)"
|
||||||
say " git tag ${TAG} && git push origin ${TAG}"
|
say " git tag ${TAG} && git push origin ${TAG}"
|
||||||
say ""
|
say ""
|
||||||
say "It would NOT deploy. Portainer stack 58 keeps running the image it has"
|
say "It would NOT deploy, and it would NOT move :dev — production runs a"
|
||||||
say "until scripts/deploy.py is run."
|
say "numbered version. Afterwards: npm run deploy -- --tag ${TAG}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -304,11 +307,6 @@ if [ "$baked" != "$next" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The pointer stack 58 follows. Tagged after the version tag is verified, so
|
|
||||||
# :dev can never point at an image that failed its own check.
|
|
||||||
docker tag "${IMAGE}:${TAG}" "${IMAGE}:${DEV_TAG}" \
|
|
||||||
|| die "could not tag ${IMAGE}:${DEV_TAG}; nothing was pushed."
|
|
||||||
|
|
||||||
say "pushing ${IMAGE}:${TAG}…"
|
say "pushing ${IMAGE}:${TAG}…"
|
||||||
if ! docker push "${IMAGE}:${TAG}"; then
|
if ! docker push "${IMAGE}:${TAG}"; then
|
||||||
say "push failed. The image exists locally and the bump is in your working"
|
say "push failed. The image exists locally and the bump is in your working"
|
||||||
|
|
@ -317,14 +315,6 @@ if ! docker push "${IMAGE}:${TAG}"; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
say "moving ${IMAGE}:${DEV_TAG}…"
|
|
||||||
if ! docker push "${IMAGE}:${DEV_TAG}"; then
|
|
||||||
say "WARNING: ${TAG} IS published, but :${DEV_TAG} was not moved. Stack 58"
|
|
||||||
say " follows :${DEV_TAG}, so a deploy right now would redeploy the"
|
|
||||||
say " PREVIOUS image. Push it by hand before deploying:"
|
|
||||||
say " docker push ${IMAGE}:${DEV_TAG}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Commit last, by explicit path.
|
# Commit last, by explicit path.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -350,9 +340,10 @@ fi
|
||||||
say "released ${TAG}."
|
say "released ${TAG}."
|
||||||
say ""
|
say ""
|
||||||
say "This published an image. It did NOT deploy it — Portainer stack 58 is still"
|
say "This published an image. It did NOT deploy it — Portainer stack 58 is still"
|
||||||
say "running whatever it was running before. To move it:"
|
say "running whatever it was running before. To move it to this version:"
|
||||||
say ""
|
say ""
|
||||||
say " python3 scripts/deploy.py"
|
say " npm run deploy -- --tag ${TAG} --dry-run"
|
||||||
|
say " npm run deploy -- --tag ${TAG}"
|
||||||
say ""
|
say ""
|
||||||
say "and see docs/OPERATIONS.md, which covers what a redeploy costs: it recreates"
|
say "and see docs/OPERATIONS.md, which covers what a redeploy costs: it recreates"
|
||||||
say "the container and takes both public front doors down together."
|
say "the container and takes both public front doors down together."
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue