Commit Graph
14 Commits
Author SHA1 Message Date
Yann Autissier f429b8c38d derive the certificates a server needs from what it routes
The hostnames are already declared, once, in the fabio route tags. A
domains.txt would be a second source of truth free to disagree with what is
actually served, so myos cert reads the tags instead and decides on its own
which name needs a wildcard: one is asked for where a tag uses one, and it
absorbs the concrete names it covers. A wildcard covers a single label, so
a.b.example.org keeps its own certificate.

dehydrated issues them: a shell script, no python, which fits a tool that has
to install on any server. It answers http-01 itself on a port bound to the
loopback and routed by fabio, and delegates dns-01 to a provider hook. The
deploy hook writes the two file names fabio watches for, through a temporary
name so fabio never reads half a certificate.

Fixed on the way: the port parser wanted six spaces of indentation and the
catalogue writes four, so every stack that binds its ports was reported
unbound.
2026-09-05 13:41:16 +02:00
Yann Autissier 90bb97cca8 replace the linux-only firewall patching with a portable exposure model
The catalogue publishes 51 compose files with the bare form 'ports: [80]',
which binds a random host port on 0.0.0.0: every service answers the internet.
ufw-docker existed to take that back afterwards, as root, on linux only,
because docker writes its own firewall rules and ufw never sees those ports.

Publishing where you mean to solves it at the source. Verified against the
daemon: '- 80' gives 0.0.0.0:32768, '127.0.0.1::80' gives 127.0.0.1:32769.
Same on macOS and on linux, no privilege, and visible in docker ps.

A stack binds with ${MYOS_BIND_PRIVATE|PUBLIC|MESH} and declares what it
means with <PREFIX>_SERVICE[_<port>]_EXPOSE. myos expose reads the resolved
compose configuration and reports what would be opened; --strict fails when a
port faces the world without declaring it, which is what an agent runs against
a server it did not set up.
2026-09-05 13:07:13 +02:00
Yann Autissier 60668fc80a implement recreate, reload and status, and stop advertising -H
The usage listed recreate but the dispatcher did not know it: myos recreate
exited 2 saying the command was unknown. status was missing too, and -H was
parsed into a variable nothing ever reads, so it was accepted and ignored. It
now refuses rather than pretending.
2026-09-05 12:15:05 +02:00
Yann Autissier f2bcbc6857 give make a single call for the settings the CLI resolves
myos export prints every setting the requested stacks declare, in one process:
68 variables in half a second, where asking for them one at a time costs a
process each and about ten seconds.

The shim reads that once and includes it, so a .mk target sees
HOST_FABIO_VERSION and the computed fabio tag without make knowing anything
about how they are produced. --make quotes the values so make neither expands
a $ nor starts a comment at a #.
2026-09-05 12:10:32 +02:00
Yann Autissier ca7338bcb6 fix the bugs the rewrite uncovered, in the make engine too
- the wrapper followed one absolute symlink only, and fed the config file to
  env(1) as-is, so a comment or a blank line in /etc/conf.d/myos made every
  command fail with 'env: #comment: No such file or directory'
- verlt called verlte, which does not exist, and used return outside a
  function: it never compared anything
- stat asked for the access time on macOS and the modification time elsewhere,
  so newer/older did not mean the same thing depending on the machine
- setup-docker-group called ansible-user-add-groups, removed with ansible, then
  announced that the user had been added to the docker group. It now runs
  usermod or addgroup, and says so when neither works
- the ssh targets looped over AWS_INSTANCE_IP, which nothing defines since
  make/apps/aws was dropped: they exited 0 having done nothing. They take
  SSH_HOSTS and fail when it is empty
- patsublist left a space before the comma joining two fabio routes
- the JWT macro splits a payload on its commas; documented at the macro, since
  fixing it would change every key it has already produced
2026-09-05 12:03:53 +02:00
Yann Autissier 6192d73cbe port the make list functions and JWT, and expose the framework variables to hooks
lib/str.sh gains firstword, lastword, or, patsubst, filter, filter_out,
addprefix, addsuffix and jwt: what the catalogue .mk files are written in.
The make JWT macro split on the commas of its payload; this one does not.

lib/context.sh registers COMPOSE_PROJECT_NAME, APP, DOCKER_NETWORK_* and the
rest as lazy defaults, so a converted .mk keeps reading them as it did.

Hooks also load a directory-level _stack.env and _stack.sh, for the .mk files
of the catalogue that hold settings for several stacks at once.
2026-09-03 21:58:22 +02:00
Yann Autissier 3e55cdcd14 chain commands, and let a project refine a catalogue stack
myos build up logs host/fabio runs the three in order and stops at the first
failure, the way make build up logs STACK=host/fabio did. Leading words that
name commands are commands; the first word that is not one starts the stacks.

