Skip to content
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
63 changes: 49 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Two images are published for every release, sharing the same kiosk:
| **Shell** | VsCodeOsCore, built into the editor | same |
| **Browser** | Chromium | Chromium |
| **Toolchain** | git, git-lfs, Node.js, Python, base-devel, Docker | git, Node.js, Python, base-devel |
| **Firewall** | `ufw`, installed and off until you turn it on | same |
| **Boot** | UEFI (x64 and ia32) and legacy BIOS, one hybrid image | Pi firmware from a FAT partition |
| **Getting it onto a machine** | live medium + `vscodeos-install` | flash the image; it *is* the system |

Expand Down Expand Up @@ -63,9 +64,9 @@ rather than being installed from the Marketplace. Its source is in

- **An all-apps button**, in the bottom-left corner — a searchable grid of every
program on the machine, the way a start menu works.
- **A tray**, at the right end of the status bar. Left to right: now playing,
battery, volume, network, Bluetooth, the clock and date, and the power button
in the corner. Each one opens a card in the side bar. (VS Code has no API for
- **A tray**, at the right end of the status bar. Left to right: notifications,
now playing, battery, volume, network, Bluetooth, the clock and date, and the
power button in the corner. Each one opens a card in the side bar. (VS Code has no API for
a popup anchored to a status bar item; the side bar is the closest thing that
does not take the terminal panel away from you. `vscodeos.flyout.location`
puts the cards back in the bottom panel if you prefer them there.)
Expand All @@ -74,17 +75,30 @@ rather than being installed from the Marketplace. Its source is in
- **Power settings**, on the battery — energy saver, a brightness slider whose
moon button toggles night light, and the charge left. Nothing else: the radio
switches live on the cards that own them.
- **Volume** — the output level and which speakers play it, plus microphone gain
and which microphone records. The voice recorder follows whatever is chosen
here.
- **Network** — scan, connect with a password, switch between saved connections,
and airplane mode.
- **Bluetooth** — turn the adapter on, scan for devices, pair, connect and
forget. The button hides itself when there is no adapter.
- **Notifications** — the shell *is* the machine's notification daemon. It owns
`org.freedesktop.Notifications`, so `notify-send` and any application that
posts a notification lands in the editor's own notification UI, action buttons
and all. A bell in the tray keeps a history. Nothing else on either image
claims that name, so without this those notifications simply vanished.
- **Task Manager**, in the activity bar — processes with CPU and memory, per-core
meters, load average, uptime and CPU temperature, sortable and filterable, with
End task.
- **Files** — a graphical file explorer with a places sidebar, grid and list
views, rename, trash, copy and paste. **Everything opens in the editor**: text
in the text editor, images in the built-in preview, video and audio in the
media player.
media player. **Archives browse like folders** — zip, tar and everything
compressed — with Extract here, Extract to… and Compress.
- **Recycle Bin** — in the Files app's places list and in the activity bar,
sharing one backend. Shows what you deleted, where it came from and when, with
Restore, Delete permanently and Empty. Before this, "Move to trash" put files
somewhere nothing could read them back from.
- **Browser** — a real browser rendered *inside* an editor tab, with tabs, an
address bar and history. It drives a headless Chromium and streams its picture
back, which is the only way to show sites that refuse to be framed. "Open in
Expand All @@ -93,8 +107,22 @@ rather than being installed from the Marketplace. Its source is in
is in the folder.
- **Music** — transport controls for whatever is playing, over MPRIS, plus
one-click launchers for Spotify Web and YouTube Music.
- **Updater** — a GUI for the two update paths below, with live output and a
restart prompt when one is needed.
- **System Settings** — Display (resolution, refresh rate, orientation, which
screen is primary, with a fifteen-second revert in case the monitor cannot
show the mode), Keyboard (layout and key repeat), Sound (which speakers play
and which microphone records), Storage, Updates and About.
- **Storage Sense**, inside System Settings — how full each disk is, the largest
folders in your home, and a checklist of caches, temporary files and the
Recycle Bin with a size against each. The package cache, the systemd journal
and orphaned packages are there too, cleaned through a privileged helper.
- **Updates**, inside System Settings — a GUI for the two update paths below,
with live output and a restart prompt when one is needed. (It used to be an
app of its own; `vscodeos.apps.updater` still opens it.)
- **Firewall** — a GUI over `ufw`: the master switch, the default policies, the
rule list and presets for SSH, HTTP and HTTPS. Turning it on offers to allow
SSH first when something is listening on 22, because the default incoming
policy is deny and a machine being administered over the network would
otherwise go dark. **ufw ships disabled**; this is what turns it on.
- **Apps** — Calculator, Paint, Screenshot and Voice Recorder. Pressing
**Print Screen** jumps straight to a region capture.

Expand Down Expand Up @@ -221,8 +249,8 @@ VSCODEOS_RESPAWN=1 # 0 = do not relaunch when VS Code exits

## Day-to-day

The **Updater** app does all of this with buttons — open it from the all-apps
button in the bottom-left corner. By hand:
The **Updates** pane of **System Settings** does all of this with buttons — open
it from the all-apps button in the bottom-left corner. By hand:

```bash
sudo pacman -Syu # update the Arch base
Expand All @@ -231,11 +259,15 @@ nmtui # join a Wi-Fi network (or use the tray)
code ~/Projects/thing # open something in the running editor
```

The Updater runs the same work through `/usr/local/bin/vscodeos-update`, which
it launches with `pkexec`. polkit is configured to allow that one program
without a password (`/etc/polkit-1/rules.d/49-vscodeos.rules`) because there is
no authentication agent in a session whose entire UI is the editor — a password
prompt would have nowhere to appear.
It runs the same work through `/usr/local/bin/vscodeos-update`, which it
launches with `pkexec`. There are three of these privileged helpers —
`vscodeos-update`, `vscodeos-clean` for Storage Sense and `vscodeos-firewall`
for the Firewall app — and polkit is configured to allow exactly those three
without a password (`/etc/polkit-1/rules.d/49-vscodeos.rules`), because there is
no authentication agent in a session whose entire UI is the editor and a
password prompt would have nowhere to appear. Each one takes a fixed vocabulary
of words and validates its arguments before running anything; none of them
accepts a command line.

Extensions, settings sync and Marketplace sign-in all work normally;
`gnome-keyring` is started by the session so credentials persist.
Expand Down Expand Up @@ -330,12 +362,15 @@ rootfs-common/ the kiosk, shared by both images
etc/passwd, group, shadow the kiosk account (uid 1000)
etc/systemd/system/ autologin on tty1, enabled services
etc/X11/xorg.conf.d/ kiosk hardening (DontVTSwitch, DontZap)
etc/polkit-1/rules.d/ power and NetworkManager without a password
etc/polkit-1/rules.d/ power, NetworkManager and three helpers, unprompted
etc/udev/rules.d/ backlight writable by the `video` group
etc/default/vscodeos kiosk settings
etc/skel/ the kiosk user's home: .xinitrc, openbox
rules, VS Code settings and keybindings
usr/local/bin/vscodeos-kiosk the session supervisor
usr/local/bin/vscodeos-update the three pkexec'd helpers: updates,
usr/local/bin/vscodeos-clean disk clean-up, and the firewall. Each takes
usr/local/bin/vscodeos-firewall a fixed vocabulary, never a command line.
usr/local/bin/vscodeos-update-code
usr/local/bin/vscodeos-install-extensions

Expand Down
14 changes: 14 additions & 0 deletions archiso/packages.x86_64
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ ca-certificates
# without bluez there is no bluetoothctl to ask in the first place.
bluez
bluez-utils
# Packet filter for the shell's Firewall app, which drives it through
# /usr/local/bin/vscodeos-firewall. ufw rather than firewalld because its status
# output parses cleanly and it needs no daemon of its own; it pulls in
# iptables-nft, which docker already brings in here anyway. Under 4 MiB
# installed between them, which the 2 GiB asset limit will not notice.
#
# Shipped *disabled*: these images run sshd, and an image that silently starts
# dropping incoming connections on first boot would be a nasty surprise. The
# app's toggle enables both ufw and its unit.
ufw

# Web browser.
#
Expand All @@ -93,6 +103,10 @@ xorg-xinit
xorg-xauth
xorg-xrandr
xorg-xset
# setxkbmap, for the keyboard pane of System Settings. It changes the layout of
# the running session; localectl writes /etc/X11/xorg.conf.d/00-keyboard.conf so
# the next one starts with it.
xorg-setxkbmap
xorg-xhost
xorg-xprop
xorg-xkill
Expand Down
8 changes: 5 additions & 3 deletions archiso/profiledef.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ file_permissions=(
["/usr/local/bin/vscodeos-kiosk"]="0:0:755"
["/usr/local/bin/vscodeos-update-code"]="0:0:755"
["/usr/local/bin/vscodeos-install-extensions"]="0:0:755"
# This one is load-bearing for security, not just for tidiness: polkit grants
# `pkexec /usr/local/bin/vscodeos-update` without a password, so a copy the
# kiosk user could write to would be a one-line path to root.
# These three are load-bearing for security, not just for tidiness: polkit
# grants `pkexec` on each of them without a password, so a copy the kiosk user
# could write to would be a one-line path to root.
["/usr/local/bin/vscodeos-update"]="0:0:755"
["/usr/local/bin/vscodeos-clean"]="0:0:755"
["/usr/local/bin/vscodeos-firewall"]="0:0:755"
["/usr/local/bin/vscodeos-screenshot"]="0:0:755"
["/usr/local/bin/code"]="0:0:755"
["/etc/sudoers.d/vscodeos"]="0:0:0440"
Expand Down
52 changes: 42 additions & 10 deletions extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@ accidentally uninstalled.
| | |
| --- | --- |
| **Launcher** | An all-apps button in the bottom-left corner: a searchable grid of every program |
| **Tray** | Now-playing, battery, volume, network, Bluetooth, clock and date, and the power button, at the right end of the status bar |
| **Flyouts** | Apps, power, calendar, power settings, volume mixer, network picker, Bluetooth and music player |
| **Tray** | Notifications, now-playing, battery, volume, network, Bluetooth, clock and date, and the power button, at the right end of the status bar |
| **Flyouts** | Apps, power, calendar, power settings, volume mixer, network picker, Bluetooth, music player and notifications |
| **Notifications** | Serves `org.freedesktop.Notifications`, turning every desktop notification on the machine into an editor notification |
| **Task Manager** | Processes with CPU/RAM, per-core meters, load, uptime and thermals, in the activity bar |
| **Files** | A graphical file explorer: places sidebar, grid/list, rename, trash, copy/paste. Everything opens in the editor |
| **Files** | A graphical file explorer: places sidebar, grid/list, rename, trash, copy/paste, and archives that browse like folders. Everything opens in the editor |
| **Recycle Bin** | Restore or permanently delete what the Files app trashed, from the places list or the activity bar |
| **Browser** | A headless Chromium streamed into an editor tab, with tabs, an address bar and history |
| **Media Player** | Video and audio in a tab, with a folder playlist |
| **Music** | MPRIS transport for whatever is playing, plus launchers for Spotify Web and YouTube Music |
| **Updater** | pacman, VS Code and the shell itself, with live output |
| **System Settings** | Display, keyboard, sound devices, storage, updates and about, in one app |
| **Firewall** | A GUI over `ufw`: master switch, default policies, rules and presets |
| **Apps** | Calculator, Paint, Screenshot and Voice Recorder |

Every feature is behind a `vscodeos.<feature>.enabled` setting, all defaulting to
on. The all-apps button, or `VS Code OS: All Apps…` in the command palette,
lists everything.

### Three things worth knowing
### Four things worth knowing

**The flyouts are not popups.** VS Code has no API to anchor one to a status bar
item, and the only floating-window route — moving an editor to an auxiliary
Expand All @@ -42,8 +45,25 @@ rectangle on most of them. `puppeteer-core` drives a headless Chromium, CDP's
events go back through `Input.dispatch*`. That costs an encode and a decode per
frame, so `vscodeos.browser.frameRate` and `vscodeos.browser.quality` are
settings — turn them down on a Pi — the stream stops when the tab is hidden, and
"Open in browser" is always there. `puppeteer-core` is the extension's one
runtime dependency and is bundled into `dist/extension.js` like everything else.
"Open in browser" is always there. `puppeteer-core` is bundled into
`dist/extension.js` like everything else.

**The shell is the notification daemon, not a listener.** Nothing on either
image owns `org.freedesktop.Notifications`, so every `notify-send` on the
machine used to fail silently. `dbus-monitor` would not have helped: it can
watch traffic to a daemon but cannot answer a method call, and the problem was
that there was no daemon. So `src/sys/notifications.ts` claims the name over
`dbus-next` — the extension's second runtime dependency, bundled like the first
— implements `Notify`, `CloseNotification`, `GetCapabilities` and
`GetServerInformation`, and emits `NotificationClosed` and `ActionInvoked`. If
something else already holds the name, it logs and walks away.

`src/sys/usocket.ts` is the odd corner. dbus-next's abstract-socket branch calls
`require('usocket')`, a native addon we do not ship, with no guard around it —
and `vscodeos-kiosk`'s `dbus-launch` fallback produces exactly that address form,
so the machines that needed the fallback would have been the ones where this
quietly failed. `node:net` has handled Linux abstract sockets for years, so
esbuild aliases `usocket` to a stub built on it.

**Print Screen is bound by the window manager, not here.** On X11 the Print key
never reaches Electron as a keydown, so a contributed keybinding cannot see it.
Expand Down Expand Up @@ -85,9 +105,11 @@ src/
extension.ts activate(): wires everything, one DisposableStore
sys/ the only code that touches the machine
statusbar/ the tray, and the priority ladder that orders it
views/ flyout (side bar) and task manager (activity bar) providers
apps/ registry, file explorer, browser, media player, updater,
mini-apps, panel plumbing
views/ flyout (side bar), task manager and recycle bin (activity
bar) providers
apps/ registry, file explorer, browser, media player, firewall,
system settings (settings/updates.ts inside it), mini-apps,
panel plumbing
webview/ HTML shell + the host↔webview message types
media/
src/ one TypeScript entry point per page, shared code in src/lib
Expand Down Expand Up @@ -156,3 +178,13 @@ These are VS Code and Electron limits, not missing work:
authentication agent in the kiosk session, so `pkexec` has nothing to prompt
with. End task on another user's process opens a terminal with
`sudo kill -9 <pid>` ready to run instead.
- **Anything else that needs root goes through a helper script.** For the same
reason: `pkexec` only works without a prompt for programs polkit has been told
about by exact path. Updates, Storage Sense's system clean-up and the whole of
the Firewall app run through `vscodeos-update`, `vscodeos-clean` and
`vscodeos-firewall` in `rootfs-common/usr/local/bin/`. Each takes a fixed
vocabulary of words and validates its arguments; none accepts a command line,
because each one is a password-free path to root.
- **Archives are read-only.** They browse like folders and extract, but nothing
can be added to an existing one — `bsdtar` cannot append to a zip, and
half-supporting it would be worse than not offering it.
12 changes: 12 additions & 0 deletions extension/esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,19 @@ const configs = [
// bundle fails on a machine that never installed them.
'bufferutil',
'utf-8-validate',
// dbus-next reaches for `x11` to find a bus address from a window
// selection, which is the pre-systemd discovery route. sys/notifications.ts
// resolves the address itself and passes it in, so that branch is
// unreachable - but it is a bare `require`, which esbuild resolves at
// build time whether or not the code can run.
'x11',
],
alias: {
// dbus-next's other optional dependency. Not externalised but
// *replaced*: see src/sys/usocket.ts for why a stub built on
// node:net is the right answer rather than an unresolved require.
usocket: join(root, 'src', 'sys', 'usocket.ts'),
},
},
{
...common,
Expand Down
Loading