177 lines
8.8 KiB
Markdown
177 lines
8.8 KiB
Markdown
# Single-node delivery
|
|
|
|
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.
|
|
|
|
Full installation and recovery acceptance is in progress. Validate a release on
|
|
an independent test node before using it for production.
|
|
|
|
## 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
|
|
```
|
|
|
|
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.
|
|
|
|
Verification is read-only. Installation, running services and execution isolation
|
|
each need their own acceptance evidence.
|
|
|
|
## Directories and authority
|
|
|
|
| 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 |
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
## 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.
|
|
|
|
Startup dispatches PostgreSQL, RabbitMQ and the Root factory together. Ordinary
|
|
services follow in batches of at most two, limiting concurrent full-bundle
|
|
startup checks within the two-CPU node budget; HTTPS follows those batches.
|
|
Each stage waits for every member's actual manager state and resource/identity
|
|
checks before continuing.
|
|
Every service retains its complete startup verification and its original unit
|
|
and observation deadlines, measured from that service's dispatch. Admission stays
|
|
closed until both normal readiness checks confirm the complete node. Drain and
|
|
stop retain their ordered owner and cleanup checks.
|
|
|
|
Installer automation can run the same setup CLI entry points in one bounded
|
|
Python operation. Use the protected delivery interpreter and its normal Python
|
|
environment, as the trusted Root operator:
|
|
|
|
```python
|
|
from scripts.release.setup_session import SetupSession
|
|
|
|
with SetupSession(maximum_seconds=1800) as setup:
|
|
for command in ("verify", "preflight", "install-files", "install-files"):
|
|
result = setup.invoke(
|
|
"scripts.release",
|
|
[command, "/path/to/bundle", "--manifest-sha256", trusted_manifest_sha256],
|
|
)
|
|
if result.exit_code:
|
|
raise RuntimeError("setup CLI refused; inspect its bounded result")
|
|
# Continue the configuration/dependency/unit steps above with the same CLI
|
|
# module names and explicit argument lists through setup.invoke().
|
|
assert setup.completed
|
|
```
|
|
|
|
The session permits at most 32 calls and a deadline up to 2400 seconds. It preserves
|
|
an earlier caller alarm. Calls retain their argument parsing, permission checks
|
|
and individual exit codes; service startup, judging and private `--inside`
|
|
helpers are excluded. Completed steps are not rolled back after a failure.
|
|
|
|
The first verification of each unchanged bundle reads every byte. Reuse stays
|
|
within the same single-threaded process, with complete topology and metadata
|
|
checks on every reuse and before the session completes. Staging directories get
|
|
an uncached full byte check before publication. Results are provisional until
|
|
the context exits successfully; a new session or process starts fresh. Ordinary
|
|
standalone CLI invocations keep their existing behavior.
|
|
|
|
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.
|