Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
401 changes: 347 additions & 54 deletions .github/workflows/containers.yml

Large diffs are not rendered by default.

18 changes: 12 additions & 6 deletions .trellis/spec/core/docs/privacy-and-publication.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,19 @@ artifacts. `internal/containercontract/contract_test.go` protects
local references, private docs, and packet captures. Do not widen the Docker
context or add a debug `COPY` that reintroduces them.

Public release assets may include the fixed application images,
strongSwan-plugin package plus corresponding source/manifest, and required
Mihomo corresponding source when the locked release workflow produces them.
Public release assets may include the fixed application images, the
allowlisted versioned Compose deployment archive and checksum, its three-image
digest manifest, the strongSwan-plugin package plus corresponding
source/manifest, and required Mihomo corresponding source when the locked
release workflow produces them. The deployment archive contains only the
literal-tag Compose file, three-key `.env.example`, reviewed host scripts,
README, version metadata, root license, and third-party notices; it never
contains `.env`, source, Git metadata, runtime data, logs, or private evidence.
The allowed set and license/provenance are defined by `Dockerfile`,
`third_party/**`, `packaging/strongswan-plugins/**`,
`THIRD_PARTY_NOTICES.md`, and `.github/workflows/containers.yml`. Container
data, site config, logs, or credentials are never release assets.
`third_party/**`, `packaging/container/**`,
`packaging/strongswan-plugins/**`, `THIRD_PARTY_NOTICES.md`, and
`.github/workflows/containers.yml`. Container data, site config, logs, or
credentials are never release assets.

## Publication Procedure

Expand Down
116 changes: 116 additions & 0 deletions .trellis/spec/core/infra/build-and-generated-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,122 @@ corresponding-source publication. When changing a path filter, release asset,
or Make target, verify the relevant workflow still runs for every source that
can affect its output.

## Scenario: Versioned container deployment release

### 1. Scope / Trigger

Apply this contract when changing the production Compose template, deployment
bundle contents, GHCR image metadata, container workflow triggers, Release
assets, or production installation/upgrade instructions.

### 2. Signatures

- Script:
`scripts/release/build-container-release-bundle.sh <vX.Y.Z> <40-lowercase-hex-commit> <non-negative-source-date-epoch> <existing-non-symlink-output-directory>`.
- Make wrapper:
`make container-release-bundle RELEASE_TAG=<vX.Y.Z> RELEASE_COMMIT=<40-hex> RELEASE_SOURCE_DATE_EPOCH=<epoch> RELEASE_OUTPUT_DIR=<directory>`.
- Source validation:
`make container-config CONTAINER_IMAGE_TAG=<development-tag>`.
- Production installation has no image-tag input. Its only editable keys are
`SIMPLUS_HTTP_PORT`, `SIMPLUS_CONTROLLER_PORT`, and `SIMPLUS_DEVICE_GID`.

### 3. Contracts

- The source `compose.yaml` requires an explicit `SIMPLUS_IMAGE_TAG` and is a
development template. The builder replaces exactly five controlled
placeholders with one strict version tag.
- `simplus-compose-<tag>-linux-amd64.tar.gz` contains exactly one root
directory with `.env.example`, `LICENSE`, `README.md`,
`THIRD_PARTY_NOTICES.md`, `VERSION`, `check-container-host.sh`,
`compose.yaml`, and `prepare-container-host.sh`. Scripts are `0755`; all
other files are `0644`; archive owner/group are numeric zero; order, mtime,
and gzip header are deterministic.
- The checksum asset is
`simplus-compose-<tag>-linux-amd64.tar.gz.sha256` and names only the archive
basename.
- Pull requests and `workflow_dispatch` build the bundle and all three
`linux/amd64` targets with `push: false`. Only an upstream push event whose
ref name matches `^v[0-9]+\.[0-9]+\.[0-9]+$` may publish.
- Tag publication creates or reuses a published Pre-release, publishes the
deployment and corresponding-source assets before the image matrix, then
publishes `control`, `agent`, and `netd`. Same-ref runs are serialized. Each
matrix item reuses an existing tag only after its commit, platform, and OCI
labels match; otherwise it stages new content by digest, rechecks that the
version tag is absent, and promotes the digest. It never moves an existing
version tag or emits `latest`, `main`, or branch tags.
- Existing same-name Release assets are accepted only when byte-identical.
Before upload, the source job checks the exact seven-file public asset
allowlist and requires every entry to be a regular `0644` file. The final
`simplus-images-<tag>.json` records `version`, the full `commit`,
`platform: linux/amd64`, and exactly three target/reference/digest entries.
- `contents: write` and `packages: write` remain job-scoped. Image builds keep
the OCI source/version/revision/license labels, SBOM, provenance, and
per-target cache scopes.

