Commit Graph
18 Commits
Author SHA1 Message Date
deytenit 0881013357 docs(apex): keep public guidance and examples deployment-neutral 2026-10-26 12:53:58 +03:00
deytenit 6463bad5ee feat(engine): de-slop commons, harden security, add targeted compose and env bridge
- Purge misplaced RFCs, scratch tokens, and node-proprietary assets
- Decouple remote provisioning to ci/scripts/init-node.py
- Add targeted composition orchestration in actions/compose.py
- Add host-side Xray stale socket cleanup in /dev/shm
- Add actions/env.py for shell exports and .env synchronization
- Harden UFW, backup retention, host tempfiles, and notify dispatcher
- Add pyproject.toml and Makefile with 78 passing unit tests
2026-10-26 00:03:33 +03:00
deytenit 458ad5f930 fix(backup): pin verified hook failure release
Pin the CI-built resticontainer image that rejects failed pre-backup hooks. Record the approved SQL-backed directory deployment gates in the replication RFC.

Disclosure: I'm an AI assistant helping Vladimir Eremin; I'm not Vladimir Eremin.
2026-10-11 16:05:40 +03:00
deytenit a9436ec385 feat: Harden allow on resources 2026-09-27 22:42:56 +03:00
deytenit 48840c3022 chore(compositions/apex): bump core service image tags to latest minor/patch 2026-09-27 20:17:15 +03:00
deytenit 5672e5d65c fix(alloy): enable docker_only in cadvisor exporter 2026-09-27 17:35:47 +03:00
deytenit 6daabae415 fix: mount containerd socket into alloy for cadvisor per-container stats
Nodes using Docker's containerd snapshotter (storage driver 'overlayfs') make
the cadvisor exporter open a containerd client to read container info; without
/run/containerd/containerd.sock it fails to register existing containers, so no
per-container metrics appear (only the machine root). Mount the socket read-only
— harmless on overlay2 nodes, required on containerd-snapshotter ones.
2026-07-12 03:03:17 +03:00
deytenit 0a585cc061 fix: run alloy in the host cgroup namespace (cgroup: host)
The cadvisor exporter reads /sys/fs/cgroup for per-container stats, but with the
default private cgroup namespace it only saw the container's own cgroup — so it
emitted machine-root container_* series (one per node, no name label) and no
per-container metrics. cgroup: host exposes the host cgroup tree so cadvisor
enumerates every container (mapped to names via the docker socket).
2026-07-12 02:52:43 +03:00
deytenit 94082ad9be chore: genericize deployment-specific defaults for public release
- core compose: parameterize TZ (default Etc/UTC), restic S3 region+endpoint
  (vendor-neutral default: AWS S3 us-east-1, no custom endpoint), and the
  xray-enclave allowlist (APEX_ENCLAVE_SUBNET, default 198.18.0.0/16).
  Cloudflare + resticontainer kept as-is.
- the reference fleet pins its real values (Europe/Moscow, ru-central1, Yandex
  Object Storage) in each node's apex.env, so deployed behavior is unchanged.
- drop PARITY.md (internal bash->python port audit; node-specific narrative).
- genericize node-name leftovers (icarus/daedalus, MSK) in tests + comments.

Tests: 26/26 pass. Verified: resticontainer resolves ru-central1/Yandex/Moscow on a node.
2026-07-11 18:00:13 +03:00
deytenit 477c0eb5d1 fix: set alloy container hostname to APEX_NODE_HOST
node_exporter's uname collector reports the container's UTS hostname as
node_uname_info{nodename}, which Docker defaults to the container id. Grafana
surfaces that as the host, so every node showed a hex id instead of its name.
Setting hostname on the alloy service makes nodename the node's public host.
node/instance labels were already correct via the config.alloy relabel.
2026-07-11 16:49:56 +03:00
deytenit bd973d783d refactor: make engine domain-agnostic — identity from node.env, no hardcoded domain
Drop the hardcoded-domain FQDN regex and every hardcoded domain from commons.
Node identity is now explicit in node.env: APEX_NODE_FQDN (real FQDN,
traefik Host()), APEX_NODE_HOST (public host name — backup --host,
obs/notify instance), APEX_SUBNET. The short name (log prefix, sync/<node>
branch, messages) is derived as the first host label.

