Files
Hydra-OS/docs/updates.md
T

144 lines
7.9 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.
An empty releases list is normal and reports no update.
```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. 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.
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.
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.