Skip to content

Feat/native(chore): restart controls - #748

Merged
Vidarte-Alberto merged 24 commits into
olympus-btc:developmentfrom
Vidarte-Alberto:feat/native-restart-controls
Sep 10, 2026
Merged

Feat/native(chore): restart controls#748
Vidarte-Alberto merged 24 commits into
olympus-btc:developmentfrom
Vidarte-Alberto:feat/native-restart-controls

Conversation

@Vidarte-Alberto

@Vidarte-Alberto Vidarte-Alberto commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Problem

Ambrosia had no way to restart the server or phoenixd from the app itself. If either process needed to pick up a config change, recover from a stuck state, or apply a pending settings change, the only options were a manual terminal/systemctl command (native installs), physically rebooting the device (hardware), or restarting containers by hand (Docker) — with zero in-app control. Electron already had the plumbing for this internally (services:restart IPC), but it was never exposed as an actual button, and no other deployment mode had anything at all.

Fix

Added "Restart server" / "Restart phoenixd" controls to Settings, working across every deployment mode — native (systemd), the Raspberry Pi/OrangePi hardware image, Docker, and Electron — by reusing each mode's own existing process supervisor (systemd Restart=always, Docker restart: unless-stopped, Electron's ServiceManager) instead of introducing new privileges like passwordless sudo or polkit rules. phoenixd is signalled directly via a pidfile since it runs as the same OS user as the server on native/hardware installs; on Docker it correctly has no way to be reached (separate container, separate PID namespace) so only the server button is shown there.

What Changed

  • Added canRestartSelf()/isSystemdManaged()/isLocalPhoenixd() server helpers to detect when a self-restart is safe.
  • Generalized the existing Docker-only restart (scheduleDockerRestart) into scheduleProcessRestart, now also used for systemd-managed native/hardware installs.
  • Forced the process to exitProcess after stopping the embedded server — a leaked non-daemon thread from the Web Push async HTTP client was silently keeping the JVM alive after stopSuspend(), so the process never actually restarted.
  • Added PhoenixdProcessService, which signals phoenixd through a pidfile.
  • Added admin-only /system/restart-capabilities, /system/restart-server, /system/restart-phoenixd endpoints.
  • Added run-server.sh / run-phoenixd.sh self-restart wrappers with signal forwarding, so systemctl stop still stops the process cleanly instead of fighting the restart loop.
  • Wired the phoenixd wrapper and an AMBROSIA_SERVICE_MANAGED flag into install.sh and the hardware image's systemd units.
  • Added an install.sh --local flag that builds and installs from the current checkout instead of downloading a GitHub release; updated uninstall.sh to remove the new wrapper too.
  • Added a "System" Settings card with a confirmation modal for each restart action, styled to match the Close Channel danger buttons.
  • Fixed two unrelated pre-existing bugs found while testing this end-to-end: a malformed fallback wordlist URL in SeedGenerator (a stray triple-quoted string embedded newlines into the URL) that broke server boot on any fresh install without the local wordlist file, and the Docker client not binding to 0.0.0.0, which made its own middleware unable to reach itself for the onboarding check.

How to Test

  1. Native: ./scripts/install.sh --local --yes, log in as admin, go to Settings → System, click "Restart server" and confirm — the service should actually come back. Same for "Restart phoenixd".
  2. Hardware: build and flash the OrangePi/RPi image, boot the device, repeat the same two checks.
  3. Docker: docker compose up --build -d, confirm only "Restart server" is shown (not phoenixd), and that clicking it restarts the ambrosia container.
  4. Electron: confirm both buttons work via the existing services:restart IPC channel.
  5. Confirm neither button appears on a deployment with no supervisor (e.g. a --no-service install).

Verification

  • Server: ./gradlew test ktlintCheck passed.
  • Client: full Jest suite passed (306 suites, 2905 tests), ESLint clean.
  • All new/modified shell scripts pass bash -n.
  • Manually verified end-to-end on native, hardware (real device flash + boot), Docker, and Electron.

Screenshots

image

@Vidarte-Alberto
Vidarte-Alberto marked this pull request as ready for review September 9, 2026 05:18
Comment on lines +41 to +43
const state = { ...defaults, ...overrides };
jest.spyOn(useSystemRestartHook, "useSystemRestart").mockReturnValue(state);
return state;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid generic variables as state in the all file.

Comment on lines +35 to +39
const { result } = renderHook(() => useSystemRestart());

await act(async () => {
await result.current.loadRestartCapabilities();
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid generic variables as result in the whole document.

Comment on lines +62 to +64
let returnValue;
await act(async () => {
returnValue = await result.current.restartServer();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be more specific with the variable names returnValue is meaningless

val deadProcess = ProcessBuilder("true").start()
deadProcess.waitFor()
val pidFile = newPidFile().apply { writeText(deadProcess.pid().toString()) }
val service = PhoenixdProcessService(pidFile = pidFile)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid generic variables as service in the whole document.

@Test
fun `isLocalPhoenixd is false when phoenixd-remote is enabled`() =
testApplication {
environment { config = MapApplicationConfig("phoenixd-remote" to "true") }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid generic variables as config in the whole document.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config here isn't a local variable we're naming, it's the config property on Ktor's ApplicationEngineEnvironmentBuilder, set via the environment { config = ... } test DSL (ktor-server-test-host).

@Sharmaz Sharmaz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@ca-ruz ca-ruz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK.

Built and tested locally, works as expected.

Image

Phoenixd Restart:

Image Image Image

Server Restart:

Image Image Image

@Vidarte-Alberto
Vidarte-Alberto merged commit 499d851 into olympus-btc:development Sep 10, 2026
16 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.

Add owner-only recovery controls for Lightning, Ambrosia, and device restart/shutdown

3 participants