A stack found in several directories of the stack path is now merged rather
than shadowed, least specific first, so a project drops
stack/postgres/postgres.local.yml next to the catalogue's postgres.yml and
refines it. Settings hooks follow the same order, so a project can redefine a
default the catalogue ships. Neither engine did this before: the project
directory simply hid the catalogue one.

An unknown command now says so and suggests the command to type, instead of
printing the whole usage.
2026-09-03 21:17:17 +02:00
Yann Autissier 55fae625d6 keep the dynamism of make in pure shell
Two mechanisms, matching what the make engine actually did:

Lazy defaults. A stack setting is a function myos_default_<VAR>, called only
when the variable has no value, and called again at every reference. That is
exactly a recursive ?=: an explicit value wins, and the default follows a
DOMAIN that a .env changes later. The prefix is what makes it safe; the first
version used a bare function named after the variable, and the test suite
caught it running /usr/bin/host for a stack group called host.

Templates. myos env-update fills a .env from the .env.dist files, expanding
${VAR} against the current values and running $(command), forward references
included.

Also fixed: the project .env now wins over /etc/conf.d/myos, which is what the
documentation claimed and the code did not.

share/make/shim.mk lets a project keep make as a front end: every myos command
becomes a target that shells out to bin/myos, and the project keeps its own
targets and its stack .mk files. It sits outside make/ because the legacy
engine globs every .mk in there.
2026-09-03 20:46:28 +02:00
Yann Autissier f541ca418b let a stack compute its settings without make
A stack can now ship <name>.env and <name>.sh next to its compose files. The
hook is sourced with the tag helpers available, which is what the computing
.mk files of the catalogue were using make for: 29 of its 44 .mk files only
exist to build variables like the fabio tags.

Converting stack/host/fabio.mk by hand gives byte-identical output for the
route tag, and drops a trailing comma the make version left in the listener
list.

Also: APP_HOST and APP_URI are computed (the tag helpers build on them),
--color controls the escape codes rather than always emitting them, and
make test-portability runs the CLI under busybox ash and dash.
2026-09-03 20:22:52 +02:00
Yann Autissier 32e2624245 add exec, run and scale, and the agent skill
The skill documents how to use myos: what to check before touching a host
stack, how a reference resolves, what the three kinds of stack mean, and where
each failure comes from. Every command it shows was run against the fixtures
before being written down, which is how exec, run and scale turned out to be
missing.

AGENTS.md covers the other side: how to change myos without breaking the
deployments that already run it.
2026-09-03 18:46:00 +02:00
Yann Autissier 35999574bd run the golden suite against the CLI too, and pin every delta
Both engines now go through the same 68 cases. 31 produce byte-identical
output; the rest have a recorded CLI expectation and a reason in DELTAS.md,
the bulk of it being that the CLI does not shell out to a recursive make and
calls compose once per project.

Fixed while comparing: the CLI was missing the COMPOSE_FILE_* defaults, so
overlays such as supabase.labels.yml would not have been loaded at all.
2026-09-03 18:39:23 +02:00
Yann Autissier 5b4db64114 add the pure bash core: core, str, naming and tags
Ported from make/utils.mk, make/def.mk, make/apps/def.docker.mk and
make/apps/def.mk, with the examples that sat as dead comments in
make/apps/def.mk turned into actual assertions.

Two deviations from the make macros are pinned and documented in DELTAS.md;
both only show up on code paths nothing uses.
2026-09-03 18:26:00 +02:00
Yann Autissier 3a64c47260 move the stack catalogue out of the framework
stack/ and docker/ now live in the myos-stacks project (extracted with their
history). What the framework itself needs stays here:
- stack/myos/*.yml            -> share/compose/
- docker/myos/                -> share/docker/myos/
- docker/compose/             -> dropped, docker compose >= 2.24.4 is now required

Residues fixed along the way: DOCKER_IMAGES scanned a hardcoded ./docker,
include.mk filtered a hardcoded stack/*.mk, and docker-image-myos expanded an
undefined MYOS_DOCKER_IMAGES on every up/build.

README and CHANGELOG rewritten: they still documented make host,
host-certbot-* and user-config, all removed when the catalogue was split out.
2026-09-03 18:21:21 +02:00
Yann Autissier 1d44a2ff32 add shellspec golden harness for the legacy make engine
- spec/golden/cases.txt: 69 black-box cases (CLI wrapper mode and make
  include mode) run in a hermetic sandbox with a mocked docker
- spec/golden/expected/*.txt recorded from this engine (baseline)
- make test / test-golden / golden-record / lint dev targets
2026-09-03 14:35:58 +02:00