### 4. Validation & Error Matrix

| Condition | Required result |
| --- | --- |
| tag is not strict `vX.Y.Z`, commit is not 40 lowercase hex, epoch is invalid, or output directory is missing/symlinked | bundle build fails before publishing an archive |
| source Compose has a missing/extra placeholder, unexpected image, `build`, or `latest` | bundle build fails closed |
| Release asset build or corresponding-source validation fails | no Pre-release asset publication and no image push |
| an existing asset has different bytes | fail; never clobber it |
| an existing image tag has different commit/platform/OCI metadata | fail before push; never move it |
| a version tag appears after digest staging | fail before promotion; never overwrite the tag |
| one image build/push fails | Release remains a Pre-release and the digest manifest is not published |
| digest artifact is missing, duplicated, malformed, or not `sha256:<64-lowercase-hex>` | manifest publication fails |
| first GHCR packages are still private | do not claim anonymous installation; owner must make all three public and visibility cannot be reverted to private |
| a code change is needed after tagging | do not move/reuse the tag; publish the next patch version |

### 5. Good / Base / Bad Cases

- Good: a strict tag first publishes deterministic install/source assets,
then three `linux/amd64` images, then a complete digest manifest; an
unauthenticated client can inspect and pull all three after owner visibility
approval.
- Base: a PR or manual run renders the source and release Compose files and
builds each target without registry login or publication.
- Bad: let production users set `SIMPLUS_IMAGE_TAG`, fall back to local builds,
publish a rolling tag, overwrite a differing asset, mark the candidate
stable, or treat an image rollback as a database-safe downgrade.

### 6. Tests Required

- `internal/containercontract/release_bundle_test.go` asserts strict metadata,
two-build byte identity, exact allowlist/modes/owner/mtime, checksum,
licenses, three literal image references, and absence of development/private
inputs.
- Container workflow contracts assert the upstream/strict-tag gate,
PR/manual `push: false`, source-before-image dependency, amd64, OCI labels,
SBOM/provenance, and immutable digest manifest shape.
- Run `make check-container-files`, `go test ./internal/containercontract`,
`make container-config CONTAINER_IMAGE_TAG=dev`, `make check-docs`,
`make lint`, `make test`, `make security`, and `git diff --check` before the
PR is merged.
- Before tagging, run a redacting secret scan over the worktree, full Git
history, and unpacked final assets, then review the exact asset/license list.
- After publication, verify checksum, OCI metadata, manifest digests, anonymous
inspect/pull, and extracted `docker compose config --quiet`/`pull`. These
checks do not authorize host preparation, `compose up`, or HIL.

### 7. Wrong vs Correct

Wrong: make a source checkout or floating image input part of production:

```bash
git clone https://github.com/leonfox28/simplus
SIMPLUS_IMAGE_TAG=latest docker compose up -d
```

Correct: verify a versioned bundle, retain its literal image references, and
pull before any separately authorized start:

```bash
sha256sum -c simplus-compose-v0.1.0-linux-amd64.tar.gz.sha256
tar -xzf simplus-compose-v0.1.0-linux-amd64.tar.gz
docker compose -f simplus-compose-v0.1.0-linux-amd64/compose.yaml pull
```

## Avoid

- Floating tool/image tags or `latest` in a reproducible path.
Expand Down
25 changes: 19 additions & 6 deletions .trellis/spec/core/infra/containers-and-privileges.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,19 @@ checks that boundary, active or enabled legacy/development service conflicts,
module loading and loads `option`, though it does not write a USB ID. Run it
only with explicit deployment authorization.

