166 lines
9.5 KiB
Markdown
166 lines
9.5 KiB
Markdown
# Signed Hydra runtime updates
|
|
|
|
The new ISO includes an independent updater and an independently pinned Ed25519
|
|
public key. Discovery uses the public Gitea repository at
|
|
`https://git.shanaty.ru/Emil_Shanaty/Hydra-OS`; discovery is not a trust source.
|
|
If Gitea has no published runtime release candidate, discovery checks the fixed
|
|
public repository path `releases/stable/` on branch `main`. If that path has no
|
|
manifest (HTTP 404), it reports no update. Both locations use the same independently
|
|
pinned signature key; Git branch contents and repository ownership do not establish
|
|
release trust.
|
|
|
|
```sh
|
|
hydra update check
|
|
hydra update status
|
|
sudo hydra update apply
|
|
sudo hydra update rollback TRANSACTION
|
|
sudo hydra-update-recover
|
|
```
|
|
|
|
`check` and `status` run without sudo. Apply prints the transaction identifier.
|
|
After applying or rolling back, log out of the graphical session and log in again
|
|
to load the changed runtime. Existing guest disks and profiles remain unchanged.
|
|
If the normal `hydra` command is broken, use the independent `hydra-update` command
|
|
or `hydra-update-recover` directly. A prepared/interrupted transaction blocks the
|
|
next apply until recovery succeeds. Recovery may be repeated after interruption.
|
|
|
|
Only the latest current transaction can be explicitly rolled back. Rollback first
|
|
checks that its installed files still match their recorded content, ownership and
|
|
permissions, and refuses local edits. Recovery restores the originals of an
|
|
interrupted transaction without requiring mixed runtime files to match the new
|
|
release. Corrupt or missing backups stop restoration before any destination changes;
|
|
keep the complete transaction directory for inspection. No automatic cleanup removes
|
|
failed transaction evidence.
|
|
|
|
Runtime updates contain only Python runtime modules, `usr/lib/hydra/bin/hydra`,
|
|
`etc/motd`, the Fastfetch configuration and its text logo. They cannot contain
|
|
`update*.py`, the independent updater, release keys, `/home`, guest profiles/disks,
|
|
credentials, network configuration, services, Portage, the kernel or boot files.
|
|
Runtime rollback **does not roll back Gentoo or its kernel**. Whole-system recovery
|
|
is a separate facility with separate acceptance requirements.
|
|
|
|
## Trust bootstrap and installation
|
|
|
|
These files are installed by the trusted image build, outside the runtime payload:
|
|
|
|
| Source | Installed path | Owner/mode |
|
|
| --- | --- | --- |
|
|
| `hydra/update_engine.py` | `/usr/lib/hydra-update/engine.py` | root:root 0644 |
|
|
| `bin/hydra-update` | `/usr/bin/hydra-update` | root:root 0755 |
|
|
| `bin/hydra-update-recover` | `/usr/bin/hydra-update-recover` | root:root 0755 |
|
|
| Independently supplied public key | `/etc/hydra/update.pub` | root:root 0644 |
|
|
|
|
The launchers use isolated Python (`/usr/bin/python3 -I`) and never import the
|
|
mutable `hydra` package. Install Python and OpenSSL in the image. Protect all parent
|
|
directories with root ownership and no group/other write permissions. The engine
|
|
and its key cannot update themselves through this protocol; their maintenance is
|
|
an independently verified image/bootstrap operation.
|
|
|
|
An old ISO without these files cannot safely bootstrap by executing a downloaded
|
|
zipapp. Reinstall from the newly verified ISO, or independently verify and install
|
|
the exact engine, launchers and public key from a trusted source as administrator.
|
|
Do not fetch the trust key from the same release discovery response being verified.
|
|
Verify its fingerprint through the independently supplied release information.
|
|
|
|
Production mutation requires UID 0, x86_64, the Hydra build marker and Hydra's
|
|
`ID=hydra-os` OS release. Production always uses `/etc/hydra/update.pub`; a key
|
|
override is refused. A `--root DIRECTORY` support/testing argument targets an
|
|
explicit isolated root containing the build marker and existing payload parent
|
|
directories. Only these staging roots allow `--public-key FILE`. No environment
|
|
variable silently redirects an installed update to another root or key.
|
|
|
|
## Release creation
|
|
|
|
Keep the Ed25519 private key outside Git, the source/build context and the Nix
|
|
store, in a user-owned private file (0600). Never upload it as a release asset.
|
|
Provision this key through the release operator's separate key-management process.
|
|
The builder does not generate keys, publish assets or overwrite an existing output.
|
|
|
|
```sh
|
|
python3 dev/release.py \
|
|
--source /path/to/verified-checkout \
|
|
--output /path/to/new-release-directory \
|
|
--private-key /private/outside-git/release.pem \
|
|
--version 1.0.0 --sequence 1
|
|
```
|
|
|
|
The three release assets are `manifest.json`, `manifest.sig`, and `runtime.zip`.
|
|
Publish them under the immutable Gitea tag `1.0.0`, exactly matching the signed
|
|
version. Alternatively, when the native Gitea release list has no runtime candidates,
|
|
publish those same three passive artifacts together in `releases/stable/` on the
|
|
repository's `main` branch. The raw HTTPS prefix is fixed:
|
|
`https://git.shanaty.ru/Emil_Shanaty/Hydra-OS/raw/branch/main/releases/stable/`.
|
|
The branch serves as a discovery location; the verified manifest supplies its
|
|
version and sequence. Keep historical signed artifacts under immutable versions
|
|
outside this moving stable pointer. Native Gitea runtime releases take precedence;
|
|
a present native candidate, including one already installed, disables this fallback.
|
|
|
|
Each new release needs a strictly increasing integer sequence; never
|
|
reuse a sequence or mutate assets at an existing version. Rollback retains the
|
|
highest accepted sequence independently of the current installed version.
|
|
The builder is deterministic for identical source, version, sequence, key and
|
|
compression implementation. It records Git HEAD; build from a reviewed clean
|
|
checkout so that commit identifies the bytes actually being released.
|
|
|
|
The signature is Ed25519 over the exact bytes of `manifest.json`, using OpenSSL
|
|
`pkeyutl -sign -rawin -inkey PRIVATE -in manifest.json -out manifest.sig`.
|
|
Verification uses `pkeyutl -verify -pubin -rawin` with the pinned key. No downloaded
|
|
Python is executed. `runtime.zip` is passive file data with reproducible timestamps.
|
|
|
|
Manifest format 1 has exact top-level fields `format`, `product` (`Hydra OS`),
|
|
`scope` (`runtime`), `channel` (`stable`), `architecture` (`x86_64`), `sequence`,
|
|
`version`, `source_commit`, `archive` and `files`. The archive entry contains
|
|
`name`, `size` and `sha256`; each allowed file has `size`, `sha256` and `mode`
|
|
(0644 or 0755). Duplicate keys, unknown fields, unsafe paths, links/special archive
|
|
members, mismatched hashes/sizes and unsupported formats are rejected.
|
|
The maximum manifest is 256 KiB, archive/expanded payload 16 MiB, individual file
|
|
4 MiB and file count 256. Downloads have a 15-second socket timeout and 60-second
|
|
per-artifact deadline; redirects remain on the fixed HTTPS origin. Discovery
|
|
checks up to the latest 20 published release records, skipping draft/prerelease
|
|
records. Publish runtime releases regularly enough that the current stable release
|
|
remains in that window. Only an otherwise successful discovery with no runtime
|
|
candidates activates the repository fallback. API errors, malformed native assets,
|
|
tag mismatches and bad signatures remain visible errors and never trigger fallback.
|
|
For the repository path, only a missing `manifest.json` (404) means no release;
|
|
a missing signature, server error or invalid signature is an error. The ZIP is
|
|
fetched only for apply after manifest verification, and its signed hash/size and
|
|
all existing download bounds/redirect restrictions still apply. A branch update
|
|
between requests may cause a safe verification failure; retry after publication
|
|
finishes.
|
|
|
|
For offline/local verification and application:
|
|
|
|
```sh
|
|
hydra update check --artifacts /trusted/local/release
|
|
sudo hydra update apply --artifacts /trusted/local/release
|
|
```
|
|
|
|
These use the same pinned key and validation as online releases. For developer
|
|
staging, add `--root /isolated/hydra-root --public-key /test/public.pem`.
|
|
The Python API is `Updater(root, public_key).apply(artifacts)`, `.status()`,
|
|
`.rollback(transaction)` and `.recover()`; staging roots may run as their owner.
|
|
|
|
## Durability and limits
|
|
|
|
A nonblocking `flock` serializes mutations. The private state directory is
|
|
`/var/lib/hydra-update` (0700). Each transaction saves original regular-file bytes,
|
|
hashes, sizes, permissions and ownership; symlink targets/ownership and previously
|
|
absent files are recorded explicitly. Unsupported destination types and unsafe
|
|
parent directories are refused. Payload parent directories must already exist.
|
|
|
|
All destination paths and payload bytes are checked before creating update state.
|
|
All backups and a prepared journal are durable before the first runtime replacement.
|
|
Writes use adjacent temporary files, file fsync, atomic replacement and directory
|
|
fsync. Recovery is independent of the runtime package. The public read-only status
|
|
summary is `/usr/share/hydra/update-status.json`; private journals are authoritative,
|
|
and an abrupt loss of power may leave the summary briefly older than those journals.
|
|
Run recovery as administrator when an update was interrupted; it refreshes the
|
|
public summary from authoritative state even when no transaction remains pending.
|
|
|
|
This is a per-file recoverable transaction, not a simultaneous filesystem snapshot.
|
|
A running process can observe a mixture during apply. Do not launch new Hydra
|
|
runtime operations until apply/recovery finishes. Root administrators and processes
|
|
that can already modify protected root-owned directories are outside the updater's
|
|
concurrent-write threat model. Filesystem/device failures can require manual repair;
|
|
recovery retains all evidence and never promises recovery from lost backup storage.
|