Edit configuration and restart service from the web UI #25
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
keyop-go/nfq_forwarder#25
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out from #22 (view configuration from the web UI), per the "ideally... but that may need
to be split out to a separate ticket" note on that ticket's description.
Scope
Once #22 (view-only) lands, extend the web UI to also let an authenticated user edit the
running configuration and restart the service to apply it.
Why this is a separate, bigger piece of work than "view"
nfq_forwarder.service'sExecReloadalready does afull process restart, not an in-process config reload - there's no mechanism today for the
running process to pick up a changed config file without actually restarting.
systemctl --user restart nfq_forwarder(matching the project's--user-service design - seeCLAUDE.md) froman authenticated HTTP request. That's a meaningfully different, more sensitive capability than
rendering a page: the web UI process holds
CAP_DAC_READ_SEARCH/CAP_NET_ADMIN/CAP_NET_RAW,and "let an authenticated web session trigger a service restart" deserves its own design pass
(how the restart is invoked, what happens to in-flight tunnels/connections during the restart,
error handling if the new config is invalid, etc.) rather than being bolted onto the view-only
ticket.
same validation
config.LoadFromFilealready does (duplicate queue numbers, bad CIDRs, missingjump_user/jump_host, etc.) before it's saved and before a restart is triggered - a bad edit
should never be allowed to leave the service unable to start back up.
Suggested approach (for whoever picks this up)
POST /api/configendpoint (theexisting
requireSessionAPI+requireCSRFpattern already used by/api/tunnels/close).config.LoadFromFiledoes before writing anythingto disk.
out to
systemctl --user restart nfq_forwarder, with clear success/failure feedback in the UI.mentioned in issue #22
mentioned in commit
5604aef9e6mentioned in merge request !68
Plan
Danny confirmed both editing modes are wanted (raw YAML textarea + structured per-field form,
switchable via a toggle), not one or the other.
Key findings shaping the design
LoadFromFilenever storesit anywhere. Adding a
ConfigPathfield (not YAML-tagged - set by Go code, not read from thefile) to
Configuration, populated byLoadFromFileitself, is the natural fix - threadedthrough the same
appConfigthat already flows into the web UI (see #22).--configfile" case doesn't need handling:web_ui.enabledcan only be set viaYAML, so if the web UI is running at all, a config file is guaranteed to exist.
docs/nfq_forwarder.service'sExecReloadisliterally
systemctl --user restart nfq_forwarder.service- "reload = full restart" is alreadythis project's deliberate design (every connection already tears down its own iptables rules on
the way out via the existing SIGTERM path, so a restart converges cleanly regardless of what
changed). No new shutdown/signal-handling logic is needed - SIGTERM already goes through the
same clean teardown Ctrl-C uses.
systemctl --user restarton the service that's doingthe triggering needs
--no-block, so the call queues the job and returns almost instantly(systemd's manager - a separate long-running daemon - has already accepted the job before this
process gets torn down moments later), letting the handler return a clean HTTP response first.
Design
internal/config: extractLoadFromFile's pipeline intoLoadFromBytes(data []byte) (*Configuration, error)(parse → apply defaults → assign queue numbers → validate - the samepath startup already uses, now reusable for validating an edit before it's saved), plus a
ParseRawBytesthat just unmarshals YAML with no resolution - used to show the edit form thetrue, unresolved on-disk content (not the already-defaults-merged view issue #22's read-only
page shows, which would be misleading to edit directly).
New endpoints (
internal/nfq_forwarder):GET /api/config/raw- the current file's raw YAML text and its structured (unresolved)JSON form, for populating either edit mode.
POST /api/config/validate- accepts either{"yaml": "..."}or{"config": {...}}(whichever mode is active), validates via
LoadFromBytes, and always echoes back bothrepresentations (converting one into the other server-side) - this single endpoint is what
makes the mode toggle work without needing a YAML parser in the browser (this project has no
JS dependencies/build step, so that's not on the table).
POST /api/config/save- same validation, then an atomic write (temp file + rename) toConfigPathof exactly the submitted (validated) YAML - never the resolved/expanded form,since writing back auto-assigned queue numbers or merged-in defaults would defeat the point of
those features. A bad edit is never written to disk.
POST /api/config/restart-systemctl --user restart --no-block nfq_forwarder.service.All state-changing routes get the existing
requireSessionAPI+requireCSRFtreatment alreadyused by
/api/tunnels/close. Saving does not hot-swap the running process's in-memoryconfig - matches this project's explicit no-live-reload design; the UI will clearly say a restart
is needed to apply a save.
Testing: extend
internal/config/load_test.goforLoadFromBytes/ParseRawBytes/ConfigPath. New handler tests following the established pattern (authenticated/CSRF/validation-rejects-without-writing). The restart handler will use a package-level function-variable
indirection (matching
sshDialFunc/addDNATRulesFuncelsewhere in this codebase) so tests canconfirm it invokes the right command without a real systemd session in the sandbox.
Work happening on branch
issue25_webui_config_edit; an MR will follow referencing this issue.mentioned in commit
6ecbdd9b1cmentioned in merge request !69
Implementation is up for review: !69 (branch
issue25_webui_config_edit). Both editing modes (raw YAML textarea + structured per-field form, toggle between them) are implemented, along with a separate explicit restart action. Waiting on review before merging - see the MR description for the full breakdown.UI refinements added after initial review feedback:
max-contentaction-button column widths independently, since they were separate CSS grids - fixed by sharing one grid across header + rows viadisplay: contents). Applied the same compact one-row-per-rule treatment to thedefaults.by_networktable. Made the Global settings / Web UI / Defaults field cards more compact too (tighter spacing, smaller inputs).All changes are on the same branch/MR (!69), lint and the full test suite stay clean throughout. Currently awaiting review.
mentioned in commit
551f561100Merged via !69 (merge commit
551f561) and released as v1.1.0 (a minor version bump rather than a patch, per Danny's call on the MR, since this adds a whole new capability rather than fixing a bug).