Commit Graph
36 Commits
Author SHA1 Message Date
deytenit 6123c3f6b4 feat: add Debian 13 golden image Packer pipeline and cloud-init bootstrap 2026-08-30 19:48:22 +03:00
deytenit b72b961968 fix: forward host proxy env vars and network to dclint container 2026-08-09 01:09:18 +03:00
deytenit 64d9138a8f fix: keep notification credentials out of process arguments 2026-08-02 15:07:52 +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 9bc97f196f feat: scrape CrowdSec agent metrics through Alloy
Add a prometheus.scrape for the CrowdSec agent's :6060 endpoint, forwarded to the
existing remote_write. CrowdSec runs on the host, so nodes that run it bind the
metrics endpoint on 0.0.0.0 (host firewall keeps it off the public interface) and
Alloy reaches it via the docker gateway; nodes without CrowdSec report the target
down. node/instance labels are stamped APEX_NODE_HOST like the other exporters.
2026-07-11 22:37:38 +03:00
deytenit e3749577d1 docs: rewrite README with a friendly what/why/how structure
Open with a centered logo and a plain-language description of what apex does for
a self-hoster, then Why, then How it works. Quick start shows a minimal
selfhost.example.com bootstrap. Links the published docs (en + Russian) and the
getting-started guide. Drops badges and the repo-layout table (covered in docs).
2026-07-11 20:23:58 +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 fc11217a90 chore: relicense to BSD 2-Clause; note AI-assisted contributions in README
Swap MIT for BSD 2-Clause (verbatim OSI/SPDX text) ahead of publishing apex as a
generic self-host framework. Add a README footer welcoming AI-assisted contributions
that meet the quality bar and are understood by the contributor.
2026-07-11 17:21:35 +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
v0.4.3
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.
v0.4.2
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.
v0.4.1
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.
v0.4.0
2026-07-07 20:37:31 +03:00
deytenit b41fc6097f refactor: replace loki store with Grafana Alloy push agent v0.3.0 2026-07-06 14:34:09 +03:00
deytenit 334ca196e0 feat: add .apex-root-owned opt-out for root-owned tier compositions
An empty .apex-root-owned marker in a composition dir makes tiers/useradd
skip its noroot user + .env UID injection, and tiers/chown force its tier
data (dir + children, recursively) to root:root instead of noroot-<proj>.
For services that run as root and reject non-root-owned data (e.g. seafile-mc).
2026-07-06 10:39:04 +03:00
deytenit 0b42ab1bfc docs: link published documentation in README 2026-07-05 12:40:17 +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).
v0.2.0
2026-07-05 12:16:51 +03:00
deytenit 1bc448961a fix: update sync/repository rebase target from trunk to main v0.1.2 2026-07-05 12:04:24 +03:00
deytenit c5b61a3294 fix: exclude commons/ from dclint --fix to prevent submodule dirtying
In node repos the shared core compose lives inside the pinned commons
submodule; letting dclint rewrite it dirties the submodule, which makes
sync/repository abort the nightly capture-up. Harmless in the standalone
commons repo (no commons/ dir).
v0.1.1
2026-07-05 11:20:25 +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.
v0.1.0
2026-07-05 10:50:40 +03:00
deytenit ad47b73928 docs: add PARITY map and mark engine readiness gate as green 2026-07-04 21:31:05 +03:00
deytenit d97311a652 feat: add shared base core compose with env-parameterized restic tier mounts 2026-07-04 21:30:25 +03:00
deytenit ec54ab592f feat: port utils actions (extract-traefik-certs, generate-happ-subscriptions, lint-docker-compose) 2026-07-04 21:29:17 +03:00
deytenit b9c80b0212 feat: port tiers actions (link, chown, useradd) 2026-07-04 21:28:28 +03:00
deytenit f7fc0eb697 feat: port sync and backup/run actions 2026-07-04 21:27:39 +03:00
deytenit 05469332e8 feat: port configure actions (base, ufw, crowdsec, cron, systemd, routing) 2026-07-04 21:26:56 +03:00
deytenit 63c4b6c7eb feat: add apex launcher, test runner, and cli smoke test 2026-07-04 21:25:59 +03:00
deytenit 83e6bcfe31 feat: add engine cli (discover/dispatch/help, hostname identity) 2026-07-04 21:25:59 +03:00
deytenit 496ed01ede feat: add engine context module (ctx assembly, tier vars, commons wrap) 2026-07-04 21:25:14 +03:00
deytenit 9ad3c93644 feat: add engine lib modules (ufw, crowdsec, systemd, notify) 2026-07-04 21:24:44 +03:00
deytenit 72eec40ef3 feat: port engine lib modules (log, sys, tpl, host) 2026-07-04 21:23:48 +03:00
deytenit 12eac9772b feat: add engine overlay module (resolve/discover, ast scan, tombstone) 2026-07-04 21:21:58 +03:00
deytenit cc004f99f8 feat: add engine identity module (FQDN resolution via node.env, hostname fallback) 2026-07-04 21:21:14 +03:00
deytenit f96c53add9 feat: add engine descriptor module (Meta/Arg/Flag/Opt/Rest, argparse) 2026-07-04 21:20:28 +03:00
deytenit e9c7e13848 feat: scaffold apex commons project (assets, source, hooks, docs) 2026-07-04 21:19:28 +03:00