`make container-build` builds the three images and `make container-config`
renders Compose. Starting Compose maps host devices and creates runtime
network objects; it is deployment/HIL-adjacent and is not authorized merely by
a request to lint or build container files. The host check must reject an
active or enabled legacy production unit or `simplus-agent-dev` before Compose
can own the modem and ports (`docs/installation.md`).
Production administrators download a versioned GitHub Pre-release deployment
bundle and pull its literal-tag GHCR images. They do not clone the source tree
or build images locally. The root `compose.yaml`, `make container-build`, and
`make container-config CONTAINER_IMAGE_TAG=dev` remain source-development
validation interfaces only; the source Compose must not default to a production
version.

Starting Compose maps host devices and creates runtime network objects; it is
deployment/HIL-adjacent and is not authorized merely by a request to lint,
build, package, publish, inspect, or pull container artifacts. The host check
must reject an active or enabled legacy production unit or
`simplus-agent-dev` before Compose can own the modem and ports
(`docs/installation.md`).

## Scenario: Container-only production and legacy service exclusion

Expand All @@ -121,11 +128,17 @@ registration path.
- Container-only driver registration: `simplus-agent register-option-driver`,
invoked by `containers/agent-entrypoint.sh`.
- No supported native production bundle/build/install/uninstall command exists.
- Container release bundle:
`scripts/release/build-container-release-bundle.sh <vX.Y.Z> <40-lowercase-hex-commit> <source-date-epoch> <existing-output-directory>`.

### 3. Contracts

- Compose is the only production deployment owner; native Go/Node development,
Simulator and `simplus-agent-dev` do not become production alternatives.
- The production installation input is the versioned Release bundle. Its
Compose contains literal `ghcr.io/leonfox28/simplus-{control,agent,netd}:vX.Y.Z`
references; only the source-development Compose accepts
`SIMPLUS_IMAGE_TAG`.
- Host preparation may install the fixed `option` module-load configuration and
run `modprobe option`; it never writes a USB ID.
- Host preflight rejects any active **or enabled**
Expand Down
5 changes: 5 additions & 0 deletions .trellis/tasks/08-21-ghcr-release-install/check.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"file":".trellis/spec/core/infra/containers-and-privileges.md","reason":"Verify no drift in service topology, UID/capability/socket/mount boundaries, host preparation authorization, or HIL scope."}
{"file":".trellis/spec/core/infra/build-and-generated-files.md","reason":"Verify deterministic bundle/release behavior, Make/CI alignment, tag policy, and source-before-image publication."}
{"file":".trellis/spec/core/docs/documentation-ownership.md","reason":"Verify the canonical install/decision/status documents were updated without duplicated manuals or false evidence claims."}
{"file":".trellis/spec/core/docs/privacy-and-publication.md","reason":"Verify bundle and release assets contain only allowed public material and that publication gates are complete."}
{"file":".trellis/tasks/08-21-ghcr-release-install/research/current-state.md","reason":"Cross-check implementation and final claims against the pre-change repository/GitHub state."}
58 changes: 58 additions & 0 deletions .trellis/tasks/08-21-ghcr-release-install/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Design: GHCR release installation

## Architecture

仓库根 `compose.yaml` 继续作为权限/服务拓扑的唯一源码模板,但镜像 tag 改为必须显式提供的开发模板变量,避免源码 checkout 默认伪装成某个已发布生产版本。Release 构建器只替换这一个受控占位符,生成写死 `vX.Y.Z` 的发行 Compose;端口和设备 GID 仍由用户 `.env` 提供。

生产数据继续相对发行 Compose 位于 `./data/core` 和 `./data/agent`。部署包更新只覆盖受管 Compose、脚本、示例和文档,不包含或删除 `.env`、`data/` 或运行 volume。

## Deployment bundle contract

新增 `scripts/release/build-container-release-bundle.sh`,接口为:

```text
build-container-release-bundle.sh <vX.Y.Z> <40-hex-commit> <source-date-epoch> <output-directory>
```

脚本必须:

1. 严格验证 tag、commit、epoch 和既有输出目录;
2. 在私有临时目录创建 `simplus-compose-<tag>-linux-amd64/`;
3. 将源码 Compose 的唯一 image-tag 占位符替换为字面版本 tag,并断言五个服务只引用预期三张 GHCR 镜像;
4. 复制 `.env.example`、两条宿主脚本、bundle README、VERSION、根许可证和 notices,固定脚本为 `0755`、其余为 `0644`;
5. 使用排序条目、固定 mtime、numeric owner/group 0 和 `gzip -n` 生成确定性归档;
6. 在输出目录写入归档及只引用其 basename 的 `.sha256` 文件。

