Expose user tools to managed Decky plugins

This commit is contained in:
SAPHID committed 2026-10-26 11:40:01 +00:00
1 parent f5213b7c44
commit 858edaf7eb
5 files changed
+50 -4

No files matched your search

@@ -0,0 +1,33 @@
# Tailscale Control read-only backend and Decky PATH, 26 October 2026
The audited archive was official Tailscale Control 0.1.3, store ID 43,
SHA-256 `06411711b27101d438339000da3d91202d682862710b02ef3ddf2462e642448a`.
Earlier local Chromium evidence showed its settings panel. This check focused
on the Frame's CLI lookup; it did not run a live Framey panel RPC.
Each real Frame check acquired `/tmp/frame-test.lock` atomically with its own
random owner, read SteamVR activity and proximity, and released only that
owner's lock. The headset was in standby. The installed Tailscale CLI wrapper
resides in `~/.local/bin`, which the systemd user manager did not put in the
Decky service's inherited `PATH`.
| Check | Result |
| --- | --- |
| Exact official backend with the current service PATH | Three read-only status methods returned `None`; `tailscale` was not found. |
| Same backend in a disposable process with `~/.local/bin` prepended | The read-only methods returned active state, two exit-node entries and 16 device entries. |
| Bounded transient systemd user unit with the proposed service PATH | Found `tailscale` and completed `tailscale status`. The unit ended inactive, PID 0, `LoadState=not-found`. |
The Frame Control managed Decky unit now sets `PATH` to `~/.local/bin` plus
standard system directories when installed or updated. The install transaction
rolls its prior unit back on failure. No Tailscale `up`, `down`, routing or
exit-node change was run. No persistent test service, headset wake, controller
input or settings write was used.
This supports CLI discovery and read-only backend data on the real Frame. It
does not prove the production Framey-to-Decky RPC, connection controls, QR
asset, worn panel visibility or physical controller usability. Tailscale
Control remains **partial** in the digest-scoped catalog.
A later independent hourly read-only check found SteamVR activity 3
(standby), proximity raw 3.0625 and battery 93%. It released its owned lock;
no worn test was possible and the hourly schedule remains enabled.
+1 -1
View File
@@ -59,7 +59,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
| 37: Discord Status 1.5.0 | 1 / 2 | partial | Disconnected panel renders; running-activity construction accepts a synthetic game in local Chromium. Exact official is_connected returned false in real Decky 3.3.0 ARM64. Empty live running-list reads and lifetime registration/cleanup passed. Real game transitions, Discord shortcut detection/connection/activity, local custom artwork and worn VR remain unverified/unsupported. | static archive, local browser, fake-Frame, real Frame SSH |
| 38: Volume Mixer 1.2.1-1 | legacy / legacy | works | Per-app stream discovery and slider/keyboard volume changes pass in local Chromium through real Decky 3.3.0 ARM64 on the Frame audio session, with independent Pulse readback of a silent test stream. Bluetooth playback, audible output and worn-VR controls unverified. | static archive, local browser, fake-Frame, isolated Linux backend, real Frame SSH |
| 40: Controller Tools 2.0.2 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: ControllerTools/bin/backend. Frontend audit failed: Audit blocks WebSocket. | static archive, local browser, fake-Frame |
| 43: Tailscale Control 0.1.3 | legacy / legacy | partial | Settings render locally after the SuspensefulImage fallback. Tailscale daemon/state, connection and exit-node changes unverified; bundled localhost QR asset remains unavailable. No network settings were changed. | static archive, local browser, fake-Frame |
| 43: Tailscale Control 0.1.3 | legacy / legacy | partial | Settings render locally after the SuspensefulImage fallback. On the real Frame, exact official read-only backend status calls returned live state, exit-node and device entries when ~/.local/bin was in PATH; a transient user unit found the CLI with the proposed managed-service PATH. Live Framey Decky RPC, connection/exit-node changes and worn VR remain unverified; the bundled localhost QR asset is unavailable. No network settings were changed. | static archive, local browser, fake-Frame, real Frame read-only backend |
| 44: DeckMTP 1.0.5 | legacy / legacy | broken | Plugin contains a non-ARM64 Linux binary: DeckMTP/bin/umtprd. | static archive, local browser, fake-Frame |
| 46: Steamback 1.0.0-1 | legacy / legacy | partial | Panel opens using the real Frame current-user/library snapshot. Account setup and empty save-list reads pass through actual Decky 3.3.0 amd64 in isolation. ARM64 backend, supported-save discovery, automatic backups and restore remain unverified; no backup/restore ran. | static archive, local browser, fake-Frame, real Frame SSH |
| 48: Quick Launch 1.2.1 | legacy / legacy | broken | This plugin needs root and is not supported by the Frame user service. | static archive, local browser, fake-Frame |
+6 -1
View File
@@ -76,17 +76,22 @@ class DeckyServiceScript(unittest.TestCase):
self.assertEqual((self.base / 'PluginLoader').read_bytes(), first)
receipt = self.base / '.frame-control/receipt.json'
self.assertEqual(json.loads(receipt.read_text())['version'], 'v3.3.0')
self.assertTrue((Path(self.tmp.name) / '.config/systemd/user/frame-control-decky.service').is_file())
service = Path(self.tmp.name) / '.config/systemd/user/frame-control-decky.service'
first_unit = service.read_text()
self.assertIn('Environment=PATH='+str(Path(self.tmp.name) / '.local/bin')+
':/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n', first_unit)
second = arm_binary(b'B')
result = self.apply('update', second, version='v3.3.1', fail=True)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((self.base / 'PluginLoader').read_bytes(), first)
self.assertEqual(json.loads(receipt.read_text())['version'], 'v3.3.0')
self.assertEqual(service.read_text(), first_unit)
result = self.apply('update', second, version='v3.3.1')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual((self.base / 'PluginLoader').read_bytes(), second)
self.assertIn('Environment=PATH=', service.read_text())
result = self.apply('remove')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertFalse((self.base / 'PluginLoader').exists())
+7
View File
@@ -123,6 +123,12 @@ try:
next_binary.write_bytes(data)
os.replace(next_binary, binary)
binary.chmod(0o700)
# The Frame's user-installed tools (including its Tailscale CLI)
# live here, while the systemd user manager omits this directory.
# Keep standard system paths for plugins that invoke OS tools.
plugin_path = ':'.join((str(home / '.local/bin'), '/usr/local/sbin',
'/usr/local/bin', '/usr/sbin', '/usr/bin',
'/sbin', '/bin'))
text = ('[Unit]\nDescription=Decky Loader (Frame Control experimental user install)\n'
'After=default.target\n[Service]\nType=simple\nRestart=on-failure\n'
'RestartSec=5\nExecStart='+str(binary)+'\n'
@@ -130,6 +136,7 @@ try:
'Environment=PRIVILEGED_PATH='+str(base)+'\n'
'Environment=CHOWN_PLUGIN_PATH=0\nEnvironment=LIVE_RELOAD=0\n'
'Environment=USE_CEF_CLOSE_WORKAROUND=0\nEnvironment=KEEP_SYSTEMD_SERVICE=1\n'
'Environment=PATH='+plugin_path+'\n'
'[Install]\nWantedBy=default.target\n')
service.write_text(text)
receipt.write_text(json.dumps({'managedBy':'Frame Control','version':version,'sha256':digest})+'\n')
+3 -2
View File
@@ -3185,12 +3185,13 @@
"https://github.com/tailscale-dev/deck-tailscale"
],
"status": "partial",
"reason": "Settings render locally after the SuspensefulImage fallback. Tailscale daemon/state, connection and exit-node changes unverified; bundled localhost QR asset remains unavailable. No network settings were changed.",
"reason": "Settings render locally after the SuspensefulImage fallback. On the real Frame, exact official read-only backend status calls returned live state, exit-node and device entries when ~/.local/bin was in PATH; a transient user unit found the CLI with the proposed managed-service PATH. Live Framey Decky RPC, connection/exit-node changes and worn VR remain unverified; the bundled localhost QR asset is unavailable. No network settings were changed.",
"blockInstall": false,
"verification": [
"static archive",
"local browser",
"fake-Frame"
"fake-Frame",
"real Frame read-only backend"
],
"browser": {
"id": 43,