diff --git a/webinstaller/app.py b/webinstaller/app.py index b062059..e7e3dde 100644 --- a/webinstaller/app.py +++ b/webinstaller/app.py @@ -33,6 +33,46 @@ settings = { HOSTNAME_RE = re.compile(r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$") USERNAME_RE = re.compile(r"^[a-z_][a-z0-9_-]{0,31}$") +# Ordered phase markers for the install progress bar. Each tuple is +# (substring to search for in the archinstall log, progress percent when +# reached, user-facing label). Pick the furthest phase whose marker is +# present in the log. If archinstall changes its stdout wording the bar +# stalls on the last recognized phase — the install itself keeps going. +PROGRESS_PHASES = [ + ("Wiping partitions", 8, "Preparing your disk"), + ("Creating partitions", 12, "Creating partitions"), + ("Starting installation", 15, "Starting installation"), + ("Waiting for", 18, "Syncing time and packages"), + ("Installing packages: ['base'", 25, "Installing the base system (this takes a while)"), + ("Adding bootloader", 65, "Setting up boot"), + ("Installing packages: ['efibootmgr'", 70, "Setting up boot"), + ("Installing packages: ['docker'", 80, "Installing your apps"), + ("Enabling service", 90, "Turning on services"), + ("Updating /mnt/etc/fstab", 95, "Almost done"), + ("Installation completed without any errors", 100, "Done!"), +] + +PROGRESS_ERROR_MARKERS = ("Traceback (most recent call last)", "archinstall: error:") + + +def parse_install_progress(log): + percent = 2 + phase = "Starting up…" + for marker, pct, label in PROGRESS_PHASES: + if marker in log: + percent = pct + phase = label + + if percent >= 100: + status = "done" + elif any(m in log for m in PROGRESS_ERROR_MARKERS): + status = "error" + phase = "Installation failed — open Show details below" + else: + status = "running" + + return {"percent": percent, "phase": phase, "status": status} + def validate_step1(form): errors = [] @@ -102,6 +142,11 @@ def build_archinstall_config(s): "packages": ["docker", "docker-compose", "vim", "git", "htop", "curl"], "profile": {"type": "server"}, "services": ["docker"], + # Add user to the docker group post-install. We can't put "docker" in + # the user's `groups` at create-time because archinstall creates users + # before pacstrapping the extras, so the docker group doesn't exist + # yet. custom_commands runs at the very end. + "custom_commands": [f"gpasswd -a {s['username']} docker"], "network_config": {"type": "iso"}, "ssh": True, "audio_config": None, @@ -123,7 +168,7 @@ def build_archinstall_creds(s): "username": s["username"], "!password": s["password"], "sudo": True, - "groups": ["docker"], + "groups": [], } ], } @@ -216,8 +261,12 @@ def install_run(): @app.route("/install/log") def install_log_view(): - log = INSTALL_LOG.read_text() if INSTALL_LOG.exists() else "(waiting for install to start)\n" - return render_template("install/log.html", log=log) + log = INSTALL_LOG.read_text() if INSTALL_LOG.exists() else "" + return render_template( + "install/log.html", + log=log, + progress=parse_install_progress(log), + ) if __name__ == "__main__": diff --git a/webinstaller/static/style.css b/webinstaller/static/style.css index 2b5ed81..702b1d7 100644 --- a/webinstaller/static/style.css +++ b/webinstaller/static/style.css @@ -337,6 +337,45 @@ select:focus { margin: 0; } +.progress { + background: var(--bg-subtle); + border: 1px solid var(--border); + border-radius: 999px; + height: 12px; + overflow: hidden; + margin: 1.5rem 0 0.5rem; +} +.progress-bar { + height: 100%; + background: linear-gradient( + 90deg, + var(--accent), + color-mix(in srgb, var(--accent) 55%, #fff) + ); + transition: width 0.6s ease; + border-radius: 999px; +} +.progress-bar-error { background: var(--danger); } +.progress-bar-done { background: var(--success); } +.progress-phase { + margin: 0.3rem 0 1.5rem; + color: var(--fg-muted); + font-size: 0.95rem; +} + +.log-details { + margin-top: 1.5rem; +} +.log-details summary { + cursor: pointer; + font-size: 0.9rem; + color: var(--fg-muted); + user-select: none; + padding: 0.25rem 0; +} +.log-details summary:hover { color: var(--fg); } +.log-details[open] summary { margin-bottom: 0.6rem; } + /* ── Footer ─────────────────────────────────────────────────── */ .site-footer { diff --git a/webinstaller/templates/install/log.html b/webinstaller/templates/install/log.html index c50b339..392ca3a 100644 --- a/webinstaller/templates/install/log.html +++ b/webinstaller/templates/install/log.html @@ -1,12 +1,30 @@ {% extends "base.html" %} {% block title %}Installing… · Furtka Installer{% endblock %} -{% block head_extra %}{% endblock %} +{% block head_extra %} +{% if progress.status == "running" %}{% endif %} +{% endblock %} {% block step_indicator %}Installing{% endblock %} {% block content %} +{% if progress.status == "done" %} +
Installation finished. Remove the USB / eject the installer image, then reboot.
+{% elif progress.status == "error" %} +Something went wrong. Open the details below and share them so we can help.
+{% else %}This page reloads every 3 seconds. Don't close it. Don't power off.
+This takes a few minutes. Don't close this page or power off the machine.
+{% endif %} -{{ log }}
+{{ progress.phase }} · {{ progress.percent }}%
+ +{{ log or "(waiting for install to start)" }}
+