bundle README 仅给出 SHA 校验后的显式命令,不提供 `curl | sh` 或隐式 sudo。`.env.example` 不含秘密或镜像版本,升级时用户保留自己的 `.env`。

## GitHub Actions data flow

1. `metadata/compose-contract` 对所有触发器运行;tag ref 必须匹配 `^v[0-9]+\.[0-9]+\.[0-9]+$`。
2. PR/手动触发用合成 CI metadata 构建部署包和三个 target,`push: false`。
3. tag 触发先构建 strongSwan/Mihomo 对应源码和部署包,并上传临时 Actions artifacts。
4. 发布任务创建或更新同 tag GitHub **Pre-release**,上传部署包、checksum 和全部对应源码;版本化资产存在时只接受内容相同的幂等重跑,不静默覆盖不同内容。
5. 镜像矩阵在来源资产发布成功后登录 GHCR。同 ref workflow 串行执行;每个 target 若已
存在同 tag,只在 commit、`linux/amd64` 和 OCI 标签完全匹配时复用其 digest;若不存在,
先用 BuildKit 按 digest 推送无 tag 内容,再次确认版本 tag 不存在后用 `imagetools create`
提升,并核对提升后的 digest 与 staged digest 完全相同。每个矩阵项把最终 digest 写成
小型 artifact。
6. 汇总任务验证三个 target/digest 唯一且完整,生成稳定排序的 `simplus-images-<tag>.json` 并上传到同一 Pre-release。

`packages: write` 只授予推送镜像的 job,`contents: write` 只授予发布 Release 资产的 job。PR 来源永不获得发布路径。保留现有 action commit pin、BuildKit cache、SBOM 和 provenance。

## First release and visibility

代码经 PR 合并后,仅给合并提交创建 `v0.1.0`。GitHub 首次创建的个人 GHCR package 默认 private,workflow 无法替代所有者的不可逆 visibility 决策;三个镜像完成后由所有者逐个改为 public,再从未登录 registry 的环境执行 inspect/pull 验证。

基础设施瞬时错误可对同一 tag/commit 重跑。若修复需要改代码,不移动 `v0.1.0`,而是在 main 修复后使用 `v0.1.1`。首发保持 Pre-release,clean-VM 验收由独立任务完成。

同 ref concurrency 消除 workflow 自身重跑竞争;发布期间其他主体不得持有或使用
`packages: write` 改写同 tag。GHCR 在本流程中没有 compare-and-set 接口,因此版本 tag
不可移动性还依赖该写权限治理,发布后继续以 digest manifest 和匿名 inspect 核对。

## Compatibility and failure boundaries

- 不改变 Compose 服务、权限、UID、socket 或持久数据布局,现有容器合同测试仍权威。
- 发布 bundle 引用版本 tag;digest JSON 只用于审计和发布后核对,不改变运行引用。
- 新 bundle 可替换旧受管文件并重建容器,但数据库只自动向前迁移。降级必须另外确认 schema 兼容或恢复备份,本文不新增 downgrade 接口。
- Release 发布/镜像 pull 验证不授权 `docker compose up`、宿主内核修改或真实硬件访问。
5 changes: 5 additions & 0 deletions .trellis/tasks/08-21-ghcr-release-install/implement.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"file":".trellis/spec/core/infra/containers-and-privileges.md","reason":"Production Compose topology, fixed identities, mounts, capabilities, lifecycle, and safe validation boundaries."}
{"file":".trellis/spec/core/infra/build-and-generated-files.md","reason":"Make target semantics, reproducible release inputs, generated ownership, workflow alignment, and corresponding-source obligations."}
{"file":".trellis/spec/core/docs/documentation-ownership.md","reason":"Canonical owners for installation, ADR, active plan, handoff, and public status wording."}
{"file":".trellis/spec/core/docs/privacy-and-publication.md","reason":"Release asset allowlist, private-data exclusions, redacted secret scanning, and publication review requirements."}
{"file":".trellis/tasks/08-21-ghcr-release-install/research/current-state.md","reason":"Task-specific repository and remote publication evidence gathered before implementation."}
Loading
Loading