feat: the first boot asks the fully qualified domain name #36

Merged
marcos-mendez merged 1 commit from feat/first-boot-fqdn into master 2026-10-02 19:29:24 -03:00
marcos-mendez commented 2026-10-02 18:55:08 -03:00 (Migrated from github.com)

What. A new interactive first boot hook, 31fqdn, asks the machine's fully qualified domain name, prefilled with the name the machine has (pct create --hostname; a dotted name as it is). On Apply, /etc/keel/instance.yaml (or the file 00declarative read) records instance.hostname, instance.fqdn and tls.acme.domains: [name] when it declares no domain yet (tls.acme.enabled is never touched; a hostname declared beside the unchanged name is kept; a description the answer does not change is not rewritten, and the file keeps its mode); then the first label becomes the hostname, set the way 09hostname sets it (the rename is now lib/hostname.sh, sourced by both hooks, matching the old name literally as a whole name or a first label, where the sed matched it inside any word), and /etc/hosts gets the entry that makes hostname -f answer the name, rewriting the line pct wrote for the host where it stands. The description comes first so a step that fails leaves one saying what the machine should be. A single label is kept as the hostname alone after a notice that no certificate can be requested without a domain; an empty answer keeps what the machine has. Lower case labels of letters, digits and dashes; anything else is refused and asked again. FQDN preseeds it (instance.fqdn renders it, so a described machine is not asked), FQDN=SKIP asks nothing.

Why. The first boot never asked a domain, so confconsole's Let's Encrypt screen had nothing to offer but example.com. The maintainer wants confconsole driven by the spec: this records the name there (Keel-Linux/confconsole#22 reads it).

How the spec is written. keel exposes no writer (no command, no API); confconsole's keelcli.py is the only one, not importable from here. libinithooks/fqdn.py follows it: PyYAML (sort_keys=False, so the rest of the file is kept in order; comments do not survive, as with every confconsole write), a copy beside the file, keel spec validate --no-secret-files on that copy when keel is installed, then os.replace. Without keel the copy is moved in after this hook's own domain check.

Decisions to note. Numbered 31: an interactive hook must be 30 or above (README; run waits for the boot from 30), and 30rootpass stays the first screen. The hostname written is the first label, never the dotted name (as keel apply does). The reader now accepts tls.acme.agree_tos, which keel's spec has and inithooks refused.

Tested. tests/test_fqdn.py (50) and tests/test_fqdn_cli.py (26) on the fake dialog, keel a stub on PATH; tests/test-hostname.bats (8) and tests/test-fqdn.bats (14), the last three running the real bin/fqdn.py with FQDN preseeded against a scratch instance.yaml and hosts file; test_dialog_brand.py covers bin/fqdn.py. Python 99 percent (both new files 100), shell 99.62 (new files 100), measured as in COVERAGE.md. The dialog texts fit 60 columns and at most 17 rows.

**What.** A new interactive first boot hook, `31fqdn`, asks the machine's fully qualified domain name, prefilled with the name the machine has (`pct create --hostname`; a dotted name as it is). On Apply, `/etc/keel/instance.yaml` (or the file `00declarative` read) records `instance.hostname`, `instance.fqdn` and `tls.acme.domains: [name]` when it declares no domain yet (`tls.acme.enabled` is never touched; a hostname declared beside the unchanged name is kept; a description the answer does not change is not rewritten, and the file keeps its mode); then the first label becomes the hostname, set the way `09hostname` sets it (the rename is now `lib/hostname.sh`, sourced by both hooks, matching the old name literally as a whole name or a first label, where the sed matched it inside any word), and `/etc/hosts` gets the entry that makes `hostname -f` answer the name, rewriting the line pct wrote for the host where it stands. The description comes first so a step that fails leaves one saying what the machine should be. A single label is kept as the hostname alone after a notice that no certificate can be requested without a domain; an empty answer keeps what the machine has. Lower case labels of letters, digits and dashes; anything else is refused and asked again. `FQDN` preseeds it (`instance.fqdn` renders it, so a described machine is not asked), `FQDN=SKIP` asks nothing. **Why.** The first boot never asked a domain, so confconsole's Let's Encrypt screen had nothing to offer but example.com. The maintainer wants confconsole driven by the spec: this records the name there (Keel-Linux/confconsole#22 reads it). **How the spec is written.** keel exposes no writer (no command, no API); confconsole's `keelcli.py` is the only one, not importable from here. `libinithooks/fqdn.py` follows it: PyYAML (`sort_keys=False`, so the rest of the file is kept in order; comments do not survive, as with every confconsole write), a copy beside the file, `keel spec validate --no-secret-files` on that copy when keel is installed, then `os.replace`. Without keel the copy is moved in after this hook's own domain check. **Decisions to note.** Numbered 31: an interactive hook must be 30 or above (README; `run` waits for the boot from 30), and `30rootpass` stays the first screen. The hostname written is the first label, never the dotted name (as `keel apply` does). The reader now accepts `tls.acme.agree_tos`, which keel's spec has and inithooks refused. **Tested.** `tests/test_fqdn.py` (50) and `tests/test_fqdn_cli.py` (26) on the fake dialog, keel a stub on PATH; `tests/test-hostname.bats` (8) and `tests/test-fqdn.bats` (14), the last three running the real `bin/fqdn.py` with `FQDN` preseeded against a scratch `instance.yaml` and hosts file; `test_dialog_brand.py` covers `bin/fqdn.py`. Python 99 percent (both new files 100), shell 99.62 (new files 100), measured as in COVERAGE.md. The dialog texts fit 60 columns and at most 17 rows.
Sign in to join this conversation.
No description provided.