Skip to content

feat(dns): implement dns service, the half a reboot takes away - #458

Merged
ralyodio merged 1 commit into
mainfrom
feat/dns-service
Aug 30, 2026
Merged

feat(dns): implement dns service, the half a reboot takes away#458
ralyodio merged 1 commit into
mainfrom
feat/dns-service

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Closes the desktop half of the DNS story that v0.77.0 left open.

The verb was advertised and unimplemented

dns service has been in DNS_VERBS since the verb table was written, so it showed up in dns help and in shell completion. It had no handler:

$ moshcode dns service
unknown: dns service

That matters more than a missing verb usually would, because it is the half of dns enable that does not survive a restart. The resolved drop-in is a file and comes back on its own; the bridge process does not. After a reboot the routing points at a port with nothing behind it and every Moshpit name stops resolving with no obvious cause.

This is the whole reason Moshpit DNS holds on servers — which got a unit installed by hand — and falls over on desktops, which never did.

The unit that shipped could not start

One did exist, in examples/templates/*/deploy/moshcode-dns.service:

ExecStart=/usr/bin/env moshcode dns start --port 5354
DynamicUser=yes
ProtectHome=yes

Three independent reasons it fails on an ordinary install, each fatal alone:

  • moshcode is not on systemd's PATH — it installs to ~/.local/bin
  • the node its wrapper execs is not on it either, because on any mise/nvm/asdf box that is another shim under $HOME
  • ProtectHome=yes hides the install from the service even if both had been found, and DynamicUser=yes gives it a $HOME that has no moshcode in it

It starts only where moshcode and node are both installed system-wide — the server case that was already working. The unit meant to fix desktops was the one thing guaranteed not to.

So the unit is generated

No static file can be correct here. process.execPath is an interpreter demonstrably able to run this code, because it is running it, and the entry is the script the command was invoked from. Nothing is guessed, nothing depends on PATH.

moshcode dns service            # print it, change nothing
moshcode dns service --write    # install and start it, as this user, no root
moshcode dns service --system   # a system unit instead
moshcode dns service --remove   # stop it and take it away

The user scope is the default because it matches how moshcode is installed: it runs as you, so $HOME and the shims are reachable, and systemd sets XDG_RUNTIME_DIR so the pidfile lands exactly where an unprivileged dns status looks. Under a system unit those are two different paths.

Installing a service stays its own verb rather than folding into enable. Nothing here starts a service that was not asked for, and --remove touches the service only — never the routing.

Also fixed, both in the path of this change

docs/hosting-a-moshpit-name.md and the dns examples in cli-schema.mjs told people to type sudo moshcode dns enable. The CLI escalates the one step that needs root by itself, and sudo moshcode takes every path from the wrong $HOME.

Verification

Exercised against a live systemd user session, not just mocked. On a spare port throughout, so this box's real bridge on 5354 was never shadowed:

  • generated unit starts and binds
  • forwards clearnet (example.com → real A records)
  • answers Moshpit names (blue.eggs69.46.46.103, the parking address)
  • Restart=always brings it back after SIGKILL, with a new PID
  • --write writes, reloads, enables and starts; --remove disables, deletes and reloads
  • --remove with nothing installed is safe and idempotent
  • afterwards: port free, no leftover units, live bridge still active, resolver still resolving

9 new unit tests, deliberately mostly negative — the failure mode is a unit that looks plausible and dies at 203/EXEC with nothing useful in the journal.

Full suite: 2707 tests, 0 failures, 334 skipped (the usual apps/pwa guards).

Not included

No release. dns service needs a version bump and a GitHub release to reach anyone, which is a separate call.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp

`dns service` has been listed in DNS_VERBS since the verb table was written,
so it appeared in `dns help` and in shell completion. It had no handler:
`moshcode dns service` printed "unknown: dns service" and the usage block.

That mattered more than a missing verb usually does, because it is the half of
`dns enable` that does not survive a restart. The resolved drop-in is a file
and comes back on its own; the bridge process does not. After a reboot the
routing points at a port with nothing behind it and every Moshpit name stops
resolving with no obvious cause. It is the whole reason Moshpit DNS holds on
servers, which got a unit installed by hand, and falls over on desktops, which
never did.

A unit file did exist, in examples/templates/*/deploy/moshcode-dns.service, and
it could not start on an ordinary install:

    ExecStart=/usr/bin/env moshcode dns start --port 5354
    DynamicUser=yes
    ProtectHome=yes

Three independent reasons, each fatal on its own. `moshcode` is not on
systemd's PATH - it installs to ~/.local/bin. The `node` its wrapper execs is
not on it either, because on a mise, nvm or asdf box that is another shim under
$HOME. And ProtectHome hides the install from the service even if both had been
found. It starts only where moshcode and node are both installed system-wide,
which is the server case that was already working, so the unit meant to fix
desktops was the one thing guaranteed not to.

No static unit can be right here, so this one is generated. `process.execPath`
is an interpreter demonstrably able to run this code, because it is running it,
and the entry is the script the command was invoked from. Nothing is guessed
and nothing depends on PATH.

    moshcode dns service            print it, change nothing
    moshcode dns service --write    install and start it, as this user
    moshcode dns service --system   a system unit instead
    moshcode dns service --remove   stop it and take it away

The user scope is the default because it matches how moshcode is installed: it
runs as the user, so $HOME and the shims are reachable, and systemd sets
XDG_RUNTIME_DIR so the pidfile lands exactly where an unprivileged `dns status`
looks. Under a system unit those are two different paths.

Installing a service stays a separate verb rather than folding into `enable` -
nothing here starts a service that was not asked for, and `--remove` touches
the service only, never the routing.

Also fixed, both in the path of this change: `docs/hosting-a-moshpit-name.md`
and the `dns` examples in cli-schema told people to type `sudo moshcode dns
enable`. The CLI escalates the one step that needs root by itself, and
`sudo moshcode` takes every path from the wrong $HOME.

Verified on a live systemd user session: the generated unit starts, binds,
forwards clearnet, answers Moshpit names, and comes back after SIGKILL under
Restart=always. Install and removal were exercised end to end on a spare port
so the box's real bridge was never shadowed. Suite: 2707 tests, 0 failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThnQwoieWt8VR6N7gtgnhp
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

4 finding(s) in the 8 file(s) this pull request changes.

HIGH/CRITICAL: 1 | MEDIUM: 3

Severity Rule Location
HIGH tls-verification-disabled src/dns.mjs:766
MEDIUM sql-string-concatenation src/cli-schema.mjs:162
MEDIUM sql-string-concatenation src/cli-schema.mjs:504
MEDIUM sql-string-concatenation src/cli-schema.mjs:630
84 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 4 | **MEDIUM**: 70 | **LOW**: 10

Not introduced by this pull request. The full set is in the Security tab.

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:138
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:152
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:178
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:365
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:369
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:414
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:663
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:859
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:861
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:920
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:966
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1036
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1139
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1162
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1184
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1383

…and 64 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit b0bd889 into main Aug 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant