Skip to content
This repository was archived by the owner on Nov 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/mseries-tailscale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Tailscale app

on:
workflow_dispatch:
push:
branches: [dev, main]
paths:
- "rust/mseries-pantheon-kit/**"
- "rust/mseries-tailscale/**"
- "rust/Cargo.toml"
- "rust/Cargo.lock"
- ".github/workflows/mseries-tailscale.yml"
pull_request:
paths:
- "rust/mseries-pantheon-kit/**"
- "rust/mseries-tailscale/**"
- "rust/Cargo.toml"
- "rust/Cargo.lock"
- ".github/workflows/mseries-tailscale.yml"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
name: Tailscale test/build/render (arm64)
runs-on: ubuntu-26.04-arm
timeout-minutes: 30
steps:
- uses: actions/checkout@v7

- uses: ./.github/actions/fast-apt

- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential pkg-config cargo rustc rustfmt rust-clippy \
libgtk-4-dev libgranite-7-dev desktop-file-utils xvfb xauth dbus-x11 python3

- uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: mseries-tailscale-${{ runner.arch }}-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: mseries-tailscale-${{ runner.arch }}-

- name: Formatting and strict lint
run: |
cargo fmt --manifest-path rust/Cargo.toml -p mseries-tailscale --check
cargo clippy --manifest-path rust/Cargo.toml -p mseries-tailscale --locked -- -D warnings

- name: Unit tests and app conventions
run: |
cargo test --manifest-path rust/Cargo.toml -p mseries-tailscale --locked
cargo test --manifest-path rust/Cargo.toml -p mseries-pantheon-kit --test conventions --locked

- name: Validate desktop metadata
run: desktop-file-validate rust/mseries-tailscale/data/org.techmore.mseries.Tailscale.desktop

- name: Render the window from fixture status, light and dark
run: bash rust/mseries-tailscale/snapshot.sh "$RUNNER_TEMP/tailscale-snapshots"

- uses: actions/upload-artifact@v7
with:
name: tailscale-snapshots-${{ github.run_id }}
path: ${{ runner.temp }}/tailscale-snapshots/
if-no-files-found: error
11 changes: 11 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["mseries-power", "granite-smoke", "mseries-update-core", "mseries-updates", "mseries-diskspace", "mseries-pantheon-kit", "mseries-image-studio", "mseries-ai", "mseries-reply", "mseries-messages", "mseries-system-monitor", "claude-pantheon-rs", "msdk", "mseries-broker", "msdk-gtk", "mseries-dock", "mseries-greeter", "mseries-apple-dev", "torrential", "mseries-signal-history"]
members = ["mseries-power", "granite-smoke", "mseries-update-core", "mseries-updates", "mseries-diskspace", "mseries-pantheon-kit", "mseries-image-studio", "mseries-ai", "mseries-reply", "mseries-messages", "mseries-system-monitor", "claude-pantheon-rs", "msdk", "mseries-broker", "msdk-gtk", "mseries-dock", "mseries-greeter", "mseries-apple-dev", "mseries-tailscale", "torrential", "mseries-signal-history"]
resolver = "2"

# Smaller, faster binaries for the session components (the compositor runs on
Expand Down
8 changes: 8 additions & 0 deletions rust/mseries-pantheon-kit/data/conventions.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@
"exempt": {
"gettext": "uses Fluent catalogs (i18n-embed; de, en-GB) rather than gettext"
}
},
{
"name": "Tailscale",
"dir": "mseries-tailscale",
"desktop": [
"org.techmore.mseries.Tailscale.desktop"
],
"exempt": {}
}
]
}
15 changes: 15 additions & 0 deletions rust/mseries-tailscale/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "mseries-tailscale"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-only"
publish = false
description = "Tailscale: a native Pantheon window for your tailnet: connect, exit nodes, devices, copy IP or ssh"

