Journey: one Ubuntu server from Git¶
The smallest useful thing Datum can do. One machine, one repository, nothing central, starting from an existing server that is already running nginx.
The repository¶
Four documents. A fleet root, a host, a layer, and the resources.
fleet/
├── datum.yaml
├── hosts/
│ └── web-001/
│ └── host.yaml
└── roles/
└── web/
├── layer.yaml
├── nginx.yaml
└── files/
└── nginx.conf
datum: v1alpha1
type: Layer
name: role-web
precedence: 30
match:
labels:
role: web
datum: v1alpha1
type: Package
name: nginx
desired:
state: present
---
datum: v1alpha1
type: File
name: nginx-config
requires:
- Package[nginx]
desired:
path: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
source: files/nginx.conf
---
datum: v1alpha1
type: Service
name: nginx
requires:
- Package[nginx]
restartOn:
- File[nginx-config]
desired:
state: running
enabled: true
Three resources, on a server that already has all three things. Declared-only ownership makes adopting an existing machine safe, since everything else on the server is unmanaged and untouched.
The agent¶
host: web-001
source:
url: https://git.example.com/fleet.git
branch: main
credential: /etc/datum/credentials/git
reconciliation:
mode: observe
state: /var/lib/datum
Starting in observe mode is the recommended first step. The
first pass will report what Datum would change without changing anything, which on a machine that
matters is the difference between a safe experiment and a surprise.
The first pass¶
Obtaining a revision. The agent fetches main and gets 4d7e1a2. With no recorded revision in
its state directory, this becomes the baseline, and the trust-on-first-use
gap applies, and the host cannot detect a downgrade
on its first pass because it has nothing to compare against.
Resolution. The resolver reads Host[web-001], finds its labels, evaluates every layer's matcher
against them, and matches role-web. One layer contributes, so there is no merging to do and no
precedence to resolve. The effective manifest holds three resources, each with roles/web recorded as
its provenance.
Provider selection. The agent reads /etc/os-release, finds ID=ubuntu with ID_LIKE=debian, and
resolves a capability set.
Graph construction. Two edges, from requires and restartOn, both pointing at Package[nginx]
and File[nginx-config]. No cycles, no unresolved references, no duplicate target identities.
Observation. Three reads. The package is installed at 1.24.0-2. The file exists with mode 0644
and a content digest. The unit is active and enabled.
Diff. The content digest does not match the repository's copy, because the file on the server was configured by hand months ago and the repository's copy came from somewhere else. Everything else matches.
Plan.
update File[nginx-config]
content differs
from roles/web
update Service[nginx]
reason File[nginx-config] changed, restartOn matched
none Package[nginx] present, 1.24.0-2
0 to create, 2 to update, 0 to remove, 0 to skip, 1 unchanged
The pass stops. observe mode applies nothing. The host reports drifted.
That plan is the useful output of the whole exercise. It says the repository's nginx.conf differs from
the server's, which is the thing to resolve before enforcing anything. The right move is almost always
to copy the server's working configuration into the repository, confirm the plan comes back empty, and
only then switch to enforcing.
Converging¶
After the repository's files/nginx.conf is replaced with the server's actual content, the next pass
observes, diffs, and finds everything matching. The plan is empty, the host reports converged, and
nothing is applied.
That empty plan is the signal that enforcing is now safe, because it means enforcing would do nothing.
Changing the mode to enforce and reconciling again produces the same empty plan and the same
converged.
Steady state¶
Passes continue on a schedule, each one resolving, observing, diffing, and finding nothing to do.
$ datum status
host web-001
revision 4d7e1a2
manifest sha256:6c1ab4f0
outcome converged
finished 2 minutes ago
3 unchanged
The metrics file is rewritten each pass, so
datum_pass_last_success_timestamp_seconds advances and the
staleness alert stays quiet.
What this journey tests¶
The single-host path needs nothing central, nothing inbound for reconciliation itself, and no identity beyond a name in a local file, which is the direct mode claim made concrete. The only listener is the optional metrics endpoint, bound to loopback.
More importantly it tests that adoption is incremental. A server that was configured by hand can be brought under management three resources at a time, and the first thing Datum does is tell you where it disagrees rather than resolving that disagreement on its own.