notify instance is just the host (no hardcoded prefix); the git bot identity
moves to APEX_GIT_AUTHOR_NAME/EMAIL (node.env) with generic defaults. Alloy
node+instance labels use APEX_NODE_HOST. Fallbacks keep a node working if it
omits a var (host<-fqdn<-hostname).

BREAKING CHANGE: removes APEX_NODE and APEX_CLUSTER env vars; node name is now derived from APEX_NODE_FQDN
2026-07-08 09:32:20 +03:00
deytenit 53d3bedca6 feat: add resticontainer backup labels for traefik/adguard/xray
Per the "Core Apex (all nodes)" policy: traefik /etc/traefik/dynamic,
adguard /opt/adguardhome/conf, xray /etc/xray/config.jsonc. Profile-gated
services are only discovered on nodes that run them.
2026-07-07 21:38:25 +03:00
deytenit c100db57fe fix: mount /srv in resticontainer so absolute tierN symlinks resolve
The tierN bind mounts are absolute symlinks into /srv (compositions/<p>/tierN ->
/srv/com-*-tierN/<p>). Under /:/hostfs those targets are dangling inside the
container (restic reported "does not exist, skipping" for every path). Mount
/srv:/srv:ro at the same path with RESTIC_HOSTFS=/ so restic follows the
symlinks and records the real host paths.
2026-07-07 21:15:15 +03:00
deytenit 07a53b9b2f feat: migrate to resticontainer with label-driven per-service backup
Swap the restic core service for resticontainer
(ghcr.io/deytenit/resticontainer:0.19.1): per-service backup intent via
restic.* compose labels (paths / stop / pre+post hooks), docker socket +
/:/hostfs:ro to resolve container mounts to host paths and snapshot the
union in one restic run. backup/run drops explicit /data/tier1,2 args
(paths now come from labels), keeps init/forget/notify, and guards on a
fresh snapshot. Snapshots are stamped --host <node>.apex.ermnvldmr.com
--tag biweekly and written with --compression max.
2026-07-07 20:37:31 +03:00
deytenit b41fc6097f refactor: replace loki store with Grafana Alloy push agent 2026-07-06 14:34:09 +03:00
deytenit ce93f879e7 refactor: gate xray behind profile, expose loki via basicauth, move happ generator to daedalus
- xray gets profiles: [xray] — daedalus/icarus opt in via COMPOSE_PROFILES,
  morpheus never runs it.
- loki is routed at /loki on the node FQDN behind basicauth
  (APEX_LOKI_BASIC_AUTH from node .env); /loki is loki's native API prefix
  so no strip-prefix needed.
- utils/generate-happ-subscriptions + source/ move to daedalus proprietaries;
  commons keeps only what every node shares (16 actions now).
2026-07-05 12:16:51 +03:00
deytenit 6aaef35780 fix: resolve 17 confirmed bash-parity drifts from port review
Adversarial parity review of every ported action against its bash source
confirmed 17 real behavioral drifts (of 22 claimed); all fixed here:

- tiers/chown: defaults were compositions/apex/tierN (one level too deep);
  now the tier roots. shared/ base reset is non-recursive again.
- compose: --dry-run was swallowed by argparse.REMAINDER; now stripped.
- host.write_file: rendered files land 0644 (tempfile's 0600 made fresh
  systemd units/crowdsec configs owner-only).
- systemd.deploy_units: top-level non-hidden files only.
- ufw: missing docker/host config dir exits 66; missing /etc/ufw target
  aborts; empty rules dir warns; dotfile .rules excluded.
- sync/packages: Telegram error-trap parity and failed notify exits 1.
  Same notify-failure exit for backup/run and sync/repository.
  repository regains the node-dir guard.
- tiers/useradd: no more secret-bearing .env.bak per run.
- tiers/link: EOF on confirm prompt declines and continues.
- extract-traefik-certs: sudo mkdir inside the notify envelope.
- generate-happ-subscriptions: realpath parent-exists guards + docker check.
- configure/crowdsec: bouncer install goes through install_packages.
- lint-docker-compose: malformed/unreadable anchor-IP inputs are lint errors.
- core compose: APEX_RESTIC_REPOSITORY now ?error; RESTIC_COMPRESSION
  per-node via APEX_RESTIC_COMPRESSION (default auto).
- sys.service_exists line-anchored; tpl docstring corrected.
2026-07-05 10:50:40 +03:00
deytenit d97311a652 feat: add shared base core compose with env-parameterized restic tier mounts 2026-07-04 21:30:25 +03:00