feat: post-install bootstrap — land in Furtka after reboot
Installs caddy + avahi + nss-mdns on the target and writes a small landing page, live status tiles (uptime / docker version / free disk via furtka-status.timer), and a console welcome banner — all via archinstall's custom_commands so the payload travels with the user_configuration.json. After reboot `http://<hostname>.local` serves a Furtka-branded page on :80 instead of the bare Arch login. No Authentik / no app store yet — demo shell for the real post- install work (Robert's area). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dfdbdd69aa
commit
8ed1d82fd3
4 changed files with 408 additions and 9 deletions
|
|
@ -112,7 +112,7 @@ None of these nail the "your dad can set this up" experience. The installer wiza
|
||||||
- [x] **Rebrand GRUB menu** — `iso/build.sh` rewrites "Arch Linux install medium" → "Furtka Live Installer" across GRUB, syslinux, and systemd-boot configs; default entry marked `(Recommended)`.
|
- [x] **Rebrand GRUB menu** — `iso/build.sh` rewrites "Arch Linux install medium" → "Furtka Live Installer" across GRUB, syslinux, and systemd-boot configs; default entry marked `(Recommended)`.
|
||||||
- [x] **Wizard: account form → drive picker → overview → archinstall** — S1 collects hostname/user/password/language with validation, S2 picks boot drive, overview confirms, `/install/run` writes `user_configuration.json` + `user_credentials.json` (0600) and execs `archinstall --silent` against its 4.x schema (`default_layout` disk_config + `!root-password` / `!password` sentinel keys + `custom_commands` for post-install group joins). Install log page polls a JSON endpoint and renders a phase-based progress bar with a collapsible raw log. `FURTKA_DRY_RUN=1` skips the real exec for testing.
|
- [x] **Wizard: account form → drive picker → overview → archinstall** — S1 collects hostname/user/password/language with validation, S2 picks boot drive, overview confirms, `/install/run` writes `user_configuration.json` + `user_credentials.json` (0600) and execs `archinstall --silent` against its 4.x schema (`default_layout` disk_config + `!root-password` / `!password` sentinel keys + `custom_commands` for post-install group joins). Install log page polls a JSON endpoint and renders a phase-based progress bar with a collapsible raw log. `FURTKA_DRY_RUN=1` skips the real exec for testing.
|
||||||
- [x] **mDNS `proksi.local`** — hostname baked into the live ISO, avahi + nss-mdns in the package list, advertised as soon as network-online fires. The HTTPS + local-CA half of this milestone is still open below.
|
- [x] **mDNS `proksi.local`** — hostname baked into the live ISO, avahi + nss-mdns in the package list, advertised as soon as network-online fires. The HTTPS + local-CA half of this milestone is still open below.
|
||||||
- [ ] **Base OS post-install** — what Furtka actually looks like *after* the wizard writes config + reboots: Caddy + Authentik + app store. Robert's area.
|
- [x] **Base OS post-install (demo level)** — after reboot the installed system comes up with Caddy on `:80` serving a Furtka landing page (welcome + live uptime/Docker/disk tiles), the console shows a banner pointing at `http://<hostname>.local`, and `nss-mdns` makes that URL resolve on the LAN. Written by `webinstaller/app.py`'s `_post_install_commands` via archinstall's `custom_commands`. No Authentik / no app store yet — that's the next milestone (Robert's area).
|
||||||
- [ ] Installer wizard screens S3–S7 — per-device purpose, network, domain, SSL, diagnostic. S5/S6 blocked on managed-gateway DNS infra not yet built.
|
- [ ] Installer wizard screens S3–S7 — per-device purpose, network, domain, SSL, diagnostic. S5/S6 blocked on managed-gateway DNS infra not yet built.
|
||||||
- [ ] `https://proksi.local` with a local CA (today: plain HTTP at `http://proksi.local:5000`)
|
- [ ] `https://proksi.local` with a local CA (today: plain HTTP at `http://proksi.local:5000`)
|
||||||
- [ ] Caddy + Authentik wired into first-boot bootstrap
|
- [ ] Caddy + Authentik wired into first-boot bootstrap
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,16 @@ mDNS (`proksi.local`) via avahi is installed but not yet wired. First milestone
|
||||||
5. Find its IP in Proxmox's VM summary (or your router's DHCP table)
|
5. Find its IP in Proxmox's VM summary (or your router's DHCP table)
|
||||||
6. Open `http://<vm-ip>:5000` — the existing 3-screen wizard should be there
|
6. Open `http://<vm-ip>:5000` — the existing 3-screen wizard should be there
|
||||||
|
|
||||||
|
## What you see after install + reboot
|
||||||
|
|
||||||
|
Once `archinstall` finishes and you click **Reboot now**, the VM comes up into the installed system. No more port `:5000` — the wizard ISO is gone. Instead:
|
||||||
|
|
||||||
|
- **Console**: agetty shows `Furtka is ready. Open http://<hostname>.local …` with the IP fallback underneath.
|
||||||
|
- **Browser** at `http://<hostname>.local` (default `http://proksi.local`): Caddy-served landing page with three live status tiles (uptime, Docker version, free disk) refreshed every 30 s by `furtka-status.timer`.
|
||||||
|
- **SSH**: `ssh <user>@<hostname>.local` works; `docker ps` works without `sudo` because the user is in the `docker` group.
|
||||||
|
|
||||||
|
This is a demo shell — no Authentik, no app store yet. The landing page lives at `/srv/furtka/www/`, served by Caddy on `:80` per `/etc/caddy/Caddyfile`. All of this is written into the target by `webinstaller/app.py`'s `_post_install_commands` via archinstall's `custom_commands`.
|
||||||
|
|
||||||
## Known rough edges
|
## Known rough edges
|
||||||
|
|
||||||
- **Disk space**: the first time you build on a fresh host, the squashfs/xorriso steps need ~15 GB free. If the host's LVM-root is smaller, `xorriso` silently dies at the very end with "Image size exceeds free space on media".
|
- **Disk space**: the first time you build on a fresh host, the squashfs/xorriso steps need ~15 GB free. If the host's LVM-root is smaller, `xorriso` silently dies at the very end with "Image size exceeds free space on media".
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,51 @@ def test_build_archinstall_config_uses_selected_locale(monkeypatch):
|
||||||
# `!password` sentinel; config only carries a gpasswd in custom_commands
|
# `!password` sentinel; config only carries a gpasswd in custom_commands
|
||||||
# so the user lands in the docker group after docker is pacstrapped.
|
# so the user lands in the docker group after docker is pacstrapped.
|
||||||
assert "users" not in cfg
|
assert "users" not in cfg
|
||||||
assert any("gpasswd -a u docker" in c for c in cfg["custom_commands"])
|
assert cfg["custom_commands"][0] == "gpasswd -a u docker"
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_archinstall_config_includes_post_install_bootstrap(monkeypatch):
|
||||||
|
# The installed system should come up with a Furtka landing page at
|
||||||
|
# http://<hostname>.local. That means caddy + avahi pacstrapped, the
|
||||||
|
# matching services enabled, a Caddyfile + index.html written into the
|
||||||
|
# target rootfs, and nss-mdns spliced into nsswitch.conf.
|
||||||
|
import app as app_module
|
||||||
|
|
||||||
|
monkeypatch.setattr(app_module, "build_disk_config", lambda d: {"stubbed_device": d})
|
||||||
|
|
||||||
|
cfg = build_archinstall_config(
|
||||||
|
{
|
||||||
|
"hostname": "heimserver",
|
||||||
|
"username": "u",
|
||||||
|
"password": "pw12345678",
|
||||||
|
"language": "en",
|
||||||
|
"boot_drive": "/dev/sda",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for pkg in ("caddy", "avahi", "nss-mdns"):
|
||||||
|
assert pkg in cfg["packages"]
|
||||||
|
for svc in ("caddy", "avahi-daemon", "furtka-welcome", "furtka-status.timer"):
|
||||||
|
assert svc in cfg["services"]
|
||||||
|
|
||||||
|
joined = "\n".join(cfg["custom_commands"])
|
||||||
|
for path in (
|
||||||
|
"/etc/caddy/Caddyfile",
|
||||||
|
"/srv/furtka/www/index.html",
|
||||||
|
"/srv/furtka/www/style.css",
|
||||||
|
"/srv/furtka/www/status.json",
|
||||||
|
"/usr/local/bin/furtka-status",
|
||||||
|
"/usr/local/bin/furtka-welcome",
|
||||||
|
"/etc/systemd/system/furtka-status.service",
|
||||||
|
"/etc/systemd/system/furtka-status.timer",
|
||||||
|
"/etc/systemd/system/furtka-welcome.service",
|
||||||
|
):
|
||||||
|
assert path in joined, f"expected {path} to be written by custom_commands"
|
||||||
|
|
||||||
|
assert "mdns_minimal" in joined
|
||||||
|
assert "nsswitch.conf" in joined
|
||||||
|
# The chosen hostname is pinned into the static HTML at install-time.
|
||||||
|
assert "s/__HOSTNAME__/heimserver/g" in joined
|
||||||
|
|
||||||
|
|
||||||
def test_build_archinstall_creds_uses_archinstall_sentinel_keys():
|
def test_build_archinstall_creds_uses_archinstall_sentinel_keys():
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
@ -130,6 +131,327 @@ def build_disk_config(boot_drive):
|
||||||
return layout.json()
|
return layout.json()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Post-install bootstrap payload
|
||||||
|
#
|
||||||
|
# Written into the target system via archinstall's `custom_commands` so that
|
||||||
|
# after reboot the user lands in "Furtka": Caddy serves a branded landing
|
||||||
|
# page + live status tiles on :80, avahi advertises proksi.local, and the
|
||||||
|
# console shows a welcome banner pointing at the URL.
|
||||||
|
#
|
||||||
|
# Files are shipped inline (base64-encoded) rather than copied from the live
|
||||||
|
# ISO because archinstall's chroot can't see the live filesystem. Payload is
|
||||||
|
# small (~200 lines across 9 files) so this is cheaper than a tarball dance.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_CADDYFILE = """\
|
||||||
|
# Serves the Furtka landing page + status.json on :80. Static only for now;
|
||||||
|
# reverse_proxy / TLS / auth come later when Authentik is wired in.
|
||||||
|
:80 {
|
||||||
|
\troot * /srv/furtka/www
|
||||||
|
\tfile_server
|
||||||
|
\tencode gzip
|
||||||
|
\tlog {
|
||||||
|
\t\toutput stdout
|
||||||
|
\t}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_INDEX_HTML = """\
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Furtka</title>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<h1>Welcome to Furtka</h1>
|
||||||
|
<p class="lead">Your home server is ready.</p>
|
||||||
|
<p class="host">Running on <code>__HOSTNAME__</code></p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="status">
|
||||||
|
<h2>System status</h2>
|
||||||
|
<div class="tiles">
|
||||||
|
<div class="tile">
|
||||||
|
<span class="label">Uptime</span>
|
||||||
|
<span class="value" id="uptime">—</span>
|
||||||
|
</div>
|
||||||
|
<div class="tile">
|
||||||
|
<span class="label">Docker</span>
|
||||||
|
<span class="value" id="docker">—</span>
|
||||||
|
</div>
|
||||||
|
<div class="tile">
|
||||||
|
<span class="label">Free disk</span>
|
||||||
|
<span class="value" id="disk">—</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="updated">Updated <span id="updated">—</span></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="soon">
|
||||||
|
<h2>App store</h2>
|
||||||
|
<p>Coming soon — one-click installs for Nextcloud, Jellyfin, and friends.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Furtka · <a href="https://furtka.org">furtka.org</a></p>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function refresh() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/status.json', {cache: 'no-store'});
|
||||||
|
if (!r.ok) return;
|
||||||
|
const s = await r.json();
|
||||||
|
document.getElementById('uptime').textContent = s.uptime || '—';
|
||||||
|
document.getElementById('docker').textContent = s.docker_version || '—';
|
||||||
|
document.getElementById('disk').textContent = s.disk_free || '—';
|
||||||
|
document.getElementById('updated').textContent = s.updated_at || '—';
|
||||||
|
} catch (e) {
|
||||||
|
/* next tick will retry */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
setInterval(refresh, 15000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
_STYLE_CSS = """\
|
||||||
|
:root {
|
||||||
|
--bg: #0f1115;
|
||||||
|
--fg: #e8eaed;
|
||||||
|
--muted: #9aa0a6;
|
||||||
|
--accent: #6ee7b7;
|
||||||
|
--card: #1a1d24;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
main { max-width: 780px; margin: 0 auto; padding: 4rem 1.5rem; }
|
||||||
|
header h1 { margin: 0 0 0.5rem; font-size: 2.5rem; }
|
||||||
|
.lead { font-size: 1.25rem; color: var(--muted); margin: 0 0 0.25rem; }
|
||||||
|
.host { color: var(--muted); margin: 0 0 3rem; }
|
||||||
|
.host code {
|
||||||
|
background: var(--card);
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
section h2 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--muted);
|
||||||
|
margin: 2rem 0 1rem;
|
||||||
|
}
|
||||||
|
.tiles {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.tile {
|
||||||
|
background: var(--card);
|
||||||
|
padding: 1.25rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.tile .label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
.tile .value { font-size: 1.25rem; margin-top: 0.5rem; }
|
||||||
|
.updated { font-size: 0.85rem; color: var(--muted); margin-top: 1rem; }
|
||||||
|
.soon {
|
||||||
|
background: var(--card);
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
margin-top: 4rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid #2a2e36;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
footer a { color: var(--accent); }
|
||||||
|
"""
|
||||||
|
|
||||||
|
_STATUS_JSON_PLACEHOLDER = """\
|
||||||
|
{
|
||||||
|
"hostname": "",
|
||||||
|
"uptime": "starting…",
|
||||||
|
"docker_version": "starting…",
|
||||||
|
"disk_free": "starting…",
|
||||||
|
"updated_at": ""
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_FURTKA_STATUS_SH = """\
|
||||||
|
#!/bin/bash
|
||||||
|
# Writes /srv/furtka/www/status.json with current system stats. Fired by
|
||||||
|
# furtka-status.timer every 30s; also runs once 10s after boot.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
out=/srv/furtka/www/status.json
|
||||||
|
tmp=$(mktemp)
|
||||||
|
|
||||||
|
hostname=$(cat /etc/hostname)
|
||||||
|
uptime=$(uptime -p 2>/dev/null | sed 's/^up //' || echo unknown)
|
||||||
|
if command -v docker >/dev/null 2>&1; then
|
||||||
|
docker_version=$(docker --version 2>/dev/null \
|
||||||
|
| awk '{print $3}' | tr -d ',' || echo unavailable)
|
||||||
|
else
|
||||||
|
docker_version=unavailable
|
||||||
|
fi
|
||||||
|
disk_free=$(df -h / 2>/dev/null | awk 'NR==2 {print $4 " free of " $2}' || echo unknown)
|
||||||
|
updated_at=$(date -Iseconds)
|
||||||
|
|
||||||
|
cat > "$tmp" <<EOF
|
||||||
|
{
|
||||||
|
"hostname": "$hostname",
|
||||||
|
"uptime": "$uptime",
|
||||||
|
"docker_version": "$docker_version",
|
||||||
|
"disk_free": "$disk_free",
|
||||||
|
"updated_at": "$updated_at"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mv "$tmp" "$out"
|
||||||
|
chmod 644 "$out"
|
||||||
|
"""
|
||||||
|
|
||||||
|
_FURTKA_STATUS_SERVICE = """\
|
||||||
|
[Unit]
|
||||||
|
Description=Refresh Furtka system status JSON
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/furtka-status
|
||||||
|
"""
|
||||||
|
|
||||||
|
_FURTKA_STATUS_TIMER = """\
|
||||||
|
[Unit]
|
||||||
|
Description=Refresh Furtka system status every 30s
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=10s
|
||||||
|
OnUnitActiveSec=30s
|
||||||
|
AccuracySec=5s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
"""
|
||||||
|
|
||||||
|
_FURTKA_WELCOME_SH = """\
|
||||||
|
#!/bin/bash
|
||||||
|
# Regenerates /etc/issue on the installed system so the console tells the
|
||||||
|
# user which URL to open. Mirrors the live-ISO furtka-update-issue pattern.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
hostname=$(cat /etc/hostname)
|
||||||
|
ip=$(ip -4 -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1)
|
||||||
|
|
||||||
|
{
|
||||||
|
echo
|
||||||
|
echo " Furtka is ready."
|
||||||
|
echo
|
||||||
|
echo " Open in a browser on another device on your network:"
|
||||||
|
echo
|
||||||
|
echo " http://${hostname}.local (easy — try this first)"
|
||||||
|
if [ -n "$ip" ]; then
|
||||||
|
echo " http://${ip} (fallback if the first doesn't work)"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
} > /etc/issue
|
||||||
|
|
||||||
|
agetty --reload 2>/dev/null || true
|
||||||
|
"""
|
||||||
|
|
||||||
|
_FURTKA_WELCOME_SERVICE = """\
|
||||||
|
[Unit]
|
||||||
|
Description=Furtka console welcome banner
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/furtka-welcome
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _write_file_cmd(path, content, mode=None):
|
||||||
|
"""Shell command that recreates `path` with `content` inside the chroot.
|
||||||
|
|
||||||
|
Uses base64 so we don't have to worry about bash / JSON / archinstall
|
||||||
|
quoting the payload through three layers of shell. `base64` is part of
|
||||||
|
coreutils and always available in the target system.
|
||||||
|
"""
|
||||||
|
b64 = base64.b64encode(content.encode()).decode()
|
||||||
|
parent = path.rsplit("/", 1)[0]
|
||||||
|
cmd = f"mkdir -p {parent} && printf %s {b64} | base64 -d > {path}"
|
||||||
|
if mode is not None:
|
||||||
|
cmd += f" && chmod {mode} {path}"
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def _post_install_commands(hostname):
|
||||||
|
# nss-mdns: splice `mdns_minimal [NOTFOUND=return]` before `resolve` on
|
||||||
|
# the hosts line so `*.local` works from the installed system too. Guarded
|
||||||
|
# so a re-run (or a future Arch default that already ships mdns) is a
|
||||||
|
# no-op instead of double-injecting.
|
||||||
|
nss_sed = (
|
||||||
|
"grep -q 'mdns_minimal' /etc/nsswitch.conf || "
|
||||||
|
"sed -i '/^hosts:/ s/resolve/mdns_minimal [NOTFOUND=return] resolve/' "
|
||||||
|
"/etc/nsswitch.conf"
|
||||||
|
)
|
||||||
|
# Pin the chosen hostname into the static HTML at install-time so the
|
||||||
|
# landing page doesn't need a server-side template.
|
||||||
|
hostname_sed = (
|
||||||
|
f"sed -i 's/__HOSTNAME__/{hostname}/g' /srv/furtka/www/index.html"
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
_write_file_cmd("/etc/caddy/Caddyfile", _CADDYFILE),
|
||||||
|
_write_file_cmd("/srv/furtka/www/index.html", _INDEX_HTML),
|
||||||
|
_write_file_cmd("/srv/furtka/www/style.css", _STYLE_CSS),
|
||||||
|
_write_file_cmd("/srv/furtka/www/status.json", _STATUS_JSON_PLACEHOLDER),
|
||||||
|
_write_file_cmd("/usr/local/bin/furtka-status", _FURTKA_STATUS_SH, mode="755"),
|
||||||
|
_write_file_cmd("/usr/local/bin/furtka-welcome", _FURTKA_WELCOME_SH, mode="755"),
|
||||||
|
_write_file_cmd(
|
||||||
|
"/etc/systemd/system/furtka-status.service", _FURTKA_STATUS_SERVICE
|
||||||
|
),
|
||||||
|
_write_file_cmd(
|
||||||
|
"/etc/systemd/system/furtka-status.timer", _FURTKA_STATUS_TIMER
|
||||||
|
),
|
||||||
|
_write_file_cmd(
|
||||||
|
"/etc/systemd/system/furtka-welcome.service", _FURTKA_WELCOME_SERVICE
|
||||||
|
),
|
||||||
|
nss_sed,
|
||||||
|
hostname_sed,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def build_archinstall_config(s):
|
def build_archinstall_config(s):
|
||||||
return {
|
return {
|
||||||
"archinstall-language": "English",
|
"archinstall-language": "English",
|
||||||
|
|
@ -139,14 +461,37 @@ def build_archinstall_config(s):
|
||||||
"disk_config": build_disk_config(s["boot_drive"]),
|
"disk_config": build_disk_config(s["boot_drive"]),
|
||||||
"hostname": s["hostname"],
|
"hostname": s["hostname"],
|
||||||
"kernels": ["linux"],
|
"kernels": ["linux"],
|
||||||
"packages": ["docker", "docker-compose", "vim", "git", "htop", "curl"],
|
"packages": [
|
||||||
|
"docker",
|
||||||
|
"docker-compose",
|
||||||
|
"vim",
|
||||||
|
"git",
|
||||||
|
"htop",
|
||||||
|
"curl",
|
||||||
|
# Base OS post-install (landing page + mDNS on installed system).
|
||||||
|
"caddy",
|
||||||
|
"avahi",
|
||||||
|
"nss-mdns",
|
||||||
|
],
|
||||||
"profile": {"type": "server"},
|
"profile": {"type": "server"},
|
||||||
"services": ["docker"],
|
"services": [
|
||||||
# Add user to the docker group post-install. We can't put "docker" in
|
"docker",
|
||||||
# the user's `groups` at create-time because archinstall creates users
|
# Base OS post-install services. `furtka-welcome` refreshes
|
||||||
# before pacstrapping the extras, so the docker group doesn't exist
|
# /etc/issue with the landing-page URL; `furtka-status.timer`
|
||||||
# yet. custom_commands runs at the very end.
|
# keeps /srv/furtka/www/status.json fresh for the dashboard.
|
||||||
"custom_commands": [f"gpasswd -a {s['username']} docker"],
|
"caddy",
|
||||||
|
"avahi-daemon",
|
||||||
|
"furtka-welcome",
|
||||||
|
"furtka-status.timer",
|
||||||
|
],
|
||||||
|
# `gpasswd -a <user> docker` has to stay first — adds the user to
|
||||||
|
# the docker group once the group exists (archinstall creates users
|
||||||
|
# before pacstrapping extras). After that we drop the Furtka landing
|
||||||
|
# page, status timer, and welcome banner into place.
|
||||||
|
"custom_commands": [
|
||||||
|
f"gpasswd -a {s['username']} docker",
|
||||||
|
*_post_install_commands(s["hostname"]),
|
||||||
|
],
|
||||||
"network_config": {"type": "iso"},
|
"network_config": {"type": "iso"},
|
||||||
"ssh": True,
|
"ssh": True,
|
||||||
"audio_config": None,
|
"audio_config": None,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue