Hardware confirmed by the owner: elementary OS 9 Early Access boots to
Pantheon on a 2021 14-inch M1 Pro, kernel
7.1.13-1-2-fairydust-usb4-ARCH, ARM64.
This is a running transformed system; it does not establish that our factory
ISO builds or boots correctly.
bash mseries/post-install.sh runs all the customizations below in order
(desktop settings, launcher, brightness, screenshots, theme, power, audio,
Brave and Widevine, video decode, gestures, network indicator and Apps/Hosts/DNS
usage logging, CodexBar and its Wingpanel indicator, and the Wingpanel/Gala
fixes). Use --list to
see the stages, --dry-run to preview, --only, --skip and --with to
choose, and --status for the last result. Run sudo -v first and run it as
your normal user. Optional stages (Firefox, Signal, Tether, and screen corners)
are off unless named with --with.
mseries/mseries-launcher.py is a small dark, rounded GTK 4 search box that
filters installed apps as you type: Enter launches, Up/Down select, Esc or
clicking away closes, and pressing the shortcut again closes it. The setup
script installs it to ~/.local/bin/mseries-launcher and binds <Control>space
(either physical Command key, since both map to Ctrl) and <Super>space
(either physical Ctrl key) as custom shortcuts. It clears Pantheon's default Super+Space
input-source switch; Alt+Shift still switches layouts. Alt+F2 still opens
Slingshot. Gala cannot place Wayland windows, so the launcher runs through
XWayland and moves itself: centered horizontally, top edge 80% of the screen
height up from the bottom (HEIGHT_FROM_BOTTOM in the script).
The shortcuts are registered by ~/.local/libexec/mseries-launcher-keys, which
starts at Pantheon login and re-adds them if custom-keybindings is ever emptied
(it was after one reboot, leaving the per-shortcut entries orphaned). Undo by
deleting ~/.config/autostart/mseries-launcher-keys.desktop, stopping that helper,
and removing the two mseries-launcher entries from
org.gnome.settings-daemon.plugins.media-keys custom-keybindings.
Run python3 mseries/apply-desktop-settings.py as the desktop user.
It preserves existing XKB options and adds ctrl:swap_lwin_lctl and
ctrl:swap_rwin_rctl: both Command keys become Ctrl (C/V/A/Z/S etc.), and both
physical Ctrl keys become Super. It fills blank media-key bindings with the
standard mute, volume-down and volume-up keys while preserving existing custom
bindings. To undo the keyboard change, remove both options from
org.gnome.desktop.input-sources → xkb-options; restore prior values for the
volume-mute, volume-down and volume-up keys in
org.gnome.settings-daemon.plugins.media-keys to undo the media-key setup.
The script also uses title-bar layout close:minimize,maximize, adding a
Minimize button beside Maximize. Restore close:maximize to undo this.
Apps drawing their own decorations may need to be reopened or may ignore it.
Physical Command+Shift+3 captures the full screen; Command+Shift+4 selects an
area. Existing Print shortcuts remain. Since Command is remapped to Ctrl,
the actual Gala bindings use <Control><Shift>3 and <Control><Shift>4.
Gala hardcodes Pictures/Screenshots; the setup script links only that folder
to Downloads/Screenshots, preserving the rest of Pictures. Existing real
screenshot folders are preserved and require a manual migration. Captures
save automatically with native timestamped filenames and notifications.
GNOME settings daemon 50 expects GNOME Shell to handle screen brightness
keys. Pantheon has no GNOME Shell brightness implementation. The new
mseries-brightness user helper grabs XF86MonBrightnessUp/Down through
Gala's existing accelerator API and shows Gala's brightness OSD. The DCP
panel backlight is calibrated in nits (0-500) and its hardware floor is
2 nits, but Mutter's SetBacklight clamps to 1% (5 nits) and a linear 5% key
step is 25 nits. The helper therefore writes apple-panel-bl through logind
Session.SetBrightness (unprivileged for the active session, no floor; Mutter
is only a fallback) and follows a perceptual curve,
nits = 2 + (max - 2) * level^2.2. The keys walk a ladder of 25 distinct
values (2, 3, 4, 5, 7, 10, 13, 16, 20, 26, 37, 51, 68, 88, 110, ... 500), so the dim end
has fine steps. The Quick Settings slider uses the same curve and path.
Below 2 nits the keys add four software steps (about 0.7, 0.5, 0.35 and 0.25 of the 2-nit
luminance) when the patched Gala from install-gala-screen-corners.sh is running: it paints a
click-through black layer (patches/gala-screen-dim.patch) with the alpha in
$XDG_RUNTIME_DIR/mseries-dim, hidden during screenshots. Without that Gala the keys stop at
2 nits. Requires python3-gi.
The user installer enables it on Pantheon login. Start immediately with
~/.local/libexec/mseries-brightness; set a one-time level with
~/.local/libexec/mseries-brightness --set 60 (60% on the perceptual scale, not 60% of
nits) or an exact value with --nits 3; --status prints the level read-only. It never
forces a level at login.
Remove ~/.config/autostart/mseries-brightness.desktop to disable it.
The arm64 image overlay also includes the helper and autostart entry.
Confirmed live: SetBacklight and OSD calls succeed; owner confirms brighter
display. Both accelerator registrations succeed. Physical key presses
and persistence across login still need user verification.
Gala reads multitouch gestures from the Touchegg daemon, and logs "Error connecting
to Touchégg daemon" every 5 seconds at startup when it is missing. Touchegg is not
packaged for Ubuntu 26.04, so bash mseries/install-touchegg.sh builds upstream
2.0.18 and enables the system service. It deliberately removes Touchegg's own
/etc/xdg/autostart/touchegg.desktop client, since Gala handles the gestures and
the X11 client would act on the same swipes again. Gala only connects at startup,
so log out and back in afterwards. Pantheon's defaults apply: three-finger swipe up
opens multitasking, three-finger swipe left/right switches workspaces, and
four-finger gestures are unset (io.elementary.desktop.wm.gestures). Remove
everything with bash mseries/install-touchegg.sh --uninstall.
Run bash mseries/install-discord.sh. Discord Inc. publishes no Linux ARM64
build, so this installs Vesktop (GPL-3.0),
an Electron Discord client with native aarch64 releases, pinned to v1.6.7 and
checked against its SHA-256 before install. It lands in ~/.local/lib/vesktop
with a discord launcher (native Wayland hint) and an app-menu entry. It is a
third-party client, not Discord's own app. Remove with
bash mseries/install-discord.sh --uninstall (login and settings in
~/.config/vesktop are kept). Voice, screen share and login are not yet
verified on this machine.
Discord's own Linux build is amd64 only, so it runs under FEX. FEX aborts on this
16 KB-page kernel, so bash mseries/install-muvm.sh builds muvm, libkrun and
libkrunfw (pinned tags; the guest kernel uses 4 KB pages), adds the FEX PPA
(key fingerprint pinned) and fetches the Ubuntu 24.04 x86-64 rootfs. Then
bash mseries/install-discord-official.sh runs Discord's own updater
bootstrap, which fetches the official app, and runs its unmodified app.asar
and modules on stock Electron 43.2.0 x86-64, because Discord's bundled Electron
42 dies with SIGTRAP under FEX 2610 (stock Electron 42.11.12 does too). Launch
with discord-official. Login, voice (native modules were built for Electron
42), rendering and speed are not verified. Third-party Vesktop stays as discord.
Run bash mseries/install-signal.sh. Installs the official ARM64 beta,
checks its detached signature against fingerprint
4B16B7232DFAA439AD791002EF9F501F13EED94C, and adds a launcher.
The launcher enables Signal's tray mode: the window still opens normally, and
minimizing it leaves Signal running behind its Wingpanel icon. The icon shows
Signal's unread badge and activates the app when clicked. Install the optional
Wingpanel StatusNotifier host with bash mseries/install-signal-wingpanel.sh;
log out and back in to load the indicator. New message notifications continue
to use elementary's Notification Center. The AppImage requires FUSE and
supports automatic updates. mseries/signal-accent.py recolours Signal's blue to
the elementary stylesheet palette (system accent by default; --accent, --check, --restore).
The installed launcher checks the system accent each time Signal opens and
reapplies it when needed. If Signal was already running when the preference
changed, quit and reopen it to see the new colors. The optional --atomic
release mode keeps its verified app tree immutable, so it retains Signal's
upstream colors. This is the official Electron client with Pantheon integration,
not a GTK or Granite replacement. Phone linking remains a user step: open Signal,
then scan its QR code from Signal on your phone. The Signal on Pantheon design
note records the maintainable integration boundary
and remaining acceptance gates.
Source: https://community.signalusers.org/t/beta-test-signal-desktop-for-linux-arm64/76184
Moving from the Flatpak. bash mseries/migrate-signal-flatpak.sh copies the Flatpak account
(~/.var/app/org.signal.Signal/config/Signal) to the native beta (~/.config/Signal Beta) so history and
the device link carry over, then hides the Flatpak launcher. Quit both Signals first (the script refuses
otherwise); --check previews; --rollback shows the Flatpak again and sets the native profile aside. The
copy is the same linked device, so never run both. The native beta must be at least the Flatpak's version
(Signal's database only migrates forward). Why native: the Flatpak has no filesystem access, so files dragged
in from host apps are unreadable inside its sandbox.
Install the optional, pinned ARM64 upstream package with
bash mseries/install-tether.sh. It pairs the iPhone directly to this laptop;
it does not need a relay Mac. Launch the app and approve pairing on both
devices. The package install does not enable a daemon or change Bluetooth or
firewall settings.
The current host has BlueZ 5.85; upstream Tether needs 5.86+ with the experimental bearer API for SMS/iMessage over Bluetooth. The app can be installed, but the message transport is not ready on this host. Do not try to solve that by enabling experimental mode on BlueZ 5.85. Wi-Fi pairing and OTP handoff are independent, but require TCP 5134 and mDNS UDP 5353 to reach the laptop; configure firewall access only if you choose to use that path.
Optional Brave setup is in tether/README.md. It requires
loading an unpacked extension and registering its native host. The extension
requests access to all websites, so review that permission before enabling it.
No Brave extension is installed automatically. Remove the app with
bash mseries/install-tether.sh --uninstall; pairing state is preserved unless
you separately run --purge-data. That removes Tether's XDG files, not the
Bluetooth bond stored by BlueZ; forget the laptop from both devices to fully
unpair.
Upstream already ships a Linux Qt desktop and ARM64 CLI; a fork is unnecessary for desktop windows. The installer brings in its Qt runtime packages, then downloads the pinned ARM64 desktop and CLI releases and verifies both published SHA256 checksums. The default post-install flow also installs the Pantheon Wingpanel indicator with its build dependencies.
bash mseries/install-codexbar.sh
~/.local/bin/codexbar-linux --settingsProvider authentication stays with the provider CLIs. Pantheon may not expose a StatusNotifier tray host; desktop launcher/windows work without one. To install or repair the Wingpanel indicator separately:
bash mseries/install-codexbar-wingpanel.shLog out and back in after installation. The indicator starts CodexBar's local backend and reads its same-user structured snapshot every five seconds without blocking Wingpanel. Opening the dropdown requests fresh usage when the cached provider data is at least 30 seconds old; these requests are limited to one per minute and skipped while a fetch is running. Manual refresh remains available. The indicator preserves CodexBar preferences. For one-minute background usage polling and refresh-on-open in CodexBar's own windows, configure the running app:
~/.local/bin/codexbar-linux --configure '{"refreshSeconds":60,"refreshOnOpen":true}'The panel shows each provider's session quota remaining and the earliest
available quota ETA, labeled with its limiting window (for example,
CX 87% · 7D ~1d7h). An exhausted limit shows its reset countdown instead.
The dropdown keeps both session and weekly percentages, reset times, and a
visible forecast below each limit. Hover the bar for the limiting quota's detail.
Forecasts reuse CodexBar's reported usage pace and describe quota availability,
not an exact remaining token balance. Missing/unrecognized pace data, provider
errors, usage older than three minutes, and expired reset boundaries suppress
the forecast. A safe window does not imply another unmeasured limit is safe. Provider credentials remain managed by
CodexBar. The
monochrome mark comes from the OpenAI 2025 symbol
and follows OpenAI's branding guidelines.
Remove it with bash mseries/install-codexbar-wingpanel.sh --uninstall.
Source: https://github.com/steipete/CodexBar/tree/main/Integrations/Linux
On the M1 Pro, the center area of Wingpanel conflicts with the camera notch.
Keep CodexBar in the left group beside the application launcher with
bash mseries/install-wingpanel-codexbar-left.sh. This rebuilds the matching
Wingpanel 8.0.4 source, places CodexBar immediately after the launcher, and
preserves the optional right-side clock ordering. It saves the current panel
binary first; restore it with bash mseries/install-wingpanel-codexbar-left.sh --uninstall.
The CodexBar installer adds OpenAI Codex and OpenCode Go to the provider list
when the current selection allows it. Other explicit single-provider
selections are preserved; switch to custom and add opencodego in
Settings → Providers to request CodexBar's account-level OpenCode Go provider.
The Wingpanel menu has a Sessions section (codexbar-sessions.py,
read-only, ~0.1 s) with a status dot for ChatGPT/Codex, Claude Code and
OpenCode: signed-in state is judged from each tool's own files (never
printed), and Claude Code and OpenCode show requests and tokens over 5 hours
and 7 days from their local history. This is local activity, not account
quota. Claude account quota in CodexBar needs a claude.ai session or Claude
CLI credentials, which a desktop-app-only sign-in does not provide.
The OpenCode v2 reader (codexbar-opencode-local.py) also reads ~/.local/share/opencode/opencode.db
directly in SQLite read-only mode. It shows local cost-bearing OpenCode Go
assistant records over 5 hours, 7 days, and 30 days, labeled local estimate ·
not account quota. This works with the v2 OAuth credential stored in
OpenCode's database and does not need an API key or browser cookies. It does
not modify OpenCode's database or credential store. CodexBar's separate
account-quota provider still requires an auth method supported by CodexBar;
the local estimate is shown even when that provider cannot authenticate.
Install the Wingpanel indicator after CodexBar so its read-only adapter is
available:
bash mseries/install-codexbar-wingpanel.shThe Signal launcher uses its supported --use-tray-icon option. Install the
GTK 4 Wingpanel host with bash mseries/install-signal-wingpanel.sh, then log
out and back in. The host displays Signal's unread-count icon (including a
bundled official icon when Signal's StatusNotifier metadata is incomplete) and
a Wingpanel menu with an Open Signal action. The action launches the local
Signal Beta build, or falls back to the Flatpak installation. Signal does not
expose an independent Wingpanel login flow, so linking or relinking opens
Signal's QR screen and must be
approved from the primary phone.
Touch ID is not enabled or verified on this M1 Pro. The earlier blanket unsupported statement was too strong: Omarchy M's September announcement reports Secure Enclave Touch ID work, and an experimental kernel was advertised on September 29. Asahi's feature matrix still lists TouchID as TBA. A tested M1 Pro kernel, firmware, enrollment service and PAM integration must be identified before enabling fingerprint login. See the optimization audit. Issue #8271 concerns Intel T1 firmware/EFI, not this Apple Silicon machine.
The full-height desktop profile restores the 74-pixel
camera strip on the 14-inch M1 Pro. Run bash mseries/install-desktop-space.sh
as the desktop user and reboot when ready. Wingpanel moves its native clock
and calendar beside the camera and keeps controls in two protected halves.
The native dock uses 40-pixel icons, hides when any window overlaps, and saves
Command-drag placement across restarts. Existing pinned apps and drawers stay.
--build-only checks both builds without installing; --revert restores the
previous profile. Existing recovery entries and queued kernel trials survive.
A queued one-shot kernel trial runs before the new normal-boot choice.
Stock Wingpanel still centers the clock: keep the cropped mode until this native profile has built and installed successfully. Factory-image full-height boot and on-device post-reboot behavior remain unqualified.
- Login stall (fixed): the Files file-chooser portal and
xdg-desktop-portalwaited on each other for the 25 second D-Bus timeout. Install the fix withbash mseries/install-files-portal-fix.sh(undo with--uninstall). - Kernel phase, about 10 seconds:
apple-dcpdefers probing becausemux-apple-display-crossbar(itsdp-xbar) is not in the initramfs, so the kernel waits out its 10 second deferred-probe timeout. - Disk-encryption passphrase screen: the roughly 24 second gap before the
root filesystem mounts is the LUKS passphrase prompt. Both systems boot the
Arch (Omarchy) kernel and initramfs, so it shows the Omarchy Plymouth theme.
The disk stays encrypted; auto-unlock would need a key stored on
/boot.
Run bash mseries/install-elementary-boot-splash.sh to add the missing module
(mseries/boot/zz_mseries_boot.conf) and switch the passphrase screen to the
elementary Plymouth theme. It backs up the current initramfs images as
*.pre-mseries, rebuilds them in the Arch root at /mnt/fdbuild/root, and
--uninstall restores the Omarchy theme and the backups. Reboot afterwards and
check dmesg no longer shows Failed to get dp-xbar.
The original stock-kernel diagnosis is superseded by the experimental fairydust USB4 work. Dock USB2 peripherals and USB3 Ethernet were verified on October 12; tunneled dock video remains unsupported. See fairydust USB4 status and recovery for the exact hardware, kernel patches and rollback procedure. Plain DP Alt Mode and Thunderbolt DP tunneling are separate paths.
Located at ~/.opencode/bin/opencode; executable is native ARM64.
Log shows its working directory was /home/admin, and update checks returned
HTTP 500 and timed out. Available RAM was about 23 GiB of 30 GiB; no evidence
of memory pressure. These observations do not establish why model responses
are slow. Launch from this checkout to keep project discovery scoped:
cd ~/Documents/ElementaryOS_MSeries && opencode.
No OpenCode settings or running sessions were changed.
The M-series image build now compiles the patched elementary/panel-network plugin at pinned
commit 8685ec8e55a45e48da0c12931a353cd440d11664 and includes it in both the live installer
and installed system image. This keeps the menu-bar speeds across reboot and OS image updates.
The build verifies the installed indicator package is still 8.0.1.x before applying the patches.
On an existing installation, bash mseries/install-network-throughput.sh applies the same build
locally and backs up the original plugin at /usr/local/lib/mseries/libnetwork.so.original.
Package replacement or deploying an older OS image can undo that local replacement; the next
M-series image build carries it by default. --uninstall restores the original plugin.
The dropdown also includes a 60-second graph (down in the accent colour, up in grey) directly
under the connection toggles (Airplane Mode etc.). When Wi-Fi is connected, its list shows the
active network by default; use the chevron to reveal other networks. The collapsible Network
Usage section has Apps, Hosts and DNS views. The default post-install flow
installs and starts the user-level logger after the network indicator build;
to apply just these customizations, run
bash mseries/post-install.sh --only network,netusage. The logger records TCP
byte counts and DNS queries and maps remote IPs using systemd-resolved's
cache/query monitor. TCP usage logging needs ss from iproute2; DNS logging
needs a working systemd-resolved service and resolvectl. Without those, DNS
views remain empty, while the TCP logger can still run. The installer grants
sudo-group administrators read-only DNS query monitor access through a dedicated
polkit rule. The background monitor never opens authentication dialogs;
installing the rule requires sudo once. Data stays in
~/.local/share/netusage/usage.db; QUIC/HTTP3 isn't attributed to apps. Build requirements for the
local installer include libsqlite3-dev, libnm-dev libnma-gtk4-dev libpolkit-gobject-1-dev, and
the Wingpanel/Granite/GTK dev packages. Gala respawns the panel, so there is no logout.
Speeds are the sum over physical interfaces from /proc/net/dev (virtual ones such as lo,
bridges and VPN tunnels are skipped to avoid double counting), sampled once a second, so the graph
is already filled when you open the menu. The Show speed in menu bar switch stores its choice in
~/.config/mseries/network-speed.ini.
Checked after a reboot with no failed system or user units. Nothing below needs action to keep the desktop working.
Resolved:
- Cmd+Space launcher closed instantly: each key press started it twice within
milliseconds and the second start toggled it shut. Fixed by ignoring a second start
within 400 ms. Apps it launched also inherited its
GDK_BACKEND=x11andGDK_SCALE; it now restores the originals for them.~/.cache/mseries-launcher.logrecords opens, focus changes and quit reasons. - Shortcuts lost after a reboot:
custom-keybindingswas found emptied while the per-shortcut entries survived.mseries-launcher-keysre-registers them at login. - No Files previews:
tumblerd.servicefailed with "Failed to create cgroup ... app.slice/tumblerd.service", and any new user service failed the same way, while scopes still worked. A reboot cleared it and the cause is unknown. If it returns, runtumblerddirectly (setsid nohup /usr/lib/aarch64-linux-gnu/tumbler-1/tumblerd &) until the next login. - Touchegg missing: see Touchpad gestures above.
Historical log observations (not a declaration that every warning is harmless):
- The login screen's
galaandwireplumber(user 108) abort during teardown while the session is handed over. They do not affect the desktop session. systemd-growfs: crypt_resize() of /dev/nvme0n1p5 failed: Operation not permittedfor/and/home; boot continues.apple-dcp: Failed to get dp-xbar, and Thunderbolt "USB3 tunnel creation failed" / "DP not active" (display and USB4 bring-up). Missingrtl_nic/rtl8156b-2.fwfor a USB adapter. The missingorg.gnome.Shell.Brightnessprovider is addressed by the 2026-10-12 compatibility bridge described below.- Wingpanel prints GTK layout and
g_str_has_prefixassertions from the custom indicators.
Possible optimizations, not applied (each needs sudo and a decision):
- Boot:
cups-browsed.servicepullsnetwork-online.targetonto the critical chain, soNetworkManager-wait-online(about 3.1 s of the 6.1 s userspace time) delaysgraphical.target. Disablingcups-browsedshould remove most of that wait.snapd.*adds roughly 3 s of parallel start-up work and is only needed for snaps. - Unused daemons enabled:
ModemManager,avahi-daemon,cups,apport,snapd. - No swap or zram (30 GiB RAM, about 23 GiB available at the time). Adding zram would only matter under memory pressure.
- CPU: with the Claude and ChatGPT desktop apps open, load average sits near 3 on 10
cores, and
galauses about 15% while they repaint. Not investigated further.
The ARM64 opencode Debian package version 2.0.27 is already installed.
Its visible application entry is /usr/share/applications/ai.opencode.desktop.desktop,
launching /opt/OpenCode/ai.opencode.desktop. It was launched successfully
during the audit. The terminal command remains ~/.opencode/bin/opencode.
GitHub's legacy latest release was 1.19.0 at audit time; do not downgrade the
installed desktop based solely on that endpoint.
Coreglass and MLX Chat are now installed on the M1 Pro. MLX uses an isolated Honeykrisp compute driver; the desktop Mesa package is unchanged. Start with the Compact model pair. See local AI setup and verification.
Screen corners (bash mseries/install-gala-screen-corners.sh [--radius N]): the M1 Pro's display has
rounded corners that the Linux desktop ignores. This builds Gala (pinned to the commit matching the
installed 8.6.2) with mseries/patches/gala-screen-corners.patch, which adds a click-through overlay that
draws black rounded masks over the four corners of the built-in display only (external monitors are
left square), above the top bar, dock, windows and lock screen. The source default is 20 logical px
(GALA_SCREEN_CORNER_RADIUS); the matching window clip and GTK CSD radius also default to 20 logical
px. This is a shared starting value, not a measured hardware claim; confirm the curve against the panel
after login. Set either environment value to 0 to disable its mask.
Safety: the patched Gala lives in /usr/local/lib/mseries/gala; /usr/bin/gala and the login screen are
untouched. Only your user session is pointed at it (systemd drop-in under ~/.config). The launcher falls
back to stock Gala by itself if the patched one dies within 15 s of starting, and then stays on stock until
~/.cache/mseries-gala-failed is deleted. It takes effect at the next login. Revert with --disable. If a
session ever will not start, switch to a text console (ctrl+alt+F3), log in and run
rm ~/.config/systemd/user/io.elementary.gala@wayland.service.d/mseries-screen-corners.conf.
Build notes: needs libibus-1.0-dev libgnome-bg-4-dev besides the mutter/gala/granite dev packages; Gala's
bundled vapi/libmutter.vapi maps Monitor/LogicalMonitor to meta/main.h, which does not declare them,
so the patch points them at meta/meta-monitor.h / meta/meta-logical-monitor.h.
Colours and window radius (bash mseries/install-theme-tweaks.sh): user stylesheets
(mseries/theme/gtk-4.0.css, gtk-3.0.css) override the theme's named colours BLUEBERRY_* and
STRAWBERRY_* (the accent, selection and error colours are derived from them) with softer tones, and use a
20 logical-pixel CSD radius aligned with the Gala window clip. ghostty.config sets a muted 16-colour palette. --uninstall
restores backups.
Findings from reading this boot's logs, systemctl, ps, /proc/meminfo and the kernel/initramfs build:
| Finding | Evidence | Fix |
|---|---|---|
| Display crossbar never made it into the fairydust initramfs | apple-dcp: Failed to get dp-xbar: -517 x70 per boot; display up ~2.6 s late; widened the greeter/display race |
install-initramfs-crossbar.sh. Root cause: mkinitcpio ignores /etc/mkinitcpio.conf.d/*.conf when a config is given with -c or a preset's ALL_config; the fairydust presets set it, the stock one does not. That also means zz_mseries_boot.conf and Omarchy's drop-ins (incl. its HOOKS with plymouth) only ever applied to the stock kernel. |
| No swap | free: 0 B swap, 19 GB used, 11.6 GB shared (GPU buffers) |
install-system-tuning.sh: zram swap (half of RAM, max 8 GiB, zstd), swappiness 100, page-cluster 0, zswap off |
| AppCenter background daemon | 682 MB resident | autostart override (Hidden=true); AppCenter itself still works |
| ModemManager running, duplicate snap CUPS | no modem; apt CUPS + snap CUPS both running | disabled ModemManager and snap cups |
| Wi-Fi resume recovery could not unload the driver | modprobe -r brcmfmac failed with "Module brcmfmac is in use": brcmfmac is held by its vendor module (brcmfmac_wcc, refcount 1), not by NetworkManager. The rejection counter also read journalctl -k, which never contains wpa_supplicant lines |
mseries-wifi-resume-fix now uses modprobe -r --remove-holders and loads the holders back, and reads rejections from the wpa_supplicant/NetworkManager units. The policy (airplane mode respected, Ethernet does not hide a down Wi-Fi, abort on failed unload) is unchanged and covered by mseries/test-wifi-resume.py. |
| Build caches on the Arch side | 34 GB (compressed on disk: ~9 GB) | removed usb4-pkg, fairydust, usb4, test; built packages kept in ~/fairydust-usb4/pkgs/ |
Historical issues left outside the system-tuning change: Wingpanel's intermittent SEGV in PanelWindow.on_realize
(upstream race when the panel is respawned; Gala restarts it), Wingpanel indicator assertions (see the subsequent log audit below), the greeter's wireplumber abort, brcmfmac P2P-device -52 errors, systemd-growfs failing
to resize the LUKS volume, USB cannot reset (-71) floods when a dock is pulled, and kernel taint S
(CPU out of spec) which is normal on Asahi. Revert the tuning with install-system-tuning.sh --revert.
The user GTK palette now uses muted slate blue (#748fae) and dusty rose
(#bf9298) for accents and error feedback. Error entry borders are softened too.
Restart apps to pick up the updated user stylesheets.
Spellcheck squiggles are drawn by each application or text engine; the GTK palette does not override every spelling marker (including Chromium/Electron). These colour tweaks keep spelling checks enabled.
Installed and verified on the M1 Pro session:
- Added a Pantheon provider for GNOME 50's
org.gnome.Shell.BrightnessAPI, backed by Mutter's real backlight API. This resolves repeated power daemon dimming failures. Manual changes during dimming are preserved. The installer saves the original ambient-brightness and idle-dim settings, disables both automatic screen adjustments, and restores the saved values on uninstall. Screen blanking and suspend remain enabled; existing brightness keys remain in use. The image enhancement layer sets the same unlocked defaults for new sessions. - Fixed the keyboard indicator's binding to a removed
activeproperty onWingpanel.PopoverMenuItem. Selection now uses GTK's checked state. - Fixed the network indicator passing its device-less VPN row into libnm's device-name disambiguation. Existing throughput enhancements remain included.
- Repaired Signal Beta's lowercase Electron desktop identity and installed its own bundled icon. Hidden aliases let the dock resolve the app without adding duplicate visible launchers. The running Signal session was preserved.
- Corrected the misplaced
StandardErrordirective in the installed SPICE user unit through a user override. Regenerate this override after vendor updates. - Cleared the stale failure of the already-disabled duplicate snap CUPS browser only when its recorded exit status was 143 from intentional termination. Native CUPS remains active.
Reproduce the session integration with bash mseries/install-log-fixes.sh.
Build the keyboard patch with bash mseries/install-keyboard-indicator-fix.sh
and the network patch with bash mseries/install-network-throughput.sh.
These indicator builds require the development packages used by the existing
Wingpanel builds and network access to their pinned upstream sources. Reload
Wingpanel after replacing plugins. The keyboard installer accepts --uninstall
to restore its original library; the brightness installer also accepts
--uninstall. Signal's original launcher is backed up under
~/.local/state/mseries-signal/desktop-before-integration.
Validation: five brightness behavior tests passed; shell syntax and diff checks passed; both native indicator libraries compiled and loaded. After a fresh panel reload, the removed-property warning and nine libnm device assertions were gone, and the panel remained running. Both system and user failed-unit lists were empty. The latest five-minute warning check was empty before the panel reload.
At the end of the initial audit (subsequently fixed below): a fresh panel still reports six
g_str_has_prefix null-string assertions and one Granite.Symbol undefined-state
warning. An isolated notifications-plugin probe reproduced null-string warnings,
but their precise origin has not been established. An isolated Bluetooth probe
also crashed outside the desktop host; that diagnostic crash does not establish
that the live Bluetooth indicator crashes. No logging was suppressed. Historical
dock/kernel and greeter teardown messages remain outside these verified fixes;
no kernel upgrade or unsafe speaker changes were made.
bash mseries/install-quick-settings-brightness.sh builds elementary Quick
Settings 1.4.0 at pinned revision fb721d4b5640a2a25ae011370c8fe8aaff8d93f4
with a native GTK brightness slider immediately beneath the toggle grid containing
Onscreen Keyboard, above text scaling. It shows the current percentage and uses
Mutter's internal-display backlight range, including its hardware minimum.
Updates are asynchronous and coalesced while dragging; external brightness-key
changes refresh the slider. No slider is shown when no active internal backlight
exists, or in the greeter.
Build dependencies include libaccountsservice-dev, libpackagekit-glib2-dev,
libportal-dev, libadwaita-1-dev, libwayland-dev, and the existing GTK,
Granite, Wingpanel, Meson and Vala development tools. The installer replaces only
libquick-settings.so, saves the stock plugin under /usr/local/lib/mseries/,
and accepts --uninstall to restore it. Reload Wingpanel after installation or
restoration. Distribution package updates may require reinstalling the patch.
Validation: native plugin compiled; widget initialized from the live display at 62.6%; moving it one percentage point changed the real backlight from 315 to 320, and a finally block restored 315. The panel reloaded and remained running. Visual appearance has not been checked through a screenshot.
The remaining seven panel startup warnings were isolated to the sound indicator,
not the notifications plugin. A Wayland plugin probe and GLib log-handler stack
trace identified two causes: DisplayWidget.volume_state was initially null when
its binding synchronized, and GTK 4.22.4's shape_apply_state() called
g_str_has_prefix() on unnamed SVG animation IDs during initial state changes.
bash mseries/install-sound-indicator-fix.sh builds official sound indicator
8.0.3 at 8b7991c9156ac8127d27559506baea8e6fb72e5b. The patch gives the volume
state a valid initial value and assigns unique IDs to the existing animations.
It preserves the artwork, state changes, animations and all audio routing;
no speaker output or volume settings are enabled. Build dependencies additionally
include libpulse-dev, libnotify-dev and libcanberra-dev. The stock library
is backed up under /usr/local/lib/mseries/; --uninstall restores it, followed
by a panel reload. GTK source reference:
https://github.com/GNOME/gtk/blob/4.22.4/gtk/gtksvg.c (shape_apply_state).
Validation of the follow-up: the patched sound indicator's isolated Wayland startup produced no warnings. After installation, a normal Gala-respawned panel (PID 162249 in this session) had no warning-level journal entries. The earlier seven warnings belonged to the old process, not the fresh panel. No diagnostic preload library or log handler is installed persistently. One temporary panel probe initially selected X11 and crashed because Wingpanel requires Wayland; the normal panel recovered, and subsequent probes explicitly used Wayland. The gear menu was opened through its native toggle action after activation.
The Quick Settings patch also adds a separately labeled Keyboard Brightness
slider immediately above screen brightness, below the toggle grid. It uses
UPower's org.freedesktop.UPower.KbdBacklight system-bus API, reads the actual
hardware maximum, supports fully off, and follows BrightnessChanged signals.
It does not write screen brightness. Writes are asynchronous and coalesced while
dragging. The existing ambient keyboard-light helper remains enabled; its
manual-change detection pauses automatic adjustments until ambient light changes
substantially.
Reinstall with bash mseries/install-quick-settings-brightness.sh and reload
Wingpanel. Validation on the M1 Pro: the widget initialized at 98% (250/255),
a one-percent slider decrease changed the keyboard backlight to 247, and the
test restored the original value of 250. The native plugin rebuilt successfully.
Fixes that can only be verified later (after a reboot or login, or when the dock is plugged in) are tracked by
mseries-verify, which lives next to the troubleshooting robot and keeps its history in SQLite
(~/.local/state/mseries-troubleshooter/verify.db, plus verify-status.json for panels/UIs). Install with
bash mseries/local-ai/install-verify.sh; a user timer runs it 90 s after login and every 10 minutes.
- Each check has a precondition (e.g. "booted after the initramfs was rebuilt", "dock present for 2 min") and
a read-only command (exit 0 = pass). Until the precondition holds the check is
waiting. - Visual items are
manual: it notifies once and asks; answer with the Looks good / Problem buttons in the Local Troubleshooter window ("Checks still open") ormseries-verify confirm ID yes|no [note]. - A desktop notification is sent only when a check changes state (passed / failed / needs you).
mseries-verify status,history [ID],run --notify,recheck ID,reload(re-readsmseries/local-ai/verify-checks.json; recorded results are kept). Add new "check this later" items there.- Commands come only from that seed file, never from a model. Tests:
python3 -I mseries/local-ai/test_verify.py.
- Speaker amplifier faults, speakersafetyd missing.
tas2764 1-0039: fault: over currentthree times (~30-60 min apart, constant context bytes02,19,00,00,00) plus bursts ofbad TDM clock. The kernel's "Speaker Volume Unlock" was locked and the sink volume 0, so nothing unsafe was being driven, but the elementary root had none of the Apple audio stack the Arch root runs. Result:speakersafetyd1.1.2 installed and running (itsj314.confis byte-identical to Arch's 2.0.1; per-woofer limits loaded). The DSP chain (asahi-audio+alsa-ucm-conf-asahi) was tried and reverted: it needs PipeWire's LV2 filter plugin, which Ubuntu's PipeWire 1.6.2 lacks (Arch's 1.6.9 haslibspa-filter-graph-plugin-lv2.so), sospa.filter-graph: can't load plugin type 'lv2'and the speaker sink vanished, leaving only a headphone sink at 100%. Audio was restored (Built-in Audio Stereo, muted) andinstall-audio-stack.sh --with-dspnow refuses until that plugin exists. Retry when Ubuntu's PipeWire ships it (or consider building the plugin from the matching PipeWire source). Tracked by theamp-overcurrentcheck. - Wingpanel SEGV (3 today). Identical stack each time:
PanelWindow.on_realize->wl_proxy_get_version, a Wayland proxy invalid during a panel (re)spawn. Gala respawns the panel, which is the recovery; a null check would leave an unanchored panel running, so it is deliberately left alone. Report upstream. - Orca started and died at 18:12. Shortcut
<Alt><Super>s; with Command/Control swapped it is physical Control+Option+S. Orca then fails with an XKBBadValueunder Xwayland. Shortcut removed (gsettings set org.gnome.settings-daemon.plugins.media-keys screenreader "[]";gsettings resetrestores it). - Repo lint:
shellcheckover 58 scripts: 0 errors; 13 warnings (mostlyls | grepstyle;SC2114is upstream elementary's intentional/usr/localreplacement in the image). Fixed:build-m1n1.shignored its pinned SHA. - CI gap: only the wifi test ran. CI now runs every Python test file.
test_bridge.pyhad no runner. - A mistake worth recording: the first Wi-Fi rewrite overwrote newer tested logic and broke two CI tests;
restored in
2bcd925. Re-read a file immediately before replacing part of it, and run the repo's tests before pushing.
Evidence (journalctl -k): suspend entry 17:34:06 (s2idle), exit 18:00:30, then
xhci-hcd.3.auto: xHC error in resume, USBSTS 0x401, Reinit / root hub lost power or was reset /
usb 2-1: USB disconnect (the ASIX Ethernet adapter). Nothing re-enumerated it until the controller was re-probed
16 minutes later. USBSTS 0x401 = halted + Save/Restore Error: the controller state was lost with its power domain.
Cause (source): drivers/phy/apple/atc.c and drivers/usb/dwc3/dwc3-apple.c define no dev_pm_ops; tipd_resume()
only re-enables the IRQ / reset line. The Type-C controller sees no event for an unchanged partner, so the PHY lane
mode, pipehandler and dwc3 wrapper setup are never re-applied after the power domain comes back.
Do not work around it from userspace by unbinding drivers: unbinding dwc3-apple (or its device-link supplier
tps6598x) oopses in dwc3_apple_remove. A real fix means: PHY suspend/resume that re-runs
atcphy_configure() for the saved mode, a dwc3-apple resume that redoes its wrapper init, and a tipd_resume that
re-asserts the current mux state; tested with real suspend/resume cycles (needs someone at the keyboard to wake it).
Tracked by usb-c-survives-suspend in mseries-verify. Workaround: replug after resume, or avoid suspend while docked.
Which system boots is chosen by the Mac's firmware (NVRAM boot-volume), not by GRUB. On 2026-10-12 it pointed at
Macintosh HD, so every restart or power-on booted macOS unless Linux was picked by hand in the boot picker
(hold the power button). Fixed with the official tools (apt install asahi-bless asahi-nvram):
sudo asahi-bless --list-volumes # *1) Macintosh HD 2) Asahi Alarm Minimal (BTRFS)
sudo asahi-bless --set-boot "Asahi Alarm Minimal (BTRFS)" -y # make Linux the default
sudo asahi-bless --get-boot # verify (and --get-boot --next for a one-time override)
sudo asahi-bless --set-boot-macos -y # undo: macOS as default again
Things that put it back to macOS: a macOS update, choosing a startup disk inside macOS (System Settings > Startup
Disk), or --set-boot with the macOS name. If a reboot lands in macOS again, re-run the --set-boot line above.
asahi-bless needs root to read NVRAM, so the checklist robot (a user service) cannot verify this on its own.
The network dropdown has a Show speed in menu bar switch under the speed graph. When on, ↓ rate ↑ rate
appears beside the network icon, averaged over 10 s (the timer stops when the switch is off). The choice is
stored in ~/.config/mseries/network-speed.ini. Built by install-network-throughput.sh from
patches/network-panel-speed.patch.
Colours: libadwaita apps ignore elementary's @BLUEBERRY_* names and read accent_bg_color and friends, which
theme/gtk-4.0.css now sets too. Screen-corner radius raised from 10 to 14 (log out/in to apply; change
GALA_SCREEN_CORNER_RADIUS in the Gala drop-in).
install-power.shinstallsmseries-power: battery charge limit 80% (resume at 75%; battery health was 91% at 89 cycles), and on battery the power-saver profile plus a performance-core ceiling (3036 MHz → 2208 MHz). A udev rule on the charger re-applies it.sudo mseries-power limit 100lifts the cap;mseries-power statusshows state. Note power-profiles-daemon here only has a placeholder driver, so the profile name itself changes nothing; the CPU ceiling is what saves power.install-native-firefox.shreplaces Ubuntu's snap shim with Mozilla's arm64 apt package (key fingerprint checked, repository pinned). Headless start 0.56 s warm, about the same as the snap Chromium (0.53 s warm); an earlier "2.6 s" snap figure was a first-run penalty, so the benefit is "no snap", not speed.- Not possible: running macOS 27 in a VM here, see the answer in chat /
mseries/MACOS-VM.md.
install-pipewire-lv2.sh builds PipeWire's LV2 filter plugin (Ubuntu omits it), then
install-audio-stack.sh --with-dsp installs asahi-audio 3.4 and its LV2 plugins. The speaker sink becomes
"MacBook Pro J314 Speakers" (listed under Filters in wpctl status). Lesson: the first attempt left that sink at
70% because the restore code only looked at the Sinks list; it is fixed, and all sinks were muted at 0 within a
minute. Raise the volume slowly. Revert: bash mseries/install-audio-stack.sh --revert.
mseries/apply-desktop-settings.py enables Gala's edge tiling
(org.gnome.mutter edge-tiling): drag a window to the left or right screen
edge to snap it to that half. Super+Left and Super+Right tile the focused
window to a half (org.gnome.mutter.keybindings toggle-tiled-left/right); with
the Command/Ctrl swap that is physical left Ctrl plus the arrow key.
bash mseries/install-window-snapping.sh installs OS-level dconf defaults and
locks so snapping stays enabled after settings resets. It also builds native
quarter snapping for Gala 8.6.2 / Mutter 50: drag into any screen corner, or use
Super+Alt+U/I/J/K for top-left/top-right/bottom-left/bottom-right. Super+keypad
7/9/1/3 also works. Quarters activate at the next sign-in; the installer leaves
open apps running. See window-snapping/README.md
for verification and removal. Factory staging carries the half-snap policy;
the quarter plugin requires the native installer build.
The built-in 3024x1890 panel defaults to a 2.0 scale, which looks large. Gala
offers 1.0, 1.5, 1.75 and 2.0; 1.5 is the practical smaller setting. Apply it
through org.gnome.Mutter.DisplayConfig.ApplyMonitorsConfig (method 2,
persistent) or Pantheon's Displays settings.
mseries/dark-mode/mseries-dark-mode is one switch for the whole desktop: Pantheon, GTK 3/4,
libadwaita, Flatpak apps (through the settings portal) and Chromium-based browsers such as Brave
(which follow the portal's colour scheme). It sets Pantheon's AccountsService PrefersColorScheme
(1 dark, 2 light) and org.gnome.desktop.interface color-scheme. It never touches brightness,
refresh rate or power.
Quick Settings contains Appearance → Light / Dark / Automatic. Its installer
(bash mseries/install-quick-settings-brightness.sh) also removes the obsolete
standalone Dark/Auto menu-bar module, keeping a rollback copy outside Wingpanel's
plugin directory. Reload Wingpanel explicitly to activate; in the Rust session:
systemctl --user restart pantheon-wingpanel.service.
mseries/install-dark-mode.sh # install to ~/.local/bin
mseries-dark-mode on | off | toggle | status
mseries/install-dark-mode.sh --schedule # user timer runs `tick` at login and every 15 minutes
mseries-dark-mode schedule 20:00 06:30 # fixed hours instead of sunset/sunrise
mseries-dark-mode schedule sun # back to sunset/sunrise (default)
mseries-dark-mode location LAT LON # override the stored Night Light coordinatesmseries-dark-mode appearance light|dark|auto selects the persistent mode.
Light and Dark pause automatic switching until Automatic is selected again.
Automatic follows sunset/sunrise using Night Light coordinates (NOAA solar
maths), or the configured fixed hours, and books a one-shot timer for the next
transition. status --json exposes the selected mode and effective appearance.
The pastel accent overrides in theme/gtk-*.css already use dark label text on pale fills, so they
read in both schemes. Verified 2026-10-16: the settings portal reports dark (org.freedesktop.appearance color-scheme = 1), which is what Brave and Flatpak apps read. Not covered yet: Qt apps (adwaita-qt is not installed and no Qt style is forced) and Ghostty
(its palette is dark-only). Not verified on the M1 Pro in a live switch; see the handoff.