diff options
| author | Petri Hienonen <petri.hienonen@gmail.com> | 2025-09-28 10:45:49 +0300 |
|---|---|---|
| committer | Petri Hienonen <petri.hienonen@gmail.com> | 2025-09-28 10:45:49 +0300 |
| commit | 650138aae6a2d6ae26f57a22056bdf0ea5fa8c77 (patch) | |
| tree | 6b90cc177702c24b399b078b0f0014fc4e3af614 | |
| download | network-650138aae6a2d6ae26f57a22056bdf0ea5fa8c77.tar.zst | |
Initial commit
| -rw-r--r-- | index.html | 80 | ||||
| -rw-r--r-- | main.go | 72 | ||||
| -rw-r--r-- | network-ui.service | 18 | ||||
| -rw-r--r-- | requirements.json | 62 |
4 files changed, 232 insertions, 0 deletions
diff --git a/index.html b/index.html new file mode 100644 index 0000000..618f523 --- /dev/null +++ b/index.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <title>Systemd-networkd Control Panel</title> + <script type="module" src="https://unpkg.com/@fluentui/web-components@2.6.1/dist/web-components.min.js"></script> + <style> + body { + font-family: Arial, sans-serif; + margin: 2em; + } + + section { + margin-bottom: 2em; + } + + pre { + background: #f5f5f5; + padding: 1em; + border-radius: 6px; + overflow-x: auto; + } + </style> +</head> + +<body> + + <h1>Systemd-networkd Control Panel</h1> + <p>This page shows network status, logs, and allows you to restart network services or reboot the device. + For more details see <a href="https://www.freedesktop.org/software/systemd/man/systemd-networkd.service.html" + target="_blank">systemd-networkd documentation</a>.</p> + + <section> + <h2>1. Network Status</h2> + <p>The command <code>networkctl status --json=short</code> shows current interfaces, addresses, and routes.</p> + <fluent-button appearance="accent" onclick="loadStatus()">Refresh Status</fluent-button> + <pre id="status">[Waiting for data]</pre> + </section> + + <section> + <h2>2. Network Logs</h2> + <p>Logs come from <code>journalctl -u systemd-networkd.service</code>. This helps diagnose DHCP and link issues. + </p> + <fluent-button onclick="loadLogs()">Show Logs</fluent-button> + <pre id="logs">[Waiting for logs]</pre> + </section> + + <section> + <h2>3. Manage Services</h2> + <p> + - Reload networkd after configuration changes.<br> + - Reboot device if required. + </p> + <fluent-button appearance="accent" onclick="reloadNetworkd()">Restart networkd</fluent-button> + <fluent-button appearance="accent" onclick="rebootDevice()">Reboot device</fluent-button> + </section> + + <script> + async function loadStatus() { + let res = await fetch('/api/status'); + document.getElementById('status').textContent = await res.text(); + } + async function loadLogs() { + let res = await fetch('/api/logs'); + document.getElementById('logs').textContent = await res.text(); + } + async function reloadNetworkd() { + let res = await fetch('/api/reload', {method: 'POST'}); + alert(await res.text()); + } + async function rebootDevice() { + let res = await fetch('/api/reboot', {method: 'POST'}); + alert(await res.text()); + } + </script> + +</body> + +</html> @@ -0,0 +1,72 @@ +package main + +import ( + "encoding/json" + "html/template" + "log" + "net/http" + "os/exec" +) + +// Templates +var tpl = template.Must(template.ParseFiles("index.html")) + +// Handlers +func indexHandler(w http.ResponseWriter, r *http.Request) { + tpl.Execute(w, nil) +} + +// Run `networkctl status --json=short` +func statusHandler(w http.ResponseWriter, r *http.Request) { + cmd := exec.Command("networkctl", "status", "--json=short") + out, err := cmd.Output() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(out) +} + +// Run `journalctl -u systemd-networkd.service --no-pager -n 200` +func logsHandler(w http.ResponseWriter, r *http.Request) { + cmd := exec.Command("journalctl", "-u", "systemd-networkd.service", "--no-pager", "-n", "200") + out, err := cmd.Output() + if err != nil { + http.Error(w, err.Error(), 500) + return + } + w.Header().Set("Content-Type", "text/plain") + w.Write(out) +} + +// Reload systemd-networkd +func reloadHandler(w http.ResponseWriter, r *http.Request) { + cmd := exec.Command("systemctl", "restart", "systemd-networkd") + if err := cmd.Run(); err != nil { + http.Error(w, err.Error(), 500) + return + } + json.NewEncoder(w).Encode(map[string]string{"status": "restarted"}) +} + +// Reboot the system +func rebootHandler(w http.ResponseWriter, r *http.Request) { + cmd := exec.Command("systemctl", "reboot") + if err := cmd.Start(); err != nil { + http.Error(w, err.Error(), 500) + return + } + json.NewEncoder(w).Encode(map[string]string{"status": "rebooting"}) +} + +func main() { + http.HandleFunc("/", indexHandler) + http.HandleFunc("/api/status", statusHandler) + http.HandleFunc("/api/logs", logsHandler) + http.HandleFunc("/api/reload", reloadHandler) + http.HandleFunc("/api/reboot", rebootHandler) + + log.Println("Listening on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/network-ui.service b/network-ui.service new file mode 100644 index 0000000..6b3f0f8 --- /dev/null +++ b/network-ui.service @@ -0,0 +1,18 @@ +[Unit] +Description=Web UI for systemd-networkd +After=network.target + +[Service] +ExecStart=/usr/local/bin/network-ui +WorkingDirectory=/var/lib/network-ui +User=network-ui +Group=network-ui +ProtectSystem=strict +ProtectHome=yes +NoNewPrivileges=yes +PrivateTmp=yes +AmbientCapabilities=CAP_SYS_BOOT CAP_NET_ADMIN + +[Install] +WantedBy=multi-user.target + diff --git a/requirements.json b/requirements.json new file mode 100644 index 0000000..3e0b728 --- /dev/null +++ b/requirements.json @@ -0,0 +1,62 @@ +{ + "project": "systemd-networkd Web UI", + "version": "0.1", + "requirements": { + "functional": [ + { + "id": "F-001", + "description": "The system shall provide a web-based dashboard to display current network status (interfaces, addresses, routes) by invoking `networkctl status --json=short`." + }, + { + "id": "F-002", + "description": "The system shall provide access to logs of `systemd-networkd.service` via `journalctl`." + }, + { + "id": "F-003", + "description": "The system shall provide controls to restart `systemd-networkd` without rebooting the device." + }, + { + "id": "F-004", + "description": "The system shall provide a control to reboot the device." + }, + { + "id": "F-005", + "description": "The system shall present contextual teaching information (e.g., IPv4 basics, logs explanation, restart rationale) directly within the web interface." + }, + { + "id": "F-006", + "description": "The system shall use a single-page design with minimal dependencies (HTML + Fluent UI web components from CDN)." + } + ], + "technical": [ + { + "id": "T-001", + "description": "The backend shall be implemented in Go, exposing HTTP endpoints on port 80." + }, + { + "id": "T-002", + "description": "The system shall run as a `systemd` service with restricted privileges (User=network-ui, ProtectSystem=strict, NoNewPrivileges=yes)." + }, + { + "id": "T-003", + "description": "The backend shall execute only specific whitelisted commands: `networkctl`, `journalctl`, `systemctl restart systemd-networkd`, and `systemctl reboot`." + }, + { + "id": "T-004", + "description": "The frontend shall consume backend endpoints via REST-style APIs returning UTF8-SON" + }, + { + "id": "T-005", + "description": "The system shall require no external database or file storage beyond access to `systemd` configuration and logs." + }, + { + "id": "T-006", + "description": "The service shall start automatically at boot and remain persistent under `systemd` supervision." + }, + { + "id": "T-007", + "description": "The design shall ensure sandboxing: no direct shell access beyond intended commands, no write access to arbitrary filesystem locations." + } + ] + } +} |
