129 lines
7.0 KiB
Markdown
129 lines
7.0 KiB
Markdown
# Architecture
|
|
|
|
How the scripts collection is put together. Every script, helper and arrow below
|
|
exists in the repository; nothing is aspirational.
|
|
|
|
## Overview
|
|
|
|
There is no shared library. Each script is one file that can be fetched and run on its
|
|
own, and the six of them share a skeleton rather than a module: the same command
|
|
runner, the same progress reporting, the same state checks before every action, and the
|
|
same shape of a run.
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
Start[invocation] --> Args[parse_args reads ARGV]
|
|
Args --> Version{version or help?}
|
|
Version -->|yes| Print[print and exit]
|
|
Version -->|no| Root[check_root, detect_os]
|
|
Root --> Sections[sections run in order]
|
|
Sections --> Each[each section checks state first]
|
|
Each --> Done{changed?}
|
|
Done -->|no| Report[report what is already in place]
|
|
Done -->|yes| Act[act, then verify what was done]
|
|
Act --> Report
|
|
Report --> Summary[print_summary on stderr]
|
|
```
|
|
|
|
A run is idempotent because of that loop: a section reads the state of the system,
|
|
compares it with the desired one, and acts only on the difference, so a second run
|
|
reports everything as already in place and changes nothing.
|
|
|
|
## The seven scripts
|
|
|
|
| Script | Owns | Deliberately does not |
|
|
|---|---|---|
|
|
| `network-diag.pl` | Latency, loss, DNS timing, MTU discovery, dual-stack reachability, listening ports, and the grade of the connection | Touch any configuration; it only measures |
|
|
| `system-diag.pl` | Reading CPU, memory, disk, network, GPU, services, security and performance, and grading the result | Change anything on the host; it is read-only |
|
|
| `server-setup.pl` | Base packages, firewall (with the SSH rule verified before the service starts), SELinux, Podman, automatic updates | Install the services themselves; that is the operator's, and the deploy scripts' |
|
|
| `workstation-setup.pl` | A Fedora desktop: Brave, the official Go toolchain, Rust, GoLand, Flatpak applications, firewall, SELinux | Anything on a server distribution; it targets Fedora Workstation |
|
|
| `sglang-deploy.pl` | The SGLang deployment: the container engine, the systemd unit, nginx with TLS, the API key, the firewall rule and the SELinux boolean | The host's own setup, which `server-setup.pl` does first |
|
|
| `system-optimise.pl` | Old kernels (the running one and one fallback always stay), journals, temporary files, core dumps, and the package audit | Touch an rpm-ostree system, which it refuses |
|
|
|
|
## Data flow: a forked section collection
|
|
|
|
`system-diag.pl` is the one script whose shape is not linear. Its sections are
|
|
independent and dominated by waiting, so each runs in its own child process, writes its
|
|
result into a private scratch directory as a Perl literal, and the parent reads the
|
|
results back after reaping. The runner keeps stdout and stderr apart in that same
|
|
directory, which is why the file names carry the process id.
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Parent
|
|
participant Child as Section child
|
|
participant Disk as Scratch directory
|
|
Parent->>Child: fork, one per requested section
|
|
Child->>Child: collect one section
|
|
Child->>Disk: write the result as a Perl literal
|
|
Parent->>Parent: waitpid, report the section as it lands
|
|
Parent->>Disk: read_literal for each child
|
|
Parent->>Parent: compute the grade and render the report
|
|
Parent->>Disk: remove the scratch directory
|
|
```
|
|
|
|
Only the process that created the scratch directory removes it: a forked child inherits
|
|
the `END` block, so the cleanup is guarded by a parent pid comparison. A child that
|
|
cannot write its result is reported as a missing section, and the grade drops that
|
|
component rather than inventing a value for it.
|
|
|
|
## Conventions every script follows
|
|
|
|
- **Builtins only.** No module beyond the interpreter is loaded, because the systems
|
|
this targets package modules separately. What Perl has no builtin for is written out:
|
|
the command runner, RPM's version comparison, the JSON decoders and the encoder, the
|
|
IPv4 and IPv6 arithmetic, `sockaddr` packing, a `which`, and argument parsing.
|
|
- **External binaries as transport.** `dnf`, `rpm`, `curl`, `openssl`, `podman`,
|
|
`systemctl`, `journalctl`, `find`, `stat`, `sha256sum`, `tar`, `gpg`, `lspci` and the
|
|
rest are driven as argument lists through `run`, which keeps stdout and stderr apart,
|
|
forces `LANG=C` and `LC_ALL=C` for parseable output, and turns a timeout, a missing
|
|
binary and a failed exec into exit codes 124, 127 and 126 rather than exceptions.
|
|
- **One optional module.** `Time::HiRes` is loaded inside an `eval` in the scripts that
|
|
time something. Where it is absent the affected figures are reported as not measured
|
|
and the grade drops that component, rather than the script failing.
|
|
- **Atomic writes for system configuration.** A file that a boot depends on is written
|
|
to a temporary name, flushed, given its mode and owner, and renamed over the target.
|
|
Perl has no `fsync`, so that barrier is delegated to `sync` where the binary exists.
|
|
- **Errors carry what the tool said.** A failure reproduces the errno, the reason and
|
|
the path, so a message can be searched for as it stands.
|
|
|
|
## The SGLang deployment
|
|
|
|
`sglang-deploy.pl` is the only script that deploys a service, and its engine runs as a
|
|
container. That is a consequence of the material rather than a preference: SGLang
|
|
publishes no ROCm wheel, and its AMD install paths are the project's container images
|
|
or a source build against a full ROCm toolchain that Fedora carries only partly and
|
|
that CentOS Stream and openEuler cannot carry at all.
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Client[client] -->|443| Nginx[nginx on the host, TLS]
|
|
Nginx -->|loopback, plain HTTP| Engine[SGLang in a Podman container]
|
|
Engine -->|device nodes| GPU[/dev/kfd, /dev/dri]
|
|
Engine -->|bind mount| Cache[state directory, model cache]
|
|
Unit[systemd unit] -->|podman run| Engine
|
|
EnvFile[EnvironmentFile, mode 0600] -->|API key| Unit
|
|
```
|
|
|
|
The host therefore needs the amdgpu kernel driver and its device nodes, never a ROCm
|
|
userland. The image tag is resolved from the card's family (mi30x for an MI300 or
|
|
MI325, mi35x for an MI350 or MI355) and from the host's ROCm, because the container's
|
|
ROCm userland must not be newer than the host's kernel driver. A Radeon 8060S or 8050S
|
|
(gfx1151) has no stable tag anywhere; AMD's dated development builds are resolved
|
|
instead, and `--image` pins one. Radeon cards need `SGLANG_USE_AITER=false` and
|
|
`SGLANG_ROCM_FUSED_DECODE_MLA=false` in the unit, which the script writes for them and
|
|
never for an Instinct host.
|
|
|
|
## Dependencies
|
|
|
|
Nothing outside the interpreter, and nothing that has to be installed beyond the tools
|
|
each script's own dependency section installs. The non-obvious ones and their reasons:
|
|
|
|
- `podman` for `sglang-deploy.pl`, because the engine is a container.
|
|
- `lspci` for the GPU family, and `rocm-smi` in `system-diag.pl` for AMD memory and
|
|
utilisation figures.
|
|
- `sha256sum` in `workstation-setup.pl`, because Perl's builtins have no hash.
|
|
- `sync` for the write barrier after an atomic write.
|
|
- `getent` or `host` in `network-diag.pl` as the resolver transport, since `getent` on
|
|
FreeBSD implements neither of the `ahosts` databases.
|