{% extends "ui/_layout.html" %} {% block title %}Settings - bty-web{% endblock %} {% block content %}

Settings

Authentication

bty-web is gated by the OS password of the {{ service_user }} account. To rotate the credential, run sudo passwd {{ service_user }} on the server. Sessions are server-signed cookies (Starlette's SessionMiddleware) with a sliding 7-day TTL. To invalidate every existing session in one shot, rotate the session secret: sudo rm /var/lib/bty/session-secret && sudo systemctl restart bty-web. The restart regenerates the secret on startup; no reboot needed.

PXE proxy-DHCP

When active, bty-pxe-proxy answers PXE / HTTP-Boot DHCP queries on the chosen interface and bty-tftp serves the iPXE binaries. Pick the interface facing the target machines, supply the subnet they're on, and click Activate. Deactivate stops and disables both daemons. The diagnostic panel below shows each daemon's current state.

{% if missing_netboot_artifacts %} {# Netboot env incomplete: activating PXE now would let clients chain into iPXE but they'd 404 fetching the kernel. Surface the precondition prominently so the operator sees it before they activate (or right after, when the activate handler's warning flash arrives). #}
Netboot environment incomplete. Missing under {{ boot_root }}: {% for name in missing_netboot_artifacts %} {{ name }}{% if not loop.last %}, {% endif %} {% endfor %}. PXE proxy-DHCP can still be activated, but clients chaining into bty's iPXE script will 404 on the kernel fetch until these files are present.
Boot artifacts
{% endif %} {% if pxe %} {% if pxe_iface_present %} {# Healthy active state: configured interface still exists. #}
Active on interface {{ pxe.interface }}, subnet {{ pxe.subnet }}.
{% else %} {# NIC-gone state: configured interface is no longer present. Likely renamed across a reboot (USB ethernet adapters, systemd predictable-name churn). dnsmasq is failing to bind silently in the background. #}
PXE is configured for interface {{ pxe.interface }} but that interface is not currently present. dnsmasq is failing to bind. Deactivate and re-activate on a present interface below.
{% endif %} {% else %}
PXE is currently not active. Machines with boot_policy=flash won't actually be served until you activate it here.
{% endif %}

{% if pxe %}Change binding{% else %}Activate{% endif %}

{# ``form_interface`` / ``form_subnet`` are set by the activate- failed path so the operator's typed values survive the re-render. Falls through to the active config, otherwise to the first detected interface's auto-detected subnet. #} {% set pxe_iface = form_interface or (pxe.interface if (pxe and pxe_iface_present) else (interfaces[0].name if interfaces else '')) %} {% set pxe_subnet = form_subnet or (pxe.subnet if pxe else (interfaces[0].subnet if interfaces and interfaces[0].subnet else '')) %}
Network address of the segment PXE clients live on. Pre-filled from the selected interface's IPv4 address; edit if needed.

Proxy-DHCP only: bty assumes there is already a DHCP server on this segment. Full DHCP is intentionally not offered here - misconfigured rogue-DHCP would conflict with the LAN's real DHCP server. Run a dedicated DHCP daemon next to bty if you need one.

{# Per-daemon state panel for the PXE stack. Surfaces the ``systemctl is-active`` state of bty-pxe-proxy.service + bty-tftp.service as badges + lets the operator Start / Stop / Restart each daemon independently for triage. Distinct from the master Activate / Deactivate above: those are the opinionated workflow; these are the diagnostic knobs. #}

Daemons

Live state and triage knobs for the two PXE-stack daemons. Both are required for end-to-end PXE; running one without the other leaves clients with a working DHCP offer pointing at a file no one serves (or vice versa). Use these for triage; use the Activate / Deactivate flow above for normal operation.

{% for d in pxe_daemons %} {% endfor %}
Daemon State Actions
{{ d.unit }}.service {# Map the raw systemctl state to a Bootstrap badge class. ``active`` is green; ``failed`` is red; ``activating`` / ``deactivating`` are amber; everything else (``inactive``, ``unknown``, ...) is neutral grey. #} {% if d.state == 'active' %} {{ d.state }} {% elif d.state == 'failed' %} {{ d.state }} {% elif d.state in ('activating', 'deactivating', 'reloading') %} {{ d.state }} {% else %} {{ d.state }} {% endif %}

``Start`` of a daemon that doesn't see /etc/default/bty-pxe-proxy is a no-op: the unit's ConditionPathExists= guard keeps it cleanly inactive until you activate the PXE workflow above.

{# Recent structured events from the daemons (DHCP discover/offer, TFTP rrq/complete/error). Read on each page render from journald; refresh by reloading. Shows operators what targets the proxy + tftp are actually talking to, without needing SSH + journalctl. #}

Recent activity

{% if pxe_events %}
{# Reverse: newest first for at-a-glance scanning. jinja's reverse filter is fine on a small list (80 items max per the handler cap). #} {% for e in pxe_events|reverse %} {% endfor %}
Time Daemon Event Detail
{{ e.hms_utc }} {{ e.unit }} {# Map event prefix to a Bootstrap badge: offer / complete = green (good), error / giveup = red, ignore = amber, everything else (discover, rrq, ...) neutral. #} {% if e.event in ('dhcp.offer', 'tftp.complete') %} {{ e.event }} {% elif e.event in ('dhcp.error', 'tftp.error', 'tftp.giveup') %} {{ e.event }} {% elif e.event == 'dhcp.ignore' %} {{ e.event }} {% else %} {{ e.event }} {% endif %} {# Flat key=value list keeps the wire shape obvious. Operators reading this are diagnosing PXE so they want the raw fields, not a stylised view. #} {% for k, v in e.fields.items() %} {{ k }}={{ v }} {% endfor %}
{% else %}

No daemon events recorded yet. Activate PXE and trigger a target to PXE-boot; this feed populates from journald (which captures the daemons' structured JSON output).

{% endif %}
{# Recent activity for settings: PXE activate / activate-failed via the per-subject events card. #} {% with events=settings_events, title="Recent settings activity", link_to_full="/ui/events?subject_kind=settings" %} {% include "ui/_events_card.html" %} {% endwith %} {% endblock %}