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.
This commit is contained in:
Yann Autissier
2026-09-03 20:46:28 +02:00
parent f541ca418b
commit 55fae625d6
33 changed files with 571 additions and 25 deletions
+4 -2
View File
@@ -7,7 +7,7 @@ stack/<name>/<name>.yml the services
stack/<name>/<name>.local.yml what only makes sense on a workstation (published ports…)
stack/<name>/<name>.labels.yml the registrator labels, so routing stays optional
stack/<name>/<name>.env plain settings: versions, defaults
stack/<name>/<name>.sh computed settings (fabio tags), no make needed
stack/<name>/<name>.sh lazy defaults (fabio tags), no make needed
stack/<name>/.env.dist the variables it expects, with defaults
stack/<name>/README.md what it is and what it needs
```
@@ -54,7 +54,9 @@ lib/core.sh logging, exit codes, dry run
lib/str.sh strings and version comparison
lib/naming.sh project names, networks, user identity
lib/stack.sh stack path, references, overlays, groups
lib/config.sh dotenv, variables of the compose files
lib/var.sh variable resolution and lazy defaults
lib/config.sh dotenv, templates, variables of the compose files
lib/hooks.sh the per-stack .env and .sh
lib/compose.sh finding and calling docker compose
lib/tags.sh fabio tags
lib/cmd/<x>.sh one file per command
+1
View File
@@ -26,6 +26,7 @@ myos [options] <command> [stack...] [VAR=value...] [-- args...]
| `build` / `pull` | images |
| `ls [--groups]` | the stacks and groups myos can see |
| `env [VAR...]` | resolved variables |
| `env-update` | fill the `.env` from the `.env.dist` templates |
| `doctor` | check the installation |
| `version` | the myos version |
+36 -8
View File
@@ -110,21 +110,49 @@ A stack keeps its own settings next to its compose files:
| `<name>.sh` | values that have to be computed |
| `<name>.mk` | the legacy make snippet; still read for its groups |
A `.sh` hook is sourced with the myos helpers available, and sets variables
directly. This is what lets a stack work on a machine that has no make:
A `.sh` hook declares **lazy defaults**: a function named
`myos_default_<VARIABLE>`, called only when the variable has no value, and
called again at each reference. That is the make `?=` on a recursive variable,
in shell:
```sh
# stack/host/fabio.sh
HOST_FABIO_VERSION=${HOST_FABIO_VERSION:-1.6.3}
HOST_FABIO_SERVICE_9998_NAME=${HOST_FABIO_SERVICE_9998_NAME:-fabio}
HOST_FABIO_SERVICE_9998_AUTH=${HOST_FABIO_SERVICE_9998_AUTH:-default}
HOST_FABIO_SERVICE_9998_TAGS=${HOST_FABIO_SERVICE_9998_TAGS:-$(myos_tagprefix HOST_FABIO 9998)}
myos_default_HOST_FABIO_VERSION() { printf '1.6.3'; }
myos_default_HOST_FABIO_SERVICE_9998_NAME() { printf 'fabio'; }
myos_default_HOST_FABIO_SERVICE_9998_AUTH() { printf 'default'; }
myos_default_HOST_FABIO_SERVICE_9998_TAGS() { myos_tagprefix HOST_FABIO 9998; }
```
Always write `${VAR:-default}` so the environment and the `.env` still win.
Two things follow, and they are the point:
- a value given anywhere (environment, `.env`, command line) wins over the
default, without the hook having to say so;
- the default is computed against the values current **at the moment it is
read**, so a `DOMAIN` set in a `.env` loaded later is taken into account.
The prefix is not decoration: a bare function named `host` or `test` would be
indistinguishable from the program of that name, and myos would run it.
Helpers available in a hook: `myos_tagprefix`, `myos_urlprefix`, `myos_uri`,
`myos_url`, `myos_envprefix`, `myos_servicenvs`, `myos_var`, `myos_lower`,
`myos_upper`.
`myos_upper`, and `myos_default NAME 'body'` when the name is built at run time.
## Templates: .env.dist
A stack may ship a `.env.dist` listing the variables it expects, with their
defaults. `myos env-update` writes the missing ones into the `.env`, expanding
`${VAR}` against the current values and running `$(command)`:
```sh
# stack/demo/.env.dist
DEMO_IMAGE=alpine:${DEMO_VERSION}
DEMO_VERSION=3.20
DEMO_SECRET=$(openssl rand -hex 16)
```
A line may refer to a variable defined further down. A variable that already
has a value keeps it: the `.env` records choices, it never overwrites them, and
running the command twice changes nothing.
## Groups