syoul 5229e2c774 feat(packaging): pre-spawn cleanup wrapper for clean restarts
Symptom: each app restart that didn't go through Stop daemon left
an orphan mycelium running as root, claiming the TUN \"mycelium\",
UDP/9650 (multicast discovery) and TCP/8990 (JSON-RPC, hardcoded
in 0.6.1 — no flag). Subsequent starts panicked with EBUSY or
\"Address in use\" on whichever port the orphan held.

We can't SIGKILL the orphan from user-space (root process). Move
the cleanup into an elevated context that runs in the same pkexec
authentication as the daemon spawn:

  /usr/bin/mycellium-bootstrap   (new shell script in the .deb)
    pkill -9 -x mycelium
    ip link del mycelium / mycel0
    exec /usr/bin/mycelium \"\$@\"

The polkit policy now annotates this exact path with
auth_admin_keep so a single password prompt covers every
subsequent restart in the user's session.

Sidecar: when /usr/bin/mycellium-bootstrap exists (production
install) we hand pkexec that path instead of the bare daemon.
\`pnpm tauri dev\` falls back to the unwrapped binary path.
2026-04-26 02:27:07 +02:00
2026-04-26 01:30:26 +02:00

mycellium-ui

Cross-platform desktop GUI for Mycelium — Threefold's end-to-end encrypted IPv6 overlay network.

The app embeds the official mycelium binary as a Tauri sidecar and pilots it through its HTTP API on a loopback ephemeral port. Root privileges (required to create the TUN interface) are obtained via pkexec.

Status

v1, Linux-only. Implements the full docs/api.yaml surface of mycelium v0.6.1: admin, peers (CRUD), routes (selected/fallback/queried), messages (send/receive/reply/status), topics (default + whitelist + sources + forward), pubkey lookup.

Architecture

┌──────────────────────────────────────────────────────────────┐
│  WebView (Vue 3 + TS + Tailwind + radix-vue + Pinia)         │
│  Status / Peers / Routes / Messages / Topics / Settings      │
└────────────────┬─────────────────────────────────────────────┘
                 │  invoke() / Tauri events
┌────────────────┴─────────────────────────────────────────────┐
│  Tauri core (Rust, tokio + reqwest)                          │
│  • sidecar.rs — supervises mycelium via pkexec               │
│  • api/* — typed REST client                                 │
│  • poller.rs — emits peers://, stats://, routes://, messages://incoming │
└────────────────┬─────────────────────────────────────────────┘
                 │  HTTP loopback :ephemeral
┌────────────────┴─────────────────────────────────────────────┐
│  mycelium daemon (sidecar binary, runs as root via pkexec)   │
│  TUN0 ◄─► overlay network                                    │
└──────────────────────────────────────────────────────────────┘

There is no Unix socket / named pipe IPC — the daemon's own HTTP API is the integration point.

Prerequisites (Debian / Ubuntu)

sudo apt install -y \
  libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev \
  libsoup-3.0-dev libayatana-appindicator3-dev librsvg2-dev \
  build-essential curl wget file libssl-dev libgtk-3-dev libxdo-dev \
  pkg-config policykit-1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

Then Node 20+ and pnpm 10+.

Setup

# 1. Install JS deps
pnpm install

# 2. Fetch the mycelium sidecar binary for your target triple
bash scripts/fetch-mycelium.sh           # uses MYCELIUM_VERSION (default v0.6.1)
# or: MYCELIUM_VERSION=v0.6.1 bash scripts/fetch-mycelium.sh

# 3. Run in dev
pnpm tauri dev

The first start triggers a pkexec dialog asking you to authenticate; the polkit policy installed by the .deb caches the auth for the user session.

Build

pnpm tauri build               # → src-tauri/target/release/bundle/{deb,appimage}/

The .deb declares Depends: policykit-1 and ships the polkit policy under /usr/share/polkit-1/actions/tech.threefold.mycellium-ui.policy. The AppImage relies on pkexec being present on the host — on systems without polkit, fall back to running with sudo after disabling the sidecar's pkexec wrapper.

Layout

src/                     # Vue 3 frontend
  views/                 # one file per nav item
  components/            # shadcn-style UI primitives + dialogs
  stores/                # Pinia: node, peers, routes, messages, topics, config
  lib/                   # api wrapper, events, base64 + format helpers
src-tauri/
  src/
    sidecar.rs           # spawn + supervise mycelium
    elevation.rs         # pkexec command builder
    poller.rs            # 3 background loops (peers, routes, inbox long-poll)
    api/                 # REST client modules (admin, peers, routes,
                         #   messages, topics, pubkey)
    commands.rs          # #[tauri::command] handlers, 1:1 with REST
    error.rs             # AppError + Serialize-as-string for invoke()
  binaries/              # gitignored; populated by scripts/fetch-mycelium.sh
  packaging/polkit/      # XML policy bundled into the .deb
scripts/fetch-mycelium.sh
.github/workflows/ci.yml # pnpm typecheck + cargo fmt/clippy/test

Verification matrix

Test How
Sidecar starts under pkexec pnpm tauri dev, daemon visible in ps, splash disappears in <10 s
Peers connect Add tcp://188.40.132.242:9651 from the Peers page; state turns to alive within ~10 s
Routes propagate Routes/Selected becomes non-empty after ~30 s
Live event stream Sidebar status dot tracks ready/idle, peers table updates without manual refresh
Bidirectional messages Two instances on different VMs, exchange via Compose → Inbox
Identity regen Settings → Regenerate; restart daemon; new IP appears on Status
.deb install Fresh Ubuntu LTS / Debian 12; daemon spawns under polkit on first start

Known limitations (v1)

  • Linux only. Windows is reachable (sidecar via runas / Wintun driver) but not implemented.
  • Auto-start at login isn't wired — the desktop entry installed by the .deb is the manual launcher.
  • The TOML config editor in Settings only exposes peers, tunName, noTun. Other keys (metricsApiAddress, etc.) are passed-through if you edit the file directly at ~/.local/share/tech.threefold.mycellium-ui/mycelium.toml and restart the daemon.
  • message_status is forwarded as opaque JSON; the upstream schema isn't pinned in the spec, so we don't strongly type it.
Description
No description provided
Readme 53 MiB
Languages
Rust 39.8%
Vue 39.3%
TypeScript 17.3%
Shell 2.2%
CSS 1.1%
Other 0.2%