Status: proposed, not started. Written 2026-10-22.
This is a plans document: it describes intended work, not shipped behaviour. To install phpup or the panel, see the README and panel/README.md.
Measured in the repo, not assumed:
phpup.shalready emits the same schema-1 payload asphpup.ps1(phpup.sh:4213):platform,install,services.items,components,urls,paths,logs,ports,warnings. The panel parses it unchanged.phpup.shimplementsstatus --json,--fast,--watch Nandstart|stop|restart --json(phpup.sh:4302-4409, action JSON at4321).- The panel already chooses its script per platform:
script_name()returnsphpup.ps1orphpup.sh, andbuild_command()has a#[cfg(not(windows))]branch that runsbash phpup.sh <verb>. - Every dependency is cross-platform:
eframe 0.37(glow, x11, wayland),tray-icon 0.24,serde,serde_json,image. - Windows-only code is 8 functions in
backend.rs:find_in_path,build_command,run_elevated,run_elevated_sc,check_phpup_service,set_service_start,autostart_enabled,set_autostart. Plus one settings-path branch inmain.rs.
So this is a port of an action layer plus builds, packaging and wording. No second UI, no fork, no rewrite of the data path.
New module panel/src/platform.rs. Same public surface on every OS, cfg-gated internals:
| Function | Windows | Linux | macOS |
|---|---|---|---|
set_service_start(name, mode) |
sc.exe config start= |
systemctl enable/disable |
brew services start/stop |
autostart_enabled() / set_autostart(bool) |
HKCU\...\Run |
XDG autostart .desktop |
LaunchAgent plist |
elevate(cmd) |
Start-Process -Verb RunAs |
pkexec + polkit policy |
none (brew is per-user) |
open_path(target) |
explorer/cmd start |
xdg-open |
open |
Rules carried over from today's work:
- The panel never learns platform quirks: state always comes from the script's JSON.
- Settings stay one file with one shape; unknown keys are ignored per platform.
- UI wording is per-platform (
Start with WindowsvsStart at login), never a lie. - Start / Restart / Stop remain process controls everywhere; startup type stays boot behaviour. That separation is what took all evening to get right, do not undo it.
- Create
panel/src/platform.rs; move the 8 Windows functions there, add Linux and macOS implementations (stubs that returnUnsupporteduntil each phase). - Replace direct
backend::calls inmain.rswithplatform::. - Per-platform label constants for "Start with Windows" / "Start at login" and the privileges row.
- Hide Settings -> Privileges on non-Windows (UAC concept does not exist there).
Verify: Windows build behaves identically (rebuild, tray start, autostart toggle, service
chips), cargo clippy clean, no new warnings.
Decision needed: elevation. systemctl needs root, so either pkexec with an installed
polkit .policy (clean GUI prompt, needs packaging) or spawning a terminal for the sudo
prompt (simpler, uglier).
Tasks:
set_service_start:systemctl enable|disable <unit>, read state withis-enabled.- Autostart:
~/.config/autostart/phpup-panel.desktoppointing at the binary, withHidden=removal to turn it off. Mirrors the Run-key semantics closely. - Tray: document
libayatana-appindicator3as a runtime dependency; note that stock GNOME needs the AppIndicator extension or the icon is invisible. - Package as AppImage (
.desktopentry, icon,AppRun) or tarball; decide whether.deband.rpmare worth it. - CI job on
ubuntu-22.04with the X11/Wayland dev libs.
Verify: needs a real Linux desktop, not WSL. WSLg is enough to prove it renders, but not to prove tray behaviour or polkit prompts.
- Build with
crossor a native ARM64 runner; identical AppImage. - Verify on ARM64 hardware (VM or Pi). Watch for
glow/GL driver differences on Pi.
Decision needed: notarisation. An unsigned .app is blocked by Gatekeeper, so either buy a
Developer ID (about GBP 79/year, codesign + notarytool + stapling) or ship unsigned and
document right-click-Open plus xattr -d com.apple.quarantine.
Tasks:
.appbundle:Info.plist,.icnsfromimages/favicon.svg.- Universal binary via
lipo(or two artefacts). Builds must run on macOS runners (macos-13x64,macos-14arm64); this cannot be cross-compiled from Windows. set_service_startviabrew services start|stop, state viabrew services list.- Wording:
brew services startboth starts the service and registers it at login, so Auto/Disabled stops being independent of Start/Stop. Label accordingly instead of pretending the platforms agree. - Autostart: LaunchAgent plist in
~/Library/LaunchAgents, or lean on brew's own login item. - Hide Privileges; the panel stays unelevated.
- Package as
.dmg, sign, notarise, staple.
Verify: on both Intel Macs available for testing, and on Apple Silicon. Confirm Gatekeeper opens the app cleanly from a fresh download.
cargo build --release --target aarch64-pc-windows-msvc.- Same UAC and registry code path, so no code change.
- Surface the emulation fact: on ARM64 the PHP stack runs x64 under emulation (see
phpup-windows-arm64), so the panel should say so rather than implying native binaries.
README.md: platform matrix for the panel.docs/MACOS.mdanddocs/LINUX.md: panel sections, tray dependency, elevation model.panel/README.md: per-OS requirements, build and packaging.CHANGELOG.md: panel version bump (suggest 0.2.0) with the port.- Release artefacts named per target; document where each one installs.
- Linux tray visibility.
libayatana-appindicator3plus a GNOME extension. Same class of problem as the Windows tray confusion, and less obvious to diagnose. - polkit UX. Wording, no credential caching, and a
.policyfile that must be installed by the package rather than dropped next to the binary. - macOS notarisation. Cost, account, certificates, and stapling failures that only show on a clean machine.
- macOS semantic coupling.
brew servicescouples "start now" with "start at login", so the clean two-axis model from Windows does not survive intact. - ARM build infrastructure. No ARM hardware on hand; everything must be proven on CI, with Pi/VM spot checks.
- WSLg is not a Linux desktop. It proves pixels, not tray, polkit, or autostart.
- Linux elevation:
pkexecwith a polkit policy, or a terminalsudoprompt? - Apple: pay for notarisation, or ship unsigned with instructions?
- Packaging: AppImage only, or also
.deband.rpm? - Panel version: bump to 0.2.0 when the port lands?