mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-04 12:32:15 -04:00
ci(server): validate and publish versioned GHCR images
This commit is contained in:
1 parent
d428357c8b
commit
1ec79ae594
5 files changed
+1481
-1
No files matched your search
+18
-1
@@ -163,14 +163,31 @@ parents). The image has no root entrypoint that changes ownership.
|
||||
`OpenNest.Server/server.env.example` to a deployment directory, save the env file
|
||||
under a local name such as `server.env`, and set:
|
||||
|
||||
- `OPENNEST_SERVER_IMAGE`: a tested version or, preferably, a digest.
|
||||
- `OPENNEST_SERVER_IMAGE`: `ghcr.io/ajisaacs/opennest-server:X.Y.Z` or, preferably,
|
||||
`ghcr.io/ajisaacs/opennest-server@sha256:<verified-manifest-digest>` from the
|
||||
successful clean pulled-image verification job. Do not substitute Docker's
|
||||
store-dependent image `Id` or the config blob digest for this verified registry
|
||||
manifest digest; see [server image releases](releasing.md#server-image-separate-from-windows-candidates).
|
||||
- `OPENNEST_BIND_ADDRESS`: `127.0.0.1` for testing on the host; this host's
|
||||
trusted LAN address for shop PCs. Do not use `0.0.0.0`. The bind address is only
|
||||
one layer: verify that the network/firewall admits only trusted clients.
|
||||
- `OPENNEST_HOST_PORT` (default 8090) and `OPENNEST_DATA_VOLUME` (default `opennest-data`).
|
||||
|
||||
Publication requires an existing verified private package; first-package
|
||||
initialization needs separate owner authorization outside the release workflow.
|
||||
An authorized deployment operator must obtain a
|
||||
`read:packages` token out of band and log in on the deployment host using
|
||||
`docker login ghcr.io -u <github-user> --password-stdin`, piping the token from a
|
||||
secret manager or a protected prompt (never a token literal in shell history).
|
||||
Keep Docker's credentials protected on that host; do not put credentials in
|
||||
`server.env`, Compose, the image, or source control. Login/pull success does not
|
||||
authorize making the package public. Until an approved image has passed the
|
||||
published-digest smoke, the reference below is only a template, not an available
|
||||
verified image.
|
||||
|
||||
```sh
|
||||
compose="docker compose --env-file server.env -f compose.server.yaml"
|
||||
$compose pull || { printf 'STOP: image pull failed\n' >&2; exit 1; }
|
||||
$compose up -d
|
||||
$compose ps # STATUS shows (healthy)
|
||||
curl --fail http://<bind-address>:<port>/healthz # {"status":"ok"}
|
||||
|
||||
@@ -56,3 +56,73 @@ and discovers posts. It does not replace interactive CAD/nesting acceptance,
|
||||
physical CNC/serial verification, GPU execution, or real-model ONNX accuracy.
|
||||
Packages are unsigned; code signing and a fuller packaged-post execution test
|
||||
remain follow-up hardening.
|
||||
|
||||
## Server image (separate from Windows candidates)
|
||||
|
||||
`Server image` validates relevant PRs and `master` pushes without registry login,
|
||||
package-write permissions, or registry upload. It builds/tests with .NET 8, records pinned
|
||||
SDK/runtime base digests, builds a single-platform `linux/amd64` image without cache
|
||||
or attestations (`--provenance=false --sbom=false`), and runs the real-client
|
||||
container persistence/backup/restore smoke. A Windows `v*` tag build alone never
|
||||
publishes a server image.
|
||||
|
||||
Publication requires owner-approved release intent: a **published GitHub Release**
|
||||
or an explicit `Server image` dispatch **from `master`**, naming an existing strict
|
||||
`vX.Y.Z` tag (no prerelease, leading zero, or extra suffix). The tag must resolve to
|
||||
a full commit already on `master`. For a published Release, that peeled commit
|
||||
must also equal the event's full `GITHUB_SHA`; a retargeted tag is refused. Manual
|
||||
dispatch intentionally resolves the approved existing tag, not the workflow's
|
||||
`master` SHA. The job checks out that exact source, reruns all
|
||||
server/image gates, and pushes the *same smoked image* to
|
||||
`ghcr.io/ajisaacs/opennest-server:X.Y.Z` and `:sha-<full-commit>` only. Both tags must
|
||||
be absent; retries after even a partial upload stop instead of overwriting. There
|
||||
are no `latest`, major, or minor aliases. Do not dispatch just to test publication.
|
||||
|
||||
Publication requires an **existing owner-verifiable private package**, linked to
|
||||
`ajisaacs/OpenNest`, with repository Actions access (normally inherited from the
|
||||
link). The job uses only its scoped `GITHUB_TOKEN`. All package metadata 404s are
|
||||
refused: GitHub can mask an inaccessible private package as `Not Found`. Opaque
|
||||
registry token success, an empty tag list, or a registry 404 proves neither package
|
||||
absence nor privacy and cannot override the metadata check. Explicit reduced
|
||||
scope and failed registry read access also stop the job. The job rechecks private
|
||||
association after upload.
|
||||
|
||||
First-package initialization is a separately owner-authorized prerequisite
|
||||
outside this workflow. Until the private package, repository link, and Actions
|
||||
access can be verified, publication remains blocked while read-only validation
|
||||
can pass. There
|
||||
is no automatic bootstrap, extra PAT, absence assertion, or bypass setting. A
|
||||
local credential's Packages API 403 proves neither absence nor privacy. No
|
||||
workflow changes visibility. Public availability needs separate owner approval
|
||||
and a later anonymous-pull check; it is not approved here.
|
||||
|
||||
The local gate reads `docker image save` to hash the actual config and verify each
|
||||
layer against its ordered uncompressed rootfs digest. It rejects unexpected
|
||||
indexes/attestations. Smoke and tagging use the inspected immutable Docker ID,
|
||||
not a mutable local tag, and readback must match that pre-smoke identity. Success
|
||||
requires exact readback of both manifest digests, `linux/amd64`, OCI labels, config
|
||||
bytes, and the full layer/rootfs chain identifying that same smoked image.
|
||||
A **separate clean job** pulls the returned manifest digest, independently checks
|
||||
its saved config/layers, and repeats the real-client smoke using tools checked out
|
||||
from that source commit. Only its pass verifies the image.
|
||||
|
||||
Artifacts retain TRX, explicit logs, and safe provenance. Docker's store-dependent
|
||||
`Id`, the config blob digest, and the registry manifest digest are separate values:
|
||||
classic Docker may use the config digest as `Id`, while containerd may use a
|
||||
manifest or index digest. Disabling attestations does not make `Id` a config
|
||||
digest. Temporary image-save archives are deleted, never uploaded. Artifacts
|
||||
never include smoke state JSON, databases, nest archives, Docker auth configs, or
|
||||
credentials.
|
||||
A failed publication/verification is not a release acceptance; inspect its logs
|
||||
and any partial tags with the owner before choosing a new approved version.
|
||||
|
||||
For a local read-only check of the release guards:
|
||||
|
||||
```sh
|
||||
python3 -m unittest discover -s scripts -p test_server_image_release.py -v
|
||||
```
|
||||
|
||||
Deployment is deliberately separate; use the verified digest and the
|
||||
[private pull/Compose procedure](nest-storage.md#deploying-with-compose). No
|
||||
publication, release creation, deployment, or visibility change is implied by
|
||||
adding or validating this workflow.
|
||||
Reference in new issue
Block a user