docs(apex): clarify public operations and configuration (#69)

This commit is contained in:
deytenit committed 2026-11-08 11:17:35 +03:00
1 parent 47c70471e1
commit cff22c88dd
18 files changed
+155 -52

No files matched your search

@@ -53,15 +53,13 @@ Listing (`apex --help`) never imports action modules; summaries and disabled ma
## The capture-up model {#capture-up}
Node repositories are ledgers, not just sources: the host itself records what it runs.
`apex sync/repository` records the working tree and commons pin in a
`sync/<node>` branch, rebases onto `origin/main`, and pushes with
`--force-with-lease`. It refuses to run when commons has uncommitted changes
or the `compositions/` directory is missing. A rebase conflict aborts the sync.
1. A scheduled `sync/repository` run on the host commits the working tree to a `sync/<node>` branch (including the commons submodule pointer), rebases it onto `origin/main`, and pushes with `--force-with-lease`.
2. A scheduled CI job in the node repository merges `sync/<node>` back into `main` under a bot identity.
This [capture-up](/apex/glossary#capture-up) loop means drift on a host becomes a commit — visible, diffable, and revertable — instead of silent divergence. The sync aborts rather than committing when the commons submodule is dirty or the `compositions/` tree is missing, so a broken checkout never becomes the recorded state.
> [!NOTE]
> The CI merge job checks out the repository *without* submodules: the commons submodule uses an SSH URL that the default CI token cannot fetch, and the merge only moves the gitlink pointer anyway.
Review and integration of that branch are repository policies. APEX does not
supply a CI merge workflow or require a particular repository hosting setup.
## Design constraints {#constraints}
@@ -44,7 +44,7 @@ Opt-in services, gated by compose profiles a node enables in `COMPOSE_PROFILE
|---|---|---|
| `xray` | xray | Host-network proxy edge for the enclave. |
| `adguard` | adguard | DNS filtering, sharing traefik's network namespace; routed at `/adguard` on the node FQDN. |
| `alloy` | alloy | Per-node observability agent (Grafana Alloy). Collects host + container metrics and journal + container logs and **pushes** them outbound to a central Prometheus and Loki (see [Concept: Security model](/apex/concepts/security-model#observability)). Its container `hostname` is set to `APEX_NODE_HOST`, and that value stamps the `node` and `instance` labels on every metric and log stream. |
| `alloy` | alloy | Optional Grafana Alloy agent. Sends host/container metrics and journal/container logs to configured compatible endpoints. See [observability configuration](/apex/concepts/security-model#observability). |
| `manual` | resticontainer | One-shot label-driven backup runner (`ghcr.io/deytenit/resticontainer`) — never starts with `up`; invoked by the `backup/run` action, which discovers per-service backup intent from `restic.*` compose labels. |
## Networks {#networks}
@@ -21,7 +21,7 @@ It declares identity explicitly through these keys:
```ini {filename="node.env"}
APEX_NODE_FQDN=node1.example.com
APEX_NODE_HOST=node1.example.com
APEX_SUBNET=198.18.16.0/24
APEX_SUBNET=198.18.0.0/24
```
### The OS hostname
@@ -34,7 +34,11 @@ The [core composition](/apex/glossary#core-composition) encodes container-level
## Observability {#observability}
Observability is an **outbound push**, not an exposed route. The optional `alloy` core service (Grafana Alloy, one agent per node) collects host metrics (a `node_exporter`-equivalent), container metrics (cadvisor), the systemd journal, and docker container logs, and pushes them to a **central** Prometheus and Loki that live on a designated node — the metrics via `prometheus.remote_write` to `APEX_PROM_PUSH_URL`, the logs via `loki.write` to `APEX_LOKI_PUSH_URL`. Both targets are authenticated with HTTP basic auth using `APEX_OBS_BASIC_AUTH_USER` / `APEX_OBS_BASIC_AUTH_PASS`. Because the push is outbound, Alloy attaches to the `direct` network (which has egress) rather than the `internal` `enclave`; there is no node-local log store and no `/loki` route to protect.
The optional `alloy` service sends host and container metrics to
`APEX_PROM_PUSH_URL` and logs to `APEX_LOKI_PUSH_URL`. Configure compatible
endpoints and HTTP basic-auth credentials through `APEX_OBS_BASIC_AUTH_USER`
and `APEX_OBS_BASIC_AUTH_PASS`. The service requires outbound connectivity to
those endpoints; their hosting and access policy are deployment choices.
## Secrets boundary {#secrets}
+1 -1
View File
@@ -16,7 +16,7 @@ Terminology used within the apex fleet operations framework.
## Capture-up {#capture-up}
**Capture-up** - The flow that records a host's live state back into its repository: a scheduled commit to the `sync/<node>` branch, rebased onto `main` and folded back by CI. Drift becomes a diff instead of divergence.
**Capture-up** — Recording a host's working state in a `sync/<node>` branch through `apex sync/repository`. The repository's own policy determines review and integration.
## Commons {#commons}
@@ -12,7 +12,8 @@ weight: 24
| Step | Effect |
|---|---|
| `configure/base` | Ensures a Debian base: package index update, essential packages (`curl`, `wget`, `git`, `rsyslog`), rsyslog running, `ufw` installed, and the `ufw-docker` helper at `~/.local/bin/ufw-docker`. |
| `configure/base` | Installs missing Debian packages after checking that APT's simulation contains no upgrades or removals, ensures rsyslog is running, and verifies the pinned `/usr/local/bin/ufw-docker` before privileged execution. Avoid concurrent package operations. |
| `configure/timezone` | Applies `configs/timezone` when present and refreshes active cron after a timezone change. Supports `--dry-run`. |
| `configure/ufw` | Applies host rules, injects the managed kernel-rule blocks, applies container rules via `ufw-docker`. Supports `--dry-run`. |
| `configure/crowdsec` | Installs CrowdSec if absent, deploys `configs/crowdsec/` to `/etc/crowdsec/`, installs collections, and the firewall bouncer when configured. Supports `--dry-run`. |
| `configure/cron` | Renders `configs/cron/crontab` through the template layer and installs it — with preview, empty-render refusal (exit `65`), and rollback on a failed install. Supports `--dry-run`. |
@@ -27,10 +28,39 @@ After `configure/ufw`, restart `ufw` deliberately (`sudo systemctl restart ufw`)
apex compose up # core first, then services alphabetically
apex compose down # reverse order
apex compose restart
apex compose up --dry-run # print the commands only
apex compose --dry-run up # print the commands only
apex compose -p apex restart traefik
apex compose -p app up -d
```
Extra arguments pass through to `docker compose` per project. Each project's `.env` and `apex.env` are supplied automatically when present.
Put wrapper options (`-p` and `--dry-run`) before the action. Extra arguments pass
through to Docker Compose for the selected project. Each project's `.env` and
`apex.env` are supplied automatically when present. Positional targeting remains
available as `apex compose restart apex traefik` or `apex compose up -d apex`;
prefer `-p` when arguments could also name a composition.
Replace `app` with a composition defined in your repository. When a mount or
other container definition changes, use targeted `up -d` to recreate it:
`restart` retains the existing definition.
### Direct core Compose commands
```bash
apex env --sync
cd compositions/apex
docker compose config --quiet
docker compose restart traefik
```
Sync preserves existing secret and UID/GID text in `.env` and its tier-storage
symlink. A managed block loads committed `apex.env` settings, including profiles,
then synthesized node/path values. Writes use mode `0600`. Resync after changing
those settings or paths; do not hand-edit the managed block. Docker's shell
environment still overrides file values.
`eval "$(apex env)"`, `apex env --dotenv`, and `apex env --write generated.env`
export or save generated APEX values only. They do not load composition secrets
or replace the full `--sync` bridge. Output modes are mutually exclusive.
## Backups {#backups}
@@ -46,7 +76,7 @@ Runs [resticontainer](https://github.com/deytenit/resticontainer) one-shot under
apex sync/repository <telegram-bot-url>
```
Commits the live checkout to `sync/<node>`, rebases onto `origin/main`, and force-pushes with lease; CI folds the branch back into `main`. The action refuses to run — with a paged error — when the commons submodule is dirty or the `compositions/` tree is missing, and aborts cleanly on a rebase conflict (manual intervention required). See [Concept: Framework architecture](/apex/concepts/architecture#capture-up) for the model.
Commits the live checkout to `sync/<node>`, rebases onto `origin/main`, and force-pushes with lease. The action refuses to run — with a paged error — when the commons submodule is dirty or the `compositions/` tree is missing, and aborts cleanly on a rebase conflict (manual intervention required). See [Concept: Framework architecture](/apex/concepts/architecture#capture-up) for the model.
## Update awareness {#updates}
@@ -25,7 +25,7 @@ The gitlink pins the exact engine version the node runs. Upgrading later mean
```ini {filename="node.env"}
APEX_NODE_FQDN=node1.example.com
APEX_NODE_HOST=node1.example.com
APEX_SUBNET=198.18.16.0/24
APEX_SUBNET=198.18.0.0/24
APEX_GIT_AUTHOR_NAME=apex [bot]
APEX_GIT_AUTHOR_EMAIL=apex@localhost
```
@@ -41,7 +41,7 @@ include:
```
```ini {filename="compositions/apex/apex.env"}
APEX_TRAEFIK_DIRECT_IP=198.18.16.10
APEX_TRAEFIK_DIRECT_IP=198.18.0.10
APEX_RESTIC_REPOSITORY=s3:<endpoint>/<bucket>/<node>
COMPOSE_PROFILES=adguard,xray,alloy
# observability push targets (only when the alloy profile is enabled)
@@ -77,9 +77,12 @@ __pycache__/
Create `compositions/ADDRESSING.md` with the node's subnet (`APEX_SUBNET`) and the reserved ranges (`.10`–`.19` core, `.20`+ services). Every [anchor IP](/apex/glossary#anchor-ip) allocated later is recorded here.
### Wire capture-up CI
### Choose a sync review policy
Add a scheduled workflow that merges `sync/<node>` into `main` when the branch exists, committing under a bot identity. Check out **without** submodules — the merge only moves the commons pointer, and an SSH submodule URL is not fetchable with the default CI token. See [Concept: Framework architecture](/apex/concepts/architecture) for the full [capture-up](/apex/glossary#capture-up) loop.
Decide how to review and integrate the `sync/<node>` branch produced by
`apex sync/repository`. If you automate integration, configure repository
permissions and submodule access for that workflow. See the
[capture-up model](/apex/concepts/architecture#capture-up).
### Validate
@@ -28,7 +28,7 @@ services:
- ./@tier2/<anchor>/data:/data
networks:
direct:
ipv4_address: 198.18.16.20
ipv4_address: 198.18.0.20
enclave: {}
labels:
- "traefik.enable=true"
@@ -4,7 +4,7 @@ description: "Every commons action: arguments and behavior."
weight: 32
---
The sixteen [commons](/apex/glossary#commons) actions, by group. Arguments are shown as the parser accepts them; optional arguments are bracketed. Exit codes follow the [engine convention](/apex/reference/cli#exit-codes).
The [commons](/apex/glossary#commons) actions, by group. Arguments are shown as the parser accepts them; optional arguments are bracketed. Exit codes follow the [engine convention](/apex/reference/cli#exit-codes).
## configure {#configure}
@@ -12,13 +12,19 @@ The sixteen [commons](/apex/glossary#commons) actions, by group. Arguments are
`apex configure [--dry-run]`
Runs the whole suite in fixed order — `base`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing` — and stops at the first failing step, propagating its exit code. `--dry-run` is forwarded to the steps that support it.
Runs the suite in order: `base`, `timezone`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing`, then additional node actions. A failing step contributes a nonzero exit status; later steps may still run. `--dry-run` is forwarded only to steps that support it, so it is not a guarantee that the entire umbrella command is read-only.
### configure/base
`apex configure/base`
Debian-only guard (exit `1` elsewhere). Updates the package index, installs the essential packages (`curl`, `wget`, `git`, `rsyslog`), ensures rsyslog is running, installs `ufw` when absent, and downloads the `ufw-docker` helper to `~/.local/bin/ufw-docker` (skipped when already present).
Debian-only guard (exit `1` elsewhere). Checks installed package state for `curl`, `wget`, `git`, `rsyslog`, and `ufw`; installs missing packages and ensures rsyslog is running. Refuses an APT simulation containing upgrades or removals and uses `--no-upgrade --no-remove`. Avoid concurrent package operations: simulation and installation are separate transactions. Verifies the immutable revision and SHA-256 of `/usr/local/bin/ufw-docker` before running it as root. Unsafe paths and unverified downloads are rejected; an executable file alone is not trusted.
### configure/timezone
`apex configure/timezone [--dry-run]`
Applies the IANA zone named in `configs/timezone` and refreshes active cron after a change. An absent declaration preserves the current timezone; an invalid zone fails before changing it.
### configure/ufw
@@ -110,9 +116,19 @@ Two passes: (1) [anchor-IP](/apex/glossary#anchor-ip) validation over `compositi
### compose
`apex compose <action> [--dry-run] [extra...]`
`apex compose [-p <composition>] [--dry-run] <action> [extra...]`
Orchestrates `docker compose <action>` across the node: the core project first, then services alphabetically (reversed for `down`). `up` implies `-d`; `.env`/`apex.env` are passed per project when present; extra arguments pass through; `--dry-run` (accepted in any position) prints commands instead of executing.
Without a target, visits core first and other compositions alphabetically, reversed for `down`. `-p` selects one composition or `all`; `up` defaults to detached mode. `.env` then `apex.env` are passed per project, with generated identity/path values in the process environment. Remaining arguments pass through to Docker Compose. Wrapper options precede the action; `--dry-run` prints commands without executing them.
Compatibility forms include `apex compose restart apex traefik` and `apex compose up -d apex`. Use explicit `-p` to distinguish a composition from a service or option value.
## env {#env}
`apex env [--export | --dotenv | --write <path> | --sync]`
Default output is shell-quoted APEX exports for `eval "$(apex env)"`. `--dotenv` emits dotenv syntax; `--write` writes generated values to a separate file, including a relative filename. These modes do not read composition credentials or merge `apex.env`.
`--sync` preserves existing `compositions/apex/.env` text and updates only its managed block: committed `apex.env` settings first, generated identity/path values last. This includes non-APEX settings such as `COMPOSE_PROFILES` and `TZ`, allowing bare Docker Compose commands in the core directory. Existing tier symlinks remain intact, and synchronized files use mode `0600`. Resync after changing source settings or tier paths. Malformed managed markers fail without rewriting uncertain content. Shell environment values still have Docker Compose precedence.
---