Files
ermnvldmr.com/services/docs/content/en/apex/concepts/architecture.md
T

4.5 KiB

title, description, weight
title description weight
Framework architecture Four-pillar node repositories, the commons submodule, and the dispatch flow. 11

apex splits fleet automation into one shared repository — the commons — and one repository per node. The commons carries everything every node shares: the engine, the shared actions, and the core composition. A node repository carries everything that makes that node itself: its identity, host configs, service compositions, and node-local proprietaries.

The four pillars

Every node repository has the same shape:

{{< filetree/container >}} {{< filetree/folder name="." state="open" >}} {{< filetree/folder name="commons" state="closed" >}}{{< /filetree/folder >}} {{< filetree/folder name="proprietaries" state="closed" >}}{{< /filetree/folder >}} {{< filetree/folder name="configs" state="closed" >}}{{< /filetree/folder >}} {{< filetree/folder name="compositions" state="closed" >}}{{< /filetree/folder >}} {{< filetree/file name="node.env" >}} {{< filetree/file name="init.sh" >}} {{< /filetree/folder >}} {{< /filetree/container >}}

Pillar Content Ownership
commons/ Git submodule → apex.ermnvldmr.com, pinned to a release tag Shared, versioned
proprietaries/ Node-local actions, libraries, and tool sources (overlaid over commons) Node
configs/ Host configuration sources: ufw/, cron/crontab, systemd/, crowdsec/ Node
compositions/ One directory per project plus the apex/ core wrapper and the addressing ledger Node

The submodule pin is the framework's version contract: a node runs exactly the engine and core composition recorded in its gitlink. Upgrading a node is a deliberate act — check out a newer tag inside commons/ and commit the moved pointer.

Dispatch flow

The launcher commons/apex acts on the local checkout; there is no node argument anywhere.

flowchart TD
    A["apex &lt;group&gt;/&lt;action&gt; [args]"] --> B{Layout}
    B -- "commons/ dir with node.env beside it" --> C[Node checkout:<br/>overlay = proprietaries/actions]
    B -- otherwise --> D[Standalone commons:<br/>no overlay]
    C --> E["Overlay resolve<br/>(proprietaries first)"]
    D --> E
    E --> F["Identity resolve<br/>(FQDN + node.env)"]
    F --> G["Build ctx<br/>(log, sys, tpl, host, ufw,<br/>crowdsec, systemd, notify)"]
    G --> H["parse args (Meta)"]
    H --> I["run(ctx, args)"]

The engine detects the layout from its own location: running from a directory named commons/ whose parent contains node.env means "node checkout" — the parent becomes the repository root and proprietaries/actions/ becomes the overlay tree. Any other location is a standalone commons checkout acting as its own root.

Listing (apex --help) never imports action modules; summaries and disabled markers are extracted by static ast scanning. Dispatch loads exactly one module by path. Details in Concept: Actions and the overlay.

The capture-up model

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.

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

  • Stdlib only. The engine runs on a stock Debian Python 3 — no pip, no third-party imports. The optional vendor/ directory exists for pinned pure-Python code but ships empty.
  • Hand-authored composes. There is no manifest layer or code generation; compose files are the source of truth.
  • Evidence over convention. Anything enforceable is enforced by an action (anchor-IP lint, crontab rollback, marker-block self-healing) rather than by documentation.

See also: