19 Commits
Author SHA1 Message Date
BearHuddleston fb98be0dbe fix(webapp): end a Start's restart only on the gateway that replaced the old one
A followed Start ended at the first status read where a gateway served the
Webapp on the configured port. The gateway reports its Webapp address by
port alone, so the gateway the restart was replacing passed too when it
already served the Webapp on that port, as an earlier Start's gateway does
when a second Start queued behind it saves another mode or address. The
second Start's record then ended before its restart applied anything, and
the pane enabled Stop on the old config.

Start now notes which gateway process answers before it asks for the
restart (the control socket's identify: pid and start time), and the record
ends only once another process serves the config. The identity is read
before the status, so a gateway replaced between the two reads still counts
as the old one.

Reported by greptile-apps and Adolanium in review of #148632.
2026-11-04 10:58:03 -06:00
BearHuddleston cf53dd8b99 fix(webapp): a Web Access Start lasts until the restarted gateway serves it
The Start record ended when the Start request answered, but the gateway
restart it asked for was still running. A pane reopened in those seconds, or
any other window, showed Restart needed with Restart gateway enabled; a second
window also dropped its 1 s poll and could wait up to 10 s more.

A Start whose restart began is now reported `restarting` past its request,
until a gateway serves the config it wrote, that restart reports a failure, a
Stop or Restart replaces it, or 90 s pass (as long as the pane itself waits).
The restart command can return before the new gateway serves, so the end is
read from the gateway, not from the child's exit.

The Start's own answer carries the record too. The pane keeps that answer as
the status it shows, and where the backend answering is the gateway being
restarted (Desktop's own gateway) every status read fails from the old one's
drain until its replacement serves: a pane reopened then showed the answer's
saved-but-not-restarted config as Restart needed.

Reported by teknium1 and yoniebans in review of #148408.
2026-11-04 10:58:03 -06:00
unsupportedpastels e22287fe77 fix(webapp): create the port-claim lock and its folder user-only
The port-claim lock lives in the Hermes root's webapp folder, beside the Webapp's credentials. _file_lock created that folder with the umask's mode (0755/0775) and the lock 0644/0664. Create the folder 0700 and the lock 0600 before taking the lock, and close an existing folder other users can reach with the helper the credential writers use. _file_lock is shared by every Hermes lock and takes no mode, so the files are created here first.
2026-10-25 23:32:04 -04:00
unsupportedpastels dcbbeeb3fe fix(webapp): refuse a Start with no Nous account before saving a port
plan() chose and saved a standalone profile's webapp.port before checking the Nous sign-in, so a Start refused with "Sign in to your Nous account under Providers first." still wrote the port to config.yaml. Check the sign-in first, so a precondition refusal writes nothing.
2026-10-25 23:32:04 -04:00
unsupportedpastels 833dd40066 fix(webapp): read a home's own webapp.port with the effective config loader
_own_port hand-rolled a raw read plus the managed overlay. It now reads
load_user_config_effective(home / "config.yaml"), the loader hermes_cli/AGENTS.md names
for presence-sensitive readers: the user file, ${VAR} expansion and the managed overlay,
with no defaults merged, so a port is still only one the user or the administrator set.
2026-10-25 23:32:04 -04:00
unsupportedpastels 1f3da07f3a fix(webapp): save a standalone profile's port under a lock every gateway shares
Each standalone profile's gateway has its own change lock, so two first Web Access
starts could both probe the same free port before either saved it, and both kept it.
Web Access now chooses the port and writes it to that profile's webapp.port while
holding a file lock under the shared Hermes root (the auth file-lock helper .env writes
already reuse), before the bundle build and registration. A Start that fails after
that keeps the port as its own.
2026-10-25 23:32:04 -04:00
unsupportedpastels 43349a8d07 fix(webapp): keep a managed webapp.port when Web Access picks a port
Web Access read a standalone profile's webapp.port from config.yaml as
written, so a port the managed scope sets read as none. Start then chose
9121 and registered the sign-in callback on it, saving dropped the port
because the managed layer pins it, and the gateway still bound the managed
one: sign-in returned to a port nothing served.

Read the port from the file with the managed overlay on top, as the
gateway loads it, still without the defaults. A managed port is kept,
nothing is written over it, and the callback names it.
2026-10-25 23:32:04 -04:00
unsupportedpastels 975446bb88 fix(webapp): give a standalone profile its own Web Access port
A standalone profile that turned on Web Access without its own webapp.port bound 9120, which the default gateway's Webapp holds, so nothing was served and the LAN sign-in callback was registered on the other gateway's address. Start now gives such a profile the first port above 9120 that no other Hermes home claims and nothing listens on, and writes it to that profile's webapp.port, so the gateway binds it, the launcher prints and opens it, the callback names it, and it stays across restarts. The default profile keeps 9120, a webapp.port the user set is never replaced, and with no free port Start refuses and names the setting.

Refs #144648 (WA-13)
2026-10-25 23:32:04 -04:00
emozilla 8032fa7eca fix(webapp): a gateway older than the Webapp is named with the restart that updates it
After an upgrade the running gateway can still be the old one (a git pull, a service manager that
kept the process). It does not know the Webapp's control commands and answers "unknown verb:
'webapp-status'". hermes webapp, --status and --sign-out printed that protocol text and never named
the fix, and Web Access turned it into "Unavailable", the answer its restart wait stops on.

The Webapp's commands are now registered before the gateway's control socket answers, so an
unknown-verb answer means the gateway predates them. hermes webapp and its --status and --sign-out
say the running gateway is older than the Webapp and to run hermes gateway restart. Web Access
reports such a gateway as serving no Webapp, with that restart as the reason, so the pane offers
its Restart gateway button instead of showing the Webapp as unavailable.
2026-10-25 03:22:39 -07:00
emozilla 7e064a62ce fix(webapp): Web Access shows and saves only a public URL the gateway serves
The Web Access status read the webapp: block four times with two validators: the gateway's
webapp_settings, plus its own readers for the port and the public URL, which returned the raw
string. A webapp.public_url the gateway refuses (credentials, a query, a bad port) showed in the
pane as the Webapp's address, and Start in "My own URL" mode accepted URLs the gateway then
refused (a port of 0 or above 65535, an @ in the path, a space), so it registered, saved and
restarted onto a Webapp the gateway kept off.

webapp_settings now reads a present block's valid host, port and public URL whether or not it
turns the Webapp on (an invalid one reads as the default), and the status takes all of them from
that one read. Start checks the public URL with the gateway's webapp.public_url reader and then
requires https on a host other devices reach. The three extra readers are gone. The gateway's
listener is unchanged: a block that is off or unusable still binds main's private listener.
2026-10-25 03:22:38 -07:00
emozilla d740c0f757 fix(webapp): Web Access signs the Webapp in with a Nous Portal client of its own
Web Access registered the Webapp's sign-in callback on the standalone dashboard's Portal client
(HERMES_DASHBOARD_OAUTH_CLIENT_ID), saved that id in .env, and `hermes dashboard register` called
back into Web Access so it would register again. If the Portal replaces a client's redirect URI on
update, turning on Web Access broke Nous sign-in on an existing `hermes dashboard` deployment.

Start now registers a client of Web Access's own (named webapp_<adjective>_<noun>) and keeps its
id, its Portal and the callback in webapp/sign-in-registration.json under the gateway home, user
only. It never sends the dashboard's client id and never writes .env. A second Start with the same
callback asks the Portal nothing; a moved callback updates Web Access's client only. While the
gateway serves the Webapp, the Nous sign-in provider uses that client, and a plugin reload keeps
it. The gateway's private listener and the standalone dashboard keep reading
HERMES_DASHBOARD_OAUTH_CLIENT_ID as on main, and a Webapp whose Nous sign-in was set up by hand
keeps the configured client. A home whose record came from an earlier build of Web Access names
the dashboard's client; its next Start registers a client of its own and leaves the dashboard's
alone. `hermes dashboard register` no longer touches Web Access's record.

The desktop guide said Start saves the client id in .env; it now says where the id lives.
2026-10-25 03:22:38 -07:00
emozilla 0d386aac54 docs(webapp): say that Web Access saves the Nous sign-in client's id in .env
Web Access Start registers the Webapp's sign-in callback with the Nous Portal and saves the client
id it gets back as HERMES_DASHBOARD_OAUTH_CLIENT_ID in .env, the same client the standalone
dashboard's Nous sign-in reads. The Desktop guide and the module's docstring said Web Access left
.env alone. Both now say what it writes: the webapp: block of the config, and that client id when it
registers a callback.
2026-10-25 03:22:35 -07:00
emozilla e77feea88d fix(webapp): on the LAN, open the address Web Access registered Nous sign-in for
With the Webapp on every interface (`webapp.host: 0.0.0.0`) and no `webapp.public_url`, `hermes
webapp` on the host opened the loopback address. Web Access Start registers Nous sign-in for the
LAN address's callback, so a sign-in started there sent the Portal a callback for 127.0.0.1 that it
had never seen, and failed.

For a launch behind the sign-in gate the gateway now opens, in order: `webapp.public_url` when set,
as before; on the LAN, the address in Start's `webapp/sign-in-registration.json`, while that is
still this machine's LAN address and the Webapp's port (a moved address or port would send the
browser elsewhere); otherwise the loopback address, which still serves a password provider and
sign-in set up by hand for it. Native clients keep the loopback address. The docs say so.
2026-10-25 03:22:32 -07:00
emozilla 3d0d4b07bb fix(webapp-access): Web Access asks the Nous Portal again only when its callback changed
Every Web Access start posted the sign-in callback to the Portal's self-hosted-client endpoint
with HERMES_DASHBOARD_OAUTH_CLIENT_ID, the client the standalone dashboard also uses. If the
Portal replaces that client's redirect URI rather than adding one, each repeated post re-points
the dashboard's own OAuth callback at the Webapp, even when nothing about the Webapp changed, and
a Portal outage blocked turning Web Access back on with the same address.

Web Access now records the Portal, client id and callback it registered in the profile's webapp
folder and skips the post when all three are unchanged; a new LAN address or URL, another client
id or another Portal registers again. hermes dashboard register drops that record, because it
updates the same client and may have replaced the Webapp's callback. Whether the Portal replaces
or adds the redirect URI is not settled here.
2026-10-25 03:22:27 -07:00
emozilla eb3d916b5a fix(webapp-access): Web Access Start builds the Webapp before turning it on
Start only wrote the webapp: config and restarted the gateway. Nothing built
apps/desktop/dist-webapp except hermes webapp, so on an install where that never ran the pane
reported the Webapp running while every page returned a 404 that named a different product.

Start now builds the bundle with prepare_webapp_renderer, the function hermes webapp uses, which
reuses a current bundle. It runs after the request is checked and before anything lasting: a
failed build registers nothing with the Portal, writes no config, restarts nothing, and its error
names hermes webapp --build-only. The pane waits up to 35 minutes for Start, since that build can
wait 30 minutes for another build of it.
2026-10-25 03:22:26 -07:00
emozilla 15830396ca fix(webapp-access): Web Access keeps a custom webapp.port and builds its addresses on it
Every Web Access start wrote port 9120 into the webapp block and registered the LAN sign-in
callback on 9120, so a port the user had chosen was silently replaced. A standalone profile with
its own gateway needs a port of its own; after Start it collided with the default gateway's
Webapp on 9120, and while Web Access was off the pane showed 9120 whatever the config said.

Start and Stop no longer write webapp.port. The LAN callback, the status the pane shows and the
addresses built from it all use the configured port, whether or not the Webapp is on.
2026-10-25 03:22:26 -07:00
emozilla 85e04c295b fix(webapp-access): a Webapp served only on this computer shows as local in Web Access
The status mapped every enabled Webapp whose host was not a wildcard to the "My own URL" mode.
The documented setup for hermes webapp (webapp.enabled: true on loopback) therefore showed as
"My own URL" with an empty link, and the pane offered no way to tell it apart from a Webapp
published through a tunnel.

The mode now follows the config: webapp.public_url means the user's own URL, a wildcard host means
the network, and any other enabled host means this computer only (mode "local"). For a local
Webapp the pane links to the address the gateway serves, says only this computer can open it, and
hides the network choice until it is stopped.
2026-10-25 03:22:26 -07:00
emozilla a973c06285 fix(webapp-access): starting or stopping Web Access no longer rewrites the dashboard's public URL
Web Access kept the Webapp's own URL in dashboard.public_url. Stop and a LAN start deleted that
key, an own-URL start overwrote it with the Webapp's address, and every start or stop removed
HERMES_DASHBOARD_PUBLIC_URL from .env. Both belong to the standalone dashboard, which uses them for
its trusted Host check, its OAuth callback and its sign-in gate, so a dashboard published through
a tunnel or reverse proxy stopped working after its next restart.

Web Access now writes only the webapp: block. The own-URL mode stores its address in
webapp.public_url, which the gateway's Webapp listener reads; a LAN start drops it, because the
LAN callback follows each request; Stop changes only webapp.enabled. The pane's address comes from
webapp.public_url too, so it shows what Web Access saved rather than the dashboard's setting.
2026-10-25 03:22:26 -07:00
IAvecilla 3c92c1a264 feat(webapp): API to turn the gateway's Webapp on for your network or your own URL, behind Nous sign-in 2026-10-25 03:22:22 -07:00