docs(release): organize installation and operator guides by task

This commit is contained in:
2026-10-14 10:14:35 +08:00
parent 16bcce3f04
commit e742c3543d
3 changed files with 618 additions and 667 deletions
+119 -92
View File
@@ -1,107 +1,134 @@
# Single-node delivery contract
# Single-node delivery
`contract.v1.json` describes the first pre-release installation interface. It is
separate from judging v1/v2 wire formats and never rewrites historical reports.
The reference target is Arch/CachyOS Linux x86_64-v3, glibc 2.44, systemd and cgroup
v2. Images and service libraries are bound to explicit file/catalog fingerprints;
support for other distributions requires a separately verified delivery profile.
The single-node profile packages the application, four judging runtimes and the
installation tools for offline use. `contract.v1.json` defines the installation
interface. Judging v1/v2 reports keep their existing formats.
The target has at least 4 GiB RAM and 12 GiB installation/data capacity. Judging
and problem-import concurrency remain one each. Configured service and execution
budgets, effective kernel limits and free capacity must also pass preflight; total
RAM alone does not prove enough capacity or verified execution isolation.
Preflight requires at least 12 GiB of filesystem capacity and derives required
free bytes from the verified application, protected runtime-image copies and
separate GNU legacy copy, plus 4 GiB of mutable-data/operation reserve. It does
not assume that a nominal 12-GiB filesystem exposes 12 GiB free after metadata.
Full installation and recovery acceptance is in progress. Validate a release on
an independent test node before using it for production.
Release payloads are directory bundles with a bounded manifest: at most 50,000
single-link regular files, 256 MiB per file, 4 GiB total and 8 MiB metadata. The
manifest SHA-256 is supplied separately by the operator. Links, extra members,
private paths and unexpected file changes are refused. Built payloads and real
configuration never belong in Git. Validation is read-only and does not claim
that any service has been installed, started or verified:
## Requirements
| Item | Required profile |
| --- | --- |
| OS | Arch/CachyOS, Linux x86_64-v3, glibc 2.44, systemd 262, cgroup v2 |
| Memory | At least 4 GiB, with enough free capacity for the configured services and tasks |
| Storage | At least 12 GiB filesystem capacity; preflight calculates the required free space |
| Concurrency | One judging job and one problem import |
| Dependencies | PostgreSQL 18.6 and the fixed broker runtime, prepared from verified offline materials |
Other distributions need a separately verified profile. Preflight checks the
effective kernel limits and available capacity as well as nominal RAM and disk
size. Its free-space calculation includes the application, protected runtime
images, the separate GNU legacy image and 4 GiB for mutable data and operations.
## Verify a bundle
Use a separately trusted checker and the manifest digest supplied with the release:
```sh
python3.13 -m scripts.release contract deploy/release/contract.v1.json
python3.13 -m scripts.release verify /path/to/bundle --manifest-sha256 SHA256_FROM_RECEIPT
```
Application versions live below `/opt/kf-onlinejudge/releases`; `current` selects
an explicitly installed version. Private operator/service configuration lives in
`/etc/kf-onlinejudge` and `/etc/kf-judge`, mutable application data under
`/var/lib/kf-onlinejudge`, and protected Sandbox state/images under
`/var/lib/kf-judge`. The delegated task cgroup is separate from the Root factory's
own supervision cgroup. Application processes do not initialize tables on start;
Backend Alembic remains the only migration entry point.
All application services and the separate delegated task subtree live beneath
`kfonlinejudge.slice`: 3840 MiB memory, no swap, 384 processes/threads and two CPU
equivalents. The remaining physical RAM is reserved for the kernel/minimal OS.
The task subtree is `/sys/fs/cgroup/kfonlinejudge.slice/kf-judge-tasks`, a sibling
of the Root factory supervision unit under that same total node budget. Its
installation template does not prove the effective limits; actual service and
task limits are read back in the independent acceptance environment.
A bundle contains at most 50,000 single-link regular files, 256 MiB per file,
4 GiB in total and 8 MiB of metadata. Verification rejects links, extra members,
private paths and changed bytes. Keep bundles and real configuration outside Git.
Database login identities are separate for migration, API/outbox, Coordinator,
problem import and local identity operations. Rust Worker and Root factory do not
hold database credentials. Root factory has no MQ/platform/SSH/GitHub identity;
privileged execution remains inside Sandbox. Public and admin browser traffic
share the HTTPS site; execution-control interfaces remain internal.
Verification is read-only. Installation, running services and execution isolation
each need their own acceptance evidence.
Upgrade and restore must retain immutable problem versions, submitted source,
audit and original judging reports. A consistent backup cut has a zero confirmed
data-loss target; restoring from already prepared release/backup materials has a
30-minute recovery target, including service health and actual four-language
verification. These are acceptance targets, not results inferred from this file.
Installation/service/restore evidence is required before this pre-release can be
treated as an accepted delivery; the contract itself does not deploy production.
## Directories and authority
The node input for `generate-configuration` has exactly `schema_version: 1`,
`origin` (one HTTPS origin), `worker_id` (canonical nonzero UUID), and
`measurement_nonce` (null or a fresh operator-generated SHA-256-sized nonce).
It is a 0600 single-link file. Endpoint input is a separate 0600 JSON object
with exactly `schema_version: 1`, `database` and `amqp`: database keys are
`api`, `coordinator`, `import`, `migration`, `identity`; broker keys are `outbox`
and `worker`. Login names and numeric loopback/socket locations are validated by
the tool. Every endpoint requires its own operator-supplied private credential;
there is no working example password or shared default. Import uses the reachable
`/run/postgresql` Unix socket; all other control connections use numeric loopback.
| Location | Contents |
| --- | --- |
| `/opt/kf-onlinejudge/releases` | Installed application versions |
| `current` | Explicitly selected application version |
| `/etc/kf-onlinejudge` and `/etc/kf-judge` | Private operator and service configuration |
| `/var/lib/kf-onlinejudge` | Mutable application data |
| `/var/lib/kf-judge` | Protected Sandbox state and runtime images |
Generated admission uses the actual verified catalogs for C17/C++20, CPython
3.13.16 and OpenJDK 21.0.12.1+1-LTS and the unchanged public per-language budgets.
The protected GNU legacy image is a separate directory: it retains the original
v1 header and identical verified GNU executable/library bytes, while the three
v2 catalog identities remain untouched. Shared Worker/task UID/GID pool and
execution/prefetch/import slots remain one. No canonical judging report or
historical runtime identity is converted by the installer.
Backend Alembic is the only schema migration entry point. Database identities are
separate for migration, API/outbox, Coordinator, problem import and local account
operations. Rust Worker has no database identity; the Root factory has no
database, MQ, platform, SSH or GitHub credentials. Sandbox owns privileged
execution. Public and admin pages share the HTTPS origin; control interfaces stay
internal.
Comparability defaults to unavailable (`measurement_epoch=null`). A trusted
operator may explicitly use `--attest-measurement` only after actual node/service
limits and the intended execution environment have been verified. The attestation
binds a fresh nonce, worker, source, exact images, budgets, CPU/microcode, kernel
and node scheduling snapshot. `check-configuration` refuses an old binding when
those values change; it does not manufacture replacement metrics for old reports.
Before any service resume after such a change, drain execution, generate/install
a new complete private group with a new nonce and independently revalidate the
environment. Raw configuration checks are not performance or isolation evidence.
Configuration batches have independent hash-bound `cfg-...` identities; files
and receipts contain both the source release and batch identifier. A fresh
nonce/identity can therefore be installed for the same source without replacing
an old configuration. Preparation never selects the new batch automatically.
`activate` requires an explicit `--configuration-id`, a fully validated group,
all application units inactive, stopped control ports and no retained task
resources. Root journals and all old batches remain intact. It binds the code
pointer and private batch in `/etc/kf-onlinejudge/active.json`; `check-active`
refuses inconsistent pointers, stale hardware bindings or changed bytes.
Interruption between the two atomic pointer writes requires operator review;
no service starts or claims readiness during that mismatch. A stopped stale
batch can be retired without accepting it for execution under a changed kernel.
Application services and the task subtree share `kfonlinejudge.slice`, capped at
3840 MiB, zero swap, 384 processes/threads and two CPU equivalents. The remaining
physical memory is reserved for the kernel and minimal OS. Tasks use
`/sys/fs/cgroup/kfonlinejudge.slice/kf-judge-tasks`, separate from the Root
supervisor's cgroup. Read back service and task limits on the target node.
The installation entry points are available with `python3.13 -m scripts.release
--help`. Use them in order: verify/preflight, install-files, generate-configuration,
install-configuration, explicit DB identity provision, controlled Backend Alembic
and account CLI operations, then activate and the separately verified bounded
service lifecycle. These operations never contact a package index and do not
install onto a business host on their own. OS dependencies (systemd 262, matching
glibc ABI bytes, PostgreSQL 18.6 and the fixed broker runtime) must already be
present from separately verified offline materials.
## Prepare configuration
Supply two 0600 single-link JSON files to `generate-configuration`:
| Input | Fields |
| --- | --- |
| Node | `schema_version: 1`, `origin`, `worker_id`, `measurement_nonce` |
| Endpoints | `schema_version: 1`, `database`, `amqp` |
`origin` is one HTTPS origin. `worker_id` is a canonical nonzero UUID.
`measurement_nonce` is null or a fresh operator-generated SHA-256-sized nonce.
The database object has `api`, `coordinator`, `import`, `migration` and `identity`
keys; the broker object has `outbox` and `worker` keys. Supply a different private
credential for every endpoint. Import uses `/run/postgresql`; other control
connections use numeric loopback. The tool checks login names and locations.
The generated admission profile binds C17/C++20, CPython 3.13.16 and OpenJDK
21.0.12.1+1-LTS to verified catalogs and the existing per-language budgets. The
GNU legacy image retains its v1 header and verified executable/library bytes in
a separate directory. Worker, task identities and execution/prefetch/import
slots remain one. Historical reports and runtime identities remain unchanged.
## Select a version
Configuration batches have content-bound `cfg-...` identities. Preparation keeps
earlier batches and does not select a new one. `activate` requires an explicit
`--configuration-id`, a fully validated batch, inactive application units,
stopped control ports and no retained task resources.
Selection writes the code pointer and `/etc/kf-onlinejudge/active.json`. A crash
between those atomic writes can leave a mismatch; stop and inspect it before
starting services. `check-active` rejects inconsistent pointers, changed bytes
and stale hardware bindings. Root journals and earlier batches remain intact.
A stopped stale batch can be retired without accepting it under a changed kernel.
## Performance comparability
Comparability defaults to unavailable (`measurement_epoch=null`). Use
`--attest-measurement` after verifying the node's limits and execution environment.
It binds a fresh nonce, worker, source, images, budgets, CPU/microcode, kernel and
node scheduling snapshot. `check-configuration` rejects a changed binding.
After an environment change, drain execution, install a complete batch with a new
nonce and revalidate before resuming. Earlier reports retain their original
measurements. Configuration validation alone does not prove comparable performance
or execution isolation.
## Installation order
Use `python3.13 -m scripts.release
--help` for the command interface. Prepare the OS dependencies and dedicated data
volumes, then follow this order:
1. Verify the bundle and run preflight.
2. Install files and runtime images.
3. Generate and install a private configuration batch.
4. Activate that batch while services and tasks are stopped.
5. Install the journal profile and selected dependency snapshots.
6. Initialize PostgreSQL, provision DB identities, run Backend Alembic and create
application accounts; stop the initialization database before installing units.
7. Install ordinary units, the Root factory and the HTTPS snapshot/TLS material.
8. Start the bounded service lifecycle and verify readiness and actual execution.
The tools use offline materials and do not contact a package index.
See the [operator command reference](../../scripts/release/README.md) for service,
HTTPS, backup and recovery operations. Upgrades and restores must preserve
immutable problem versions, submitted source, audit and original judging reports.
A confirmed consistent backup cut has a zero data-loss target. Recovery from
prepared release and backup materials has a 30-minute target, including health
and four-language verification. Both targets require measured acceptance results.