[dependencies]
mseries-pantheon-kit = { path = "../mseries-pantheon-kit" }
# Same GTK/Granite pins as mseries-apple-dev (see mseries/RUST-UPSTREAM-MAINTENANCE.md).
gtk = { package = "gtk4", version = "0.11", features = ["v4_20"] }
granite = { package = "granite-rs", version = "3.3", features = ["v7_8", "gtk_v4_20"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
56 changes: 56 additions & 0 deletions rust/mseries-tailscale/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Tailscale

A native Pantheon window for Tailscale. It replaces the old launcher that opened Tailscale's read-only web page
(`http://100.100.100.100/`) in a browser tab.

| Connected | Dark |
| --- | --- |
| ![Connected](docs/main-light.png) | ![Connected, dark](docs/main-dark.png) |
| **Disconnected** | **A refused change shows the one-time setup** |
| ![Disconnected](docs/stopped-light.png) | ![Setup banner](docs/setup-light.png) |
| **Logged out** | **Service not running** |
| ![Log in](docs/login-light.png) | ![Not running](docs/notrunning-dark.png) |

## What it does

- **Status card:** connected or disconnected, tailnet and account, and a switch that runs `tailscale up` / `down`.
This device's name and IP, with a copy button.
- **Exit node:** pick any device that offers itself as an exit node (`tailscale set --exit-node=IP`), or None, and
allow local network access while using one.
- **Devices:** every device on the tailnet, online first, with IP, OS and when it was last seen. Search with
Ctrl+F. Click a row or the copy button to copy its IP; the terminal button copies `ssh <name>`
(the MagicDNS name, e.g. `ssh sdolbec-ser8`).
- **Settings:** Tailscale DNS, subnet routes, incoming connections (shields up), and the Tailscale SSH server,
each a `tailscale set` flag.
- **Logged out:** "Log In…" starts `tailscale login` and opens the sign-in page.
- **Service not running:** says so and copies `sudo systemctl enable --now tailscaled`.

Reading needs no root. Changes need the operator setting; without it Tailscale refuses ("access denied"), every
control springs back to the real state, and the window shows the one-time command with a Copy button:

```sh
sudo tailscale set --operator=$USER
```

The window refreshes every 3 s while it is open, after each change, and on Ctrl+R / F5.

## Build, install, screenshots

```sh
rust/mseries-tailscale/install-local.sh # ~/.local, no sudo
rust/mseries-tailscale/snapshot.sh OUT_DIR # light/dark PNGs from fixtures/ under Xvfb
cargo test -p mseries-tailscale # parsing and error classification
```

`MSERIES_TAILSCALE_FIXTURES=DIR` makes the app read `status.json` / `prefs.json` from DIR (every change is then
refused, which is how the setup-banner screenshot is made with `MSERIES_TAILSCALE_SNAPSHOT_TOGGLE=1`).

## Icon

The app icon and the status icons redraw the Tailscale logo from the dot layout in Tailscale's own tray code,
[`client/systray/logo.go`](https://github.com/tailscale/tailscale/blob/v1.104.4/client/systray/logo.go)
(BSD-3-Clause, © Tailscale Inc & contributors): lit dots when connected, all grey when not. The Tailscale name
and logo are trademarks of Tailscale Inc., used here only to label Tailscale itself.

The top-panel menu and Work Mode taskbar button live in the OS repo
(techmore/ElementaryOS_MSeries `mseries/tailscale-wingpanel`).
15 changes: 15 additions & 0 deletions rust/mseries-tailscale/data/mseries-tailscale-offline-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions rust/mseries-tailscale/data/mseries-tailscale-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions rust/mseries-tailscale/data/org.techmore.mseries.Tailscale.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[Desktop Entry]
Type=Application
Name=Tailscale
GenericName=VPN
Comment=Connect to your tailnet, pick an exit node, and copy any device's IP or ssh command
Exec=mseries-tailscale
Icon=org.techmore.mseries.Tailscale
Terminal=false
Categories=GTK;Network;
Keywords=tailscale;vpn;tailnet;ssh;exit node;network;
StartupNotify=true
StartupWMClass=org.techmore.mseries.Tailscale
21 changes: 21 additions & 0 deletions rust/mseries-tailscale/data/org.techmore.mseries.Tailscale.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions rust/mseries-tailscale/data/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* Tailscale window. Theme colours only (APP-CONVENTIONS: no hex literals). */
.mseries-tailscale .mseries-tailscale-card {
padding: 12px 15px;
}

.mseries-tailscale .mseries-tailscale-setup {
background-color: alpha(@warning_color, 0.12);
box-shadow: inset 0 0 0 1px alpha(@warning_color, 0.35);
}

.mseries-tailscale .mseries-tailscale-badge {
border-radius: 999px;
padding: 1px 8px;
font-size: 0.8em;
font-weight: 700;
color: @accent_color;
background-color: alpha(@accent_color, 0.18);
}

.mseries-tailscale .mseries-tailscale-devices > row {
min-height: 48px;
}

.mseries-tailscale .mseries-tailscale-dot {
font-size: 0.7em;
}
Binary file added rust/mseries-tailscale/docs/login-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rust/mseries-tailscale/docs/main-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rust/mseries-tailscale/docs/main-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rust/mseries-tailscale/docs/setup-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rust/mseries-tailscale/docs/stopped-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions rust/mseries-tailscale/fixtures/prefs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"ControlURL": "https://controlplane.tailscale.com",
"RouteAll": false,
"ExitNodeID": "",
"ExitNodeIP": "",
"ExitNodeAllowLANAccess": false,
"CorpDNS": true,
"RunSSH": false,
"WantRunning": true,
"ShieldsUp": false
}
Loading
Loading