NoSignal — fully-offline Arch → Hyprland desktop installer

Single-script builder (nosignal.sh) that turns a stock Arch Linux ISO into a
fully-offline installer for a themed Hyprland + caelestia (Quickshell) desktop:
matching SDDM greeter, Btrfs/Limine bootable snapshots, chwd-style GPU
detection, and a curated "os updates" layer (keybind cheatsheet, settings
panels, system polish, on-box management skill). See README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
28allday 2026-06-21 08:51:50 +01:00
commit 19fd794b6b
228 changed files with 12008 additions and 0 deletions

36
.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# Build artifacts
work/
*.iso
build*.log
# Editor / OS cruft
*.swp
.DS_Store
# Offline payload cache (multi-GB: built AUR pkgs + repo mirror + chroot)
offline/
# VM test rig (QEMU certification harness) — maintainer-only dev tooling,
# kept LOCAL (scripts + disks/firmware/screenshots/logs all stay off the repo).
vm/
# squashfs debug extracts
sq/
*.sfs
# Integration inbox from live test boxes — content is fully folded into
# "os updates/" + nosignal.sh; kept locally for reference only
# (-done = archived rounds; hypros-* = pre-rename rounds 2+3)
nosignal-handoff/
nosignal-handoff-done/
hypros-handoff/
hypros-handoff-done/
# Dev-internal docs — kept LOCAL only, never in the public repo:
# the dev status log, hardware-test briefs, QA/builder handoff, and the
# internal phase/design docs (infra hostnames, dev-machine specs, process).
NOTES.md
ONBOX-BRIEF-*.md
dev-docs/
docs/PHASE2-DESIGN.md
docs/PHASE3-OFFLINE.md

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Gavin Nugent
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

202
README.md Normal file
View file

@ -0,0 +1,202 @@
# NoSignal OS
A single-script builder that turns a stock Arch Linux ISO into a **fully
offline installer** for a ready-to-use **Hyprland desktop**: Arch +
[Hyprland](https://hyprland.org) + the
[caelestia](https://github.com/caelestia-dots/caelestia) dotfiles/shell, with
a themed [SDDM](https://github.com/sddm/sddm) login screen that matches the
desktop.
Everything needed for the install is bundled **on the ISO** — a complete
pacman repo (every package incl. all GPU driver variants, with the caelestia
stack pre-built from the AUR at ISO-build time), the pinned dotfiles, the
theme. **No internet is needed to install or on first boot**: the machine
boots straight into the themed login screen and a fully configured desktop.
> NoSignal is the *ISO builder*. It bundles and installs the upstream caelestia
> dotfiles; it is not affiliated with that project.
> ⚠️ **Experimental — and built to be modified.** NoSignal OS is young and
> opinionated; expect rough edges. It is designed to be reshaped by *you*: every
> NoSignal tweak sits in a config-override layer on top of stock caelestia
> (delete a file to revert one), and the installed system ships an **on-box AI
> guide** so you can customise and fix it with
> [Claude Code](https://claude.com/claude-code) — see
> **[Customising the desktop](#customising-the-desktop-on-box-ai-guide)** below.
## What you get on the installed system
- **Hyprland + caelestia shell** (Quickshell), restyled with a restrained
flat look (no blur/glassmorphism) and the OS's own Moebius-style wallpaper
set (dynamic Material palette generated from the wallpaper).
- **Themed SDDM login** that mirrors the desktop palette/wallpaper/font
(`sudo sddm-theme-sync` re-syncs it after you change wallpaper).
- **Omarchy-style keybindings**`Super+K` opens a searchable cheatsheet of
every bind. Universal `Super+C`/`Super+V` copy/paste, `Print` = region
screenshot, `Super+Space` launcher, `Super+D` dashboard/calendar,
`Super+Grave` workspace overview.
- **Software story**: `yay` (AUR), the **Shelly** GUI store on `Super+I`
(repos + AUR + Flathub), flatpak preconfigured.
- **Btrfs + bootable snapshots**: every package transaction snapshots
(snapper + snap-pac), and each snapshot appears as a bootable entry in the
**Limine** menu (10-second timeout) — roll back a bad update from the boot
screen.
- **`nosignal-update`**: one command for snapshot → package upgrade → layer
migrations; a Settings → Updates page shows pending counts.
- **Gaming (opt-in)**: the `[multilib]` repo is enabled and Omarchy-installer
shims are included, so
[DeckShift](https://github.com/28allday/DeckShift)-style Steam gaming
sessions can be added post-install — including a login fix that keeps a
password on cold boots. Not active unless you set it up.
- Bluetooth, printing (CUPS + network discovery), power-profiles, zram swap,
ufw on, locale/timezone/keymap of your choice.
## Customising the desktop (on-box AI guide)
NoSignal OS is meant to be tinkered with, so nothing is locked down. Every
NoSignal change lives in a **config-override layer** at `~/.config/caelestia/`
on top of stock caelestia — edit a file to change behaviour, delete it to fall
back to the default. Updates and rollbacks are safe: `nosignal-update` snapshots
before every upgrade, and each snapshot is a bootable entry in the Limine menu.
To make changes easy — even if you don't know the internals — the installed
system ships an **on-box AI skill for [Claude Code](https://claude.com/claude-code)**.
It explains exactly what's safe to edit, how to change keybinds / monitors /
wallpaper / theme, how updates and snapshots work, how to install software, and
how to recover the desktop if the shell ever fails to start.
- **Just ask.** Run `claude` on a NoSignal machine and it **auto-discovers** the
`nosignal` skill — say *"customise my NoSignal desktop"* or *"the bar didn't
load, fix it"* and it reads the guide before touching anything.
- **Point an assistant at the files directly** if you prefer:
- `~/.claude/skills/nosignal/SKILL.md` — the Claude skill (guardrails + tasks)
- `~/.local/share/nosignal/ONBOX-AI-NOTES.md` — the full system briefing
- `~/.local/share/nosignal/NoSignal-keybindings.md` — the keybinding reference
The skill is strictly for managing **your installed system** — it carries no
build/release tooling, and it always reads the guardrails before editing config.
## Build the ISO
The builder runs on an **Arch Linux** (or Arch-based) host with an internet
connection — it uses `pacman`, `makepkg`, and `devtools` to assemble the offline
package set, so it can't run on a non-Arch distro.
**1. Install the build dependencies:**
```bash
sudo pacman -S --needed git libisoburn squashfs-tools coreutils devtools pacman-contrib
```
**2. Clone this repository and enter it:**
```bash
git clone https://github.com/28allday/NoSignal-OS.git
cd NoSignal-OS
```
**3. Download a stock Arch Linux ISO into this folder.** The filename must start
with `archlinux-`. Grab the latest from any mirror, e.g.:
```bash
curl -LO https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso
```
(or pick a mirror from <https://archlinux.org/download/>.)
**4. Run the builder:**
```bash
./nosignal.sh
```
It downloads the full package closure and builds the AUR pieces in a clean
chroot — allow roughly **1540 minutes** on the first run (you'll be asked for
`sudo`). Everything is cached in `./offline/`, so later rebuilds take a couple of
minutes.
**5. Done.** `nosignal-arch-YYYYMMDD.iso` (~4 GB) appears in the folder —
Ventoy-compatible and `dd`-bootable. Write it to a USB stick and install (see
**Install** below).
## Install
**1. Write the ISO to a USB stick.** Replace `/dev/sdX` with your USB device
(check it with `lsblk` first — this erases the whole stick):
```bash
sudo dd if=nosignal-arch-YYYYMMDD.iso of=/dev/sdX bs=4M status=progress conv=fsync
```
(or just drop the `.iso` onto a [Ventoy](https://www.ventoy.net) USB.)
**2. Boot the USB** on the target machine — **UEFI only** (no BIOS/MBR).
**3. Follow the installer.** It is **fully offline** (no network questions) and
asks for: hostname, user, password, your region (timezone / locale / keyboard),
and the target disk. It then **wipes that disk** — 1G EFI + btrfs with
`@`/`@home`/`@snapshots`/`@log` subvolumes and the Limine bootloader — and runs
a clean, centred progress screen. **It erases the chosen disk — back up first.**
**4. Reboot** into the themed SDDM login and your desktop. Connect Wi-Fi from the
desktop's network panel; once you're online, run `sudo pacman -Syu` one time to
sync the package databases.
## GPU detection
The installer auto-detects the GPU(s) and installs the right drivers,
borrowing CachyOS [`chwd`](https://github.com/CachyOS/chwd)'s **method** (PCI
display-class scan → vendor-id → driver set) but implemented natively, so the
ISO stays vanilla Arch (no CachyOS repos / lib32 / cachyos-kernel deps).
Multiple GPUs union their package sets (e.g. an Intel/AMD iGPU + an NVIDIA
dGPU). All variants are bundled in the offline repo; only the matching subset
is installed.
| Detected | Packages |
|----------|----------|
| NVIDIA (`10de`) | `nvidia-open-dkms nvidia-utils egl-wayland libva-nvidia-driver nvidia-settings linux-headers` + DRM/KMS setup |
| AMD (`1002`) | `mesa vulkan-radeon` |
| Intel (`8086`) | `mesa vulkan-intel intel-media-driver` |
| VM guest (`1af4`/`1b36`/`1234`/`15ad`/`80ee`) | `qemu-guest-agent spice-vdagent` |
| Unknown | `vulkan-swrast` (software fallback) |
For NVIDIA it also does the Wayland-critical bits (the same ones chwd does):
early-loads `nvidia nvidia_modeset nvidia_uvm nvidia_drm` in the initramfs,
drops the `kms` hook so nouveau doesn't grab the card, sets
`nvidia_drm.modeset=1`, and writes the EGL/VA env to `/etc/environment`.
## Notes / current limitations
- **UEFI only.** No BIOS/MBR boot.
- **No disk encryption** yet.
- **NVIDIA uses the open kernel module** (`nvidia-open-dkms`), which is for
Turing and newer (RTX 20-series+). Older Maxwell/Pascal cards would need
the proprietary/legacy branch — not yet selected by generation.
- The bundled packages are a snapshot from build day — run `sudo pacman -Syu`
once online.
## Credits
NoSignal OS is a thin builder around excellent upstream work — it bundles,
themes, and installs these projects rather than replacing them. Please support
the originals. Each remains under its own licence.
- **[Arch Linux](https://archlinux.org)** — the base system and live ISO.
- **[Hyprland](https://hyprland.org)** — the Wayland compositor.
- **[caelestia](https://github.com/caelestia-dots/caelestia)** — the desktop
dotfiles and shell that define the look and most of the UX. The installed
shell is a light soft-fork; the CLI / theming engine is upstream, unchanged.
- **[Quickshell](https://quickshell.outfoxxed.me)** — the runtime the shell runs on.
- **[SDDM](https://github.com/sddm/sddm)** — the login manager (themed to match).
- **[Limine](https://github.com/limine-bootloader/limine)**, with **snapper** and
**snap-pac** — bootloader and Btrfs snapshot tooling (bootable snapshots).
- **[Omarchy](https://omarchy.org)** — keybinding layout and UX inspiration, and
its prebuilt `[omarchy]` package repo for the Limine snapshot tools.
- **[CachyOS `chwd`](https://github.com/CachyOS/chwd)** — the GPU-detection
*method* (PCI scan → vendor-id → driver set), reimplemented natively here.
- Fonts: **JetBrains Mono Nerd Font** and **Material Symbols**.
## License
MIT — see [LICENSE](LICENSE). The wallpaper art set is the project author's
own original work and ships with the ISO under the same licence.

View file

@ -0,0 +1,138 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
"type": "file",
"source": "~/.config/fastfetch/logo.txt",
"color": { "1": "green" },
"padding": {
"top": 2,
"right": 6,
"left": 2
}
},
"modules": [
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Hardware──────────────────────┐"
},
{
"type": "host",
"key": " PC",
"keyColor": "green"
},
{
"type": "cpu",
"key": "│ ├",
"showPeCoreCount": true,
"keyColor": "green"
},
{
"type": "gpu",
"key": "│ ├",
"detectionMethod": "pci",
"keyColor": "green"
},
{
"type": "display",
"key": "│ ├󱄄",
"keyColor": "green"
},
{
"type": "disk",
"key": "│ ├󰋊",
"keyColor": "green"
},
{
"type": "memory",
"key": "│ ├",
"keyColor": "green"
},
{
"type": "swap",
"key": "└ └󰓡 ",
"keyColor": "green"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Software──────────────────────┐"
},
{
"type": "command",
"key": " OS",
"keyColor": "blue",
"text": "echo \"NoSignal\""
},
{
"type": "kernel",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "wm",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "de",
"key": " DE",
"keyColor": "blue"
},
{
"type": "terminal",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "packages",
"key": "│ ├󰏖",
"keyColor": "blue"
},
{
"type": "wmtheme",
"key": "│ ├󰉼",
"keyColor": "blue"
},
{
"type": "command",
"key": "│ ├󰸌",
"keyColor": "blue",
"text": "theme=$(caelestia scheme get 2>/dev/null | awk -F': *' '/^Name:/{print $2; exit}'); [ -n \"$theme\" ] || theme=dynamic; echo -e \"$theme \\e[38m●\\e[37m●\\e[36m●\\e[35m●\\e[34m●\\e[33m●\\e[32m●\\e[31m●\""
},
{
"type": "terminalfont",
"key": "└ └",
"keyColor": "blue"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌────────────────────Age / Uptime────────────────────┐"
},
{
"type": "command",
"key": "󱦟 OS Age",
"keyColor": "magenta",
"text": "birth_install=$(stat -c %W /); current=$(date +%s); time_progression=$((current - birth_install)); days_difference=$((time_progression / 86400)); echo $days_difference days"
},
{
"type": "uptime",
"key": "󱫐 Uptime",
"keyColor": "magenta"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break"
]
}

26
assets/fastfetch-logo.txt Normal file
View file

@ -0,0 +1,26 @@
⢀⣀⣤⣴⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣦⣤⣀⡀
⢀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⡀
⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⡀
⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀
⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀
⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄
⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆
⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆
⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿⣿⠿⡿⠿⠿⠿⣿⣿⣿⠿⠿⠿⠿⢿⣿⣿⣿⣿⠿⣿⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿⡆
⢀⣿⣿⣿⣿⣶⡶⠸⣿⣿⣿⣿⠛⠛⠃⠛⠛⠛⢻⣿⡟⢐⣓⣒⣒⣒⣛⣿⣿⠰⠶⠶⠶ ⣿⣿⣛⡋⢈⣉⡭⠿⢿⣿⡇⢸⣿⣿⣿⣿⣿⡀
⢸⣿⣿⣿⣿⣿⢃⡄⢻⣿⣿⣿⣿⣿⠃⣿⣿⡏⢸⣏⡄⢸⣗⣒⣒⣒⣺⣯⣉⡉⣉⣉⣉⣉⣉⣿⡿⢁⣿⣷⡒⣶⣾⣿⡇⢸⣿⣿⣿⣿⣿⡇
⣿⣿⣿⣿⠿⢁⣾⣿⣄⠻⢿⣿⡿⢃⣼⣿⣿⡇⣸⣿⡇⢸⠁⣤⣤⡤⢸⣿⣿⣤⣶⣶⣶⢀⣿⣿⣥⣾⠩⣤⠄⣙⢻⣿⣇⠸⠿⠿⠟⣩⣿⣿
⣿⣿⣿⣷⣶⣿⣿⣿⣿⣷⣶⣷⣶⣿⣿⣿⣤⣴⣿⣿⣧⣾⣦⣶⣶⣶⣼⣿⣿⣿⣿⣤⣤⣾⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⣷⣶⣶⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿ ⠙⢿ ⢸⡟⢁⣠⡀⠙⣿⣿⣿⡏⠠⣤⣿⡇⢸⡟⠁⣠⣄⣙⣿ ⠈⢻⡇⢸⣿⡏ ⠘⣿⡇ ⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿ ⣦⡀ ⢸⡄⠘⠿⠟ ⣿⣿⣿⡷⠦ ⢹⡇⢸⣇ ⠿⠄ ⣿ ⢰⡄⠁⢸⡟ ⠘ ⢹⡇ ⠿⢿⣿⣿⣿⣿⣿⣿
⢸⣿⣿⣿⣿⣿⣿⣶⣿⣿⣶⣾⣿⣶⣦⣶⣾⣿⣿⣿⣶⣶⣶⣿⣷⣾⣿⣷⣶⣶⣾⣿⣶⣾⣿⣶⣾⣷⣾⣿⣷⣶⣷⣶⣶⣾⣿⣿⣿⣿⣿⡇
⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁
⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇
⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏
⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏
⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋
⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁
⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁
⠈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠁
⠈⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁
⠈⠉⠛⠻⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠟⠛⠉⠁

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -0,0 +1,8 @@
[Plymouth Theme]
Name=NoSignal
Description=NoSignal boot splash — NO SIGNAL logo on black
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/nosignal
ScriptFile=/usr/share/plymouth/themes/nosignal/nosignal.script

View file

@ -0,0 +1,43 @@
# NoSignal Plymouth splash — the "NO SIGNAL / 入力信号なし" logo centred on black.
# Shows between Limine and the SDDM greeter (and on shutdown/reboot). The logo's
# black disc blends into the background, so only the green text floats on black.
# Solid black background.
Window.SetBackgroundTopColor(0, 0, 0);
Window.SetBackgroundBottomColor(0, 0, 0);
screen_width = Window.GetWidth();
screen_height = Window.GetHeight();
# Scale the logo to ~67.5% of the shorter screen edge (0.45 enlarged by half),
# keeping aspect ratio, so it is a sensible size on both 1080p and 4K.
short_edge = screen_height;
if (screen_width < screen_height)
short_edge = screen_width;
target = short_edge * 0.675;
orig = Image("logo.png");
scale = target / orig.GetHeight();
logo.image = orig.Scale(orig.GetWidth() * scale, orig.GetHeight() * scale);
logo.sprite = Sprite(logo.image);
logo.sprite.SetX(screen_width / 2 - logo.image.GetWidth() / 2);
logo.sprite.SetY(screen_height / 2 - logo.image.GetHeight() / 2);
# Static splash — full opacity, no animation.
logo.sprite.SetOpacity(1);
# Subtle status line (fsck / "press a key" style messages) below the logo.
message.sprite = Sprite();
message.sprite.SetX(20);
message.sprite.SetY(screen_height - 36);
fun message_callback(text) {
message.image = Image.Text(text, 0.66, 0.84, 0.45); # muted NoSignal green
message.sprite.SetImage(message.image);
}
Plymouth.SetMessageFunction(message_callback);
# On quit (handing off to the greeter), snap the logo to full opacity.
fun quit_callback() {
logo.sprite.SetOpacity(1);
}
Plymouth.SetQuitFunction(quit_callback);

View file

@ -0,0 +1 @@
{"name": "shadotheme", "flavour": "default", "mode": "dark", "variant": "tonalspot", "colours": {"primary_paletteKeyColor": "6f72ac", "secondary_paletteKeyColor": "75758e", "tertiary_paletteKeyColor": "9d648f", "neutral_paletteKeyColor": "78767b", "neutral_variant_paletteKeyColor": "777680", "background": "131317", "onBackground": "e5e1e7", "surface": "131317", "surfaceDim": "131317", "surfaceBright": "39393d", "surfaceContainerLowest": "0e0e11", "surfaceContainerLow": "1b1b1f", "surfaceContainer": "1f1f23", "surfaceContainerHigh": "2a292e", "surfaceContainerHighest": "353438", "onSurface": "e5e1e7", "surfaceVariant": "46464f", "onSurfaceVariant": "c7c5d1", "inverseSurface": "e5e1e7", "inverseOnSurface": "303034", "outline": "918f9a", "outlineVariant": "46464f", "shadow": "000000", "scrim": "000000", "surfaceTint": "bfc1ff", "primary": "bfc1ff", "onPrimary": "282b60", "primaryContainer": "6f72ac", "onPrimaryContainer": "000028", "inversePrimary": "565992", "secondary": "c5c4e0", "onSecondary": "2e2f44", "secondaryContainer": "44455c", "onSecondaryContainer": "b3b3ce", "tertiary": "f4b2e2", "onTertiary": "4e1e45", "tertiaryContainer": "ba7eaa", "onTertiaryContainer": "000000", "error": "ffb4ab", "onError": "690005", "errorContainer": "93000a", "onErrorContainer": "ffdad6", "primaryFixed": "e0e0ff", "primaryFixedDim": "bfc1ff", "onPrimaryFixed": "12144a", "onPrimaryFixedVariant": "3e4278", "secondaryFixed": "e1e0fd", "secondaryFixedDim": "c5c4e0", "onSecondaryFixed": "191a2e", "onSecondaryFixedVariant": "44455c", "tertiaryFixed": "ffd7f1", "tertiaryFixedDim": "f4b2e2", "onTertiaryFixed": "35082f", "onTertiaryFixedVariant": "67355d", "term0": "353434", "term1": "a875ff", "term2": "44def5", "term3": "ffdcf2", "term4": "97aad7", "term5": "b29feb", "term6": "9dceff", "term7": "e8d3de", "term8": "ac9fa9", "term9": "bd95ff", "term10": "89ecff", "term11": "fff0f6", "term12": "b4c1dc", "term13": "c8b5f5", "term14": "bae0ff", "term15": "ffffff", "rosewater": "f7eff9", "flamingo": "e8def3", "pink": "e1d8ff", "mauve": "bdb9ff", "red": "bfa6fe", "maroon": "c7b6ed", "peach": "e0c2f9", "yellow": "ffecf3", "green": "c8e3ff", "teal": "d2e0ff", "sky": "cedaff", "sapphire": "b5c5ff", "blue": "aeb8ff", "lavender": "c6c8ff", "klink": "7083d2", "klinkSelection": "6f83d2", "kvisited": "7e73db", "kvisitedSelection": "7d73db", "knegative": "9d69ff", "knegativeSelection": "9b6aff", "kneutral": "c794ff", "kneutralSelection": "c794ff", "kpositive": "60adff", "kpositiveSelection": "60adff", "text": "e5e1e7", "subtext1": "c7c5d1", "subtext0": "918f9a", "overlay2": "7e7c86", "overlay1": "6b6972", "overlay0": "595860", "surface2": "48474e", "surface1": "37373d", "surface0": "25252a", "base": "131317", "mantle": "131317", "crust": "121216", "success": "B5CCBA", "onSuccess": "213528", "successContainer": "374B3E", "onSuccessContainer": "D1E9D6"}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

2366
nosignal.sh Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,299 @@
# NoSignal — Keybinding Map
> **System:** Arch Linux (rolling), hostname `nosignal`, Wayland compositor **Hyprland**.
> **Base:** [Caelestia](https://github.com/caelestia-dots) dotfiles + shell (QuickShell).
> **NoSignal layer:** styling flattened, stock apps rebound, overview added, and
> **the key layout follows Omarchy's defaults** (see
> `omarchy-keys/README.md` for the full mapping and deviations).
>
> **Modifier legend:** `Super` = Windows/Meta key · `Ctrl` · `Alt` · `Shift`.
> `XF86*` = laptop media/function keys. `mouse_up/down` = scroll wheel.
>
> Bindings are **variable-driven**. The tables below show the *resolved* keys
> (variables from `variables.conf` + overrides applied). See "Source map" at the
> end for where each value comes from and how to change it.
---
## 0. Built-in help (NoSignal addition)
| Keys | Action |
|------|--------|
| `Super+K` | Open the searchable on-screen keybinding cheatsheet (Omarchy key) |
| `Super+/` | Same (alias) |
| `Super+F1` | Same (alias) |
The cheatsheet is a fuzzel dmenu list — start typing to filter by key or by
category tag (`[Window]`, `[Apps]`, `[Audio]`, …). Selecting a line copies that
shortcut to the clipboard (if `wl-copy` is present); it never executes anything.
**This very document is the single source of truth.** The on-screen help is
*generated* from the tables below — `nosignal-keybinds` regenerates the list from
this `.md` on every launch, so the cheatsheet can never drift from the doc.
Edit the keymap here and the help updates itself.
## 1. Shell & session
| Keys | Action |
|------|--------|
| `Super+Space` | Open app launcher (Omarchy key) |
| `Super` (tap & release) | Open app launcher (tapping another key/mouse cancels it) |
| `Super+Escape` | Session menu (logout / shutdown / reboot) |
| `Ctrl+Alt+Delete` | Session menu (alias, old key) |
| `Super+D` | Toggle dashboard — calendar + widgets (was hover-only) |
| `Super+Alt+Space` | Open Settings (Caelestia nexus) |
| `Super+N` | Toggle sidebar |
| `Super+Ctrl+K` | Show all shell panels |
| `Super+Ctrl+L` | Lock screen (Omarchy key) |
| `Super+Alt+L` | Restart shell daemon + re-lock (recover a broken lock) |
| `Super+Shift+Comma` | Clear all notifications (Omarchy key) |
| `Ctrl+Alt+C` | Clear all notifications (alias, old key) |
| `Ctrl+Super+Shift+R` | Kill the Caelestia shell (`qs -c caelestia kill`) |
| `Ctrl+Super+Alt+R` | Restart the Caelestia shell |
| `Super+Alt+F12` | Fire a test notification (dev/debug) |
## 2. Overview (NoSignal addition)
| Keys | Action |
|------|--------|
| `Super+Grave` | Toggle the overview sidecar (moved off Super+Tab for Omarchy) |
## 3. Workspaces
| Keys | Action |
|------|--------|
| `Super+1``9`,`0` | Go to workspace 110 |
| `Super+Tab` | Next workspace (Omarchy key) |
| `Super+Shift+Tab` | Previous workspace (Omarchy key) |
| `Super+Ctrl+Tab` | Back to former workspace (Omarchy key) |
| `Ctrl+Super+1``9`,`0` | Go to workspace **group** 110 |
| `Ctrl+Super+Left` / `Right` | Previous / next workspace |
| `Super+Page_Up` / `Page_Down` | Previous / next workspace |
| `Super` + scroll up/down | Next / previous workspace |
| `Ctrl+Super` + scroll up/down | Jump workspace group ±10 |
| `Super+S` | Toggle special (scratchpad) workspace |
## 4. Moving windows between workspaces
| Keys | Action |
|------|--------|
| `Super+Shift+1``9`,`0` | Move window to workspace 110 (Omarchy key) |
| `Super+Alt+1``9`,`0` | Move window to workspace 110 (alias, old key) |
| `Ctrl+Super+Alt+1``9`,`0` | Move window to workspace **group** 110 |
| `Super+Alt+Page_Up` / `Page_Down` | Move window to workspace ±1 |
| `Super+Alt` + scroll up/down | Move window to workspace ±1 |
| `Ctrl+Super+Shift+Right` / `Left` | Move window to workspace +1 / 1 |
| `Ctrl+Super+Shift+Up` | Move window to special workspace |
| `Ctrl+Super+Shift+Down` | Move window to first empty workspace |
| `Super+Alt+S` | Move window to special workspace |
## 5. Window focus, move & resize
| Keys | Action |
|------|--------|
| `Super+←/→/↑/↓` | Move focus left/right/up/down |
| `Alt+Tab` / `Shift+Alt+Tab` | Cycle focus next / previous window (Omarchy key) |
| `Super+Shift+←/→/↑/↓` | Move window in that direction |
| `Super+Minus` / `Super+Equal` | Resize narrower / wider (/+10% width) |
| `Super+Shift+Minus` / `Equal` | Resize shorter / taller (/+10% height) |
| `Super+Alt+←/→/↑/↓` | Resize active window |
| `Super` + left-drag | Move window (mouse) |
| `Super+Z` + drag | Move window (mouse) |
| `Super` + right-drag | Resize window (mouse) |
| `Super+X` + drag | Resize window (mouse) |
| `Ctrl+Super+\` | Center window |
| `Ctrl+Super+Alt+\` | Resize to 55%×70% and center |
| `Super+Alt+\` | Picture-in-picture (PiP) mode |
| `Super+T` | Toggle floating (Omarchy key) |
| `Super+J` | Toggle window split (Omarchy key) |
| `Super+P` | Pseudo window (Omarchy key) |
| `Super+O` | Pin window — show on all workspaces (Omarchy "pop out") |
| `Super+F` | Fullscreen |
| `Super+Alt+F` | Fullscreen **with borders** |
| `Super+W` | Close window (Omarchy key) |
## 6. Window groups (tabbed stacks)
| Keys | Action |
|------|--------|
| `Super+G` | Toggle group on active window (Omarchy key) |
| `Super+Alt+G` | Move window out of group (Omarchy key) |
| `Super+Alt+Tab` | Cycle to next window in group (Omarchy key) |
| `Super+Alt+Shift+Tab` | Cycle to previous window in group (Omarchy key) |
| `Ctrl+Alt+Tab` | Change active group member (forward) |
| `Ctrl+Shift+Alt+Tab` | Change active group member (backward) |
## 7. Apps
| Keys | Action | App (NoSignal default) |
|------|--------|----------------------|
| `Super+Return` | Terminal | **kitty** |
| `Super+Shift+Return` | Browser | **chromium** |
| `Super+Shift+B` | Browser (alias) | **chromium** |
| `Super+Shift+N` | Text editor | **gnome-text-editor** |
| `Super+Shift+F` | File manager | **nautilus** |
| `Super+Alt+E` | File manager (alt) | nemo |
| `Super+Shift+G` | GitHub Desktop | github-desktop |
| `Super+I` | Install software (app store) | **Shelly** |
| `Ctrl+Alt+Escape` | Process/task viewer | qps |
| `Super+Ctrl+A` | Audio control (Omarchy key) | pavucontrol |
| `Ctrl+Alt+V` | Audio control (alias) | pavucontrol |
| `Super+Ctrl+H` | Monitor layout editor & profiles | **hyprmoncfg** (TUI) |
> All app binds use Omarchy's keys. Caelestia's stock app picks
> (foot / zen-browser / codium / thunar) are **not installed**; NoSignal points
> these binds at shipped apps via `hypr-vars.conf`.
## 8. Special-workspace toggles
| Keys | Action |
|------|--------|
| `Super+Ctrl+T` | System monitor — "Activity" (Omarchy key) |
| `Ctrl+Shift+Escape` | System monitor (alias) |
| `Super+Shift+M` | Music (Omarchy key) |
| `Super+M` | Music player — CLIAmp, floating (default for audio files) |
| `Super+Shift+D` | Communication (moved from Super+D — that's the dashboard now) |
| `Super+R` | To-do |
## 9. Screenshots & screen recording
| Keys | Action |
|------|--------|
| `Print` | Region capture (crosshair) → clipboard + ~/Pictures/Screenshots |
| `Shift+Print` | Full screen → clipboard + ~/Pictures/Screenshots |
| `Super+Shift+S` | Region capture, frozen screen (swappy → ~/Pictures/Screenshots) — becomes **Gaming Mode** after opting in (see §14) |
| `Super+Shift+Alt+S` | Region capture, live (swappy → ~/Pictures/Screenshots) |
| `Super+Print` | Color picker — hyprpicker (Omarchy key) |
| `Super+Shift+C` | Color picker (alias) |
| `Ctrl+Alt+R` | Record screen (no audio) |
| `Super+Alt+R` | Record screen **with sound** |
| `Super+Shift+Alt+R` | Record a region |
## 10. Audio & brightness
| Keys | Action |
|------|--------|
| `XF86AudioRaiseVolume` / `LowerVolume` | Volume ±10% (unmutes first) |
| `XF86AudioMute` | Mute / unmute output |
| `XF86AudioMicMute` | Mute / unmute microphone |
| `XF86MonBrightnessUp` / `Down` | Screen brightness up / down |
## 11. Media playback
| Keys | Action |
|------|--------|
| `Ctrl+Super+Space` or `XF86AudioPlay`/`Pause` | Play / pause |
| `Ctrl+Super+Equal` or `XF86AudioNext` | Next track |
| `Ctrl+Super+Minus` or `XF86AudioPrev` | Previous track |
| `XF86AudioStop` | Stop |
## 12. Clipboard & emoji
| Keys | Action |
|------|--------|
| `Super+C` | **Copy** — universal (terminals → Ctrl+Shift+C, GUI → Ctrl+C) |
| `Super+V` | **Paste** — universal (terminals → Ctrl+Shift+V, GUI → Ctrl+V) |
| `Super+Ctrl+V` | Clipboard history (Omarchy key) |
| `Super+Alt+V` | Clipboard history — delete-entry mode |
| `Super+Ctrl+E` | Emoji picker (Omarchy key) |
| `Super+Period` | Emoji picker (alias) |
| `Ctrl+Shift+Alt+V` | Alternate paste — types last clip (needs ydotool, NOT installed: dead bind) |
## 13. Power
| Keys | Action |
|------|--------|
| `Super+Shift+L` | Suspend-then-hibernate |
## 14. Gaming Mode (opt-in)
Not active on a fresh install. Opt in on the installed system with
`sh ~/deckshift/deckshift.sh`, then
`sh ~/.local/share/nosignal/deckshift-login/install-deckshift-login.sh`.
`Super+Shift+S` is bound in the **base** and self-guards: it launches Gaming Mode
only if DeckShift is installed and **does nothing** otherwise. (It replaces
caelestia's Super+Shift+S screenshot-freeze for everyone — region shots stay on
Print / Shift+Print / Super+Shift+Alt+S.)
| Keys | Action |
|------|--------|
| `Super+Shift+S` | Switch to Gaming Mode (full DeckShift gamescope/Steam session via SDDM restart) **if DeckShift is installed**; does nothing otherwise |
| `Super+Shift+R` | Exit Gaming Mode back to the desktop — works **inside the gaming session only** (or Steam > Power > Exit to Desktop) |
Cold boots always show the SDDM password greeter; only the desktop↔gaming
switch itself skips the password (one-shot autologin).
---
## Source map (for the install-system handoff)
All paths below; `~/.config/hypr` → symlink → `~/.local/share/caelestia/hypr`.
| File | Role |
|------|------|
| `~/.config/hypr/hyprland.conf` | Top-level; sources everything in order |
| `~/.config/hypr/hyprland/keybinds.conf` | **The bind definitions** (uses `$kb*` variables) |
| `~/.config/hypr/variables.conf` | **Default key + app values** (`$kbCloseWindow = Super, Q`, etc.) |
| `~/.config/caelestia/hypr-vars.conf` | **NoSignal overrides** — app rebinds + flattened styling + **Omarchy `$kb*` remaps** |
| `~/.config/caelestia/hypr-user.conf` | **NoSignal overrides** — overview (Super+Grave), help binds, **Omarchy unbinds/extra binds**, `kb_layout = gb`, window rules |
| `~/.local/bin/nosignal-keybinds-gen` | **Generator** — parses this `.md` → display-ready cheatsheet lines (single source of truth) |
| `~/.local/bin/nosignal-keybinds` | **NoSignal help launcher** — regenerates from this doc each run, shows the fuzzel dmenu |
| `~/.local/share/nosignal/keybinds.list` | Generated cache (auto-rebuilt; used as fallback if the doc is missing) |
**How the override layering works** (load order matters — later wins):
`variables.conf` sets stock defaults → `hyprland.conf` sources `hypr-vars.conf`
*before* `keybinds.conf` (so `$kb*` remaps take effect) and `hypr-user.conf`
*after everything* (so its `unbind`/`bind` lines win).
**NoSignal deltas vs. stock Caelestia:**
- **Key layout remapped to Omarchy defaults** — full mapping table
and deliberate deviations in `omarchy-keys/README.md`.
- Apps: terminal `foot→kitty`, browser `zen-browser→chromium`,
editor `codium→gnome-text-editor`, file manager `thunar→nautilus`.
- Added bind: `Super+Grave` → overview sidecar (`exec-once = qs -c overview -d`).
- Added bind: `Super+K` (+ `Super+/`, `Super+F1`) → searchable keybinding
cheatsheet, **generated from this document** so the two never drift.
- Added bind: `Super+I` → Shelly software store (repos + AUR + Flathub); yay
and flatpak/Flathub added underneath (see `software-install/`).
- Added binds: `Super+C` / `Super+V` universal copy/paste (clipboard
history → `Super+Ctrl+V`); `Print` region screenshot;
`Super+D` dashboard (communication → `Super+Shift+D`).
- Keyboard layout forced to `gb` (UK).
- Glassmorphism off: blur/shadows disabled, opacity 1.0, smaller rounding/gaps.
- Keys left unbound by the Omarchy remap: `Super+Q`, `Super+E`,
`Super+U`, `Super+Comma`, `Super+L`, `Super+Alt+Space`.
### Rebuild: this whole component ships in Downloads
Because the live system gets wiped and only Downloads is kept, the entire
feature is packaged there and reinstalled with one command per component:
| File in Downloads | Installs to |
|-------------------|-------------|
| `nosignal-keybinds-gen` | `~/.local/bin/` (the markdown→list generator) |
| `nosignal-keybinds` | `~/.local/bin/` (the cheatsheet launcher) |
| `NoSignal-keybindings.md` (this file) | `~/.local/share/nosignal/` (canonical source of truth) |
| `hyprland-keybinds-help.conf` | appended to `~/.config/caelestia/hypr-user.conf` (Super+/ + F1 binds) |
| `install-keybinds-help.sh` | the installer — run it after rebuild |
| `omarchy-keys/` | the Omarchy remap (vars + user conf + installer) |
```sh
sh ~/Downloads/install-keybinds-help.sh
sh ~/Downloads/omarchy-keys/install-omarchy-keys.sh
```
Installers are idempotent (safe to re-run) and reload Hyprland if it's
running. **No PATH dependency:** binds use the absolute path
`~/.local/bin/nosignal-keybinds`, and that script locates its generator next to
itself — so it works even if `~/.local/bin` isn't on the session `PATH`.
No keymap data lives in the scripts; editing this doc is all that's needed to
change the on-screen help. Deps: `awk`, `fuzzel`, optional `wl-copy`.
**To change a binding:** edit `keybinds.conf` for new actions, or redefine the
matching `$kb*` variable. Put NoSignal-specific changes in `hypr-user.conf` /
`hypr-vars.conf` so they survive a Caelestia update (the `hypr` symlink target
gets overwritten on upgrade; the two override files do not).
---
*Keybindings follow Omarchy defaults; the Super+K cheatsheet is generated from this file.*

View file

@ -0,0 +1,128 @@
# NoSignal — notes for an AI agent working on this machine
You are on **NoSignal**: Arch Linux + Hyprland + the Caelestia shell (Quickshell)
+ SDDM (themed greeter). A "NoSignal layer" of config and tooling sits ON TOP of
stock Caelestia. Read this before changing desktop/system config — your job is to
help the user manage their machine safely.
## Config layout — what is safe to edit
- `~/.config/caelestia/`**the NoSignal override layer** (edit here):
- `hypr-user.conf` — user binds/rules/exec-once; sourced LAST, so it wins.
This is the right place for new keybinds. `unbind =` works here.
- `hypr-vars.conf``$variables` ($terminal, $kb*, gaps, blur…); sourced
BEFORE keybinds.conf consumes them.
- `shell.json`, `shell-tokens.json` — Caelestia shell appearance/behaviour.
- Deleting any of these files restores stock Caelestia behaviour.
- `~/.config/hypr` is a **SYMLINK** into `~/.local/share/caelestia` (the
dotfiles clone). Several other `~/.config` entries (foot, fish, fastfetch,
uwsm) are symlinks into it too. **Never replace these symlinks with copies**
and never edit the clone's hyprland.conf directly — use hypr-user.conf.
- `~/.config/hypr/scripts/wsaction.fish` and `configs.fish` are **bash** ports
kept under their original .fish names (the stock config calls those paths).
Keep the names and the bash shebang.
## Updates
- `~/.local/share/nosignal/` — the on-system layer: component sources, the
keybindings doc, and `nosignal-update/` (the update command + its migrations).
- **`nosignal-update`** = the system update command: snapper snapshot →
`yay -Syu` → run any NEW migrations. Applied migrations are tracked in
`~/.local/state/nosignal/applied` — do not re-run or edit by hand.
## Keybindings
- `Super+K` (aliases `Super+/`, `Super+F1`) = searchable fuzzel cheatsheet.
- **Source of truth: `~/.local/share/nosignal/NoSignal-keybindings.md`** — the
cheatsheet is GENERATED from it (`nosignal-keybinds-gen`). If you add a bind
in hypr-user.conf, document it in that file too.
- Layout follows **Omarchy defaults**: Super+Return terminal (kitty, bash),
Super+W close, Super+Space launcher, Super+Tab next workspace,
**Super+Grave = workspace overview**, Super+I Shelly store,
Super+Ctrl+H monitor editor, Super+Shift+number move-to-workspace
(bound by KEYCODE — GB layout).
- **Super+C / Super+V = universal copy/paste** (class-aware sendshortcut;
clipboard HISTORY is Super+Ctrl+V). **Print = region screenshot**
clipboard + ~/Pictures/Screenshots (Shift+Print full). **Super+D =
dashboard/calendar** (communication workspace → Super+Shift+D).
**Super+M = CLIAmp**, the default music player (floating TUI; audio files
open in it via `~/.config/mimeapps.list`). The shell's music/MPRIS panel
is **Super+Shift+M**.
## Shell / desktop architecture gotchas
- The Caelestia shell runs from `/etc/xdg/quickshell/caelestia`. NoSignal
patches THREE things there, each re-applied by its own pacman hook after
every caelestia-shell upgrade: the Settings → System → **Updates** page
(`PageCompRegistry.qml` + `pages/UpdatesPage.qml`), the Settings →
**Additions** page (same registry + `pages/AdditionsPage.qml` +
Plugins→Additions relabel in `PageRegistry.qml`; backend =
`nosignal-additions` + `additions.json` in the layer tree), and the Wi-Fi
wrong-password recovery in `utils/NetworkConnection.qml`.
**Do NOT create `~/.config/quickshell/caelestia`** — a user copy shadows
/etc/xdg and silently drops the patches (and future ones).
If the shell ever fails at login (background but no bar): reproduce with
`qs -c caelestia -n` from a TTY (`WAYLAND_DISPLAY=wayland-1`) to get the QML
error chain; suspect the page patches first.
- The workspace **overview is a separate Quickshell instance**
(`~/.config/quickshell/overview`, `qs -c overview -d`, IPC-toggled by
Super+Grave). It is independent of the main shell.
- The update-status timer (`nosignal-update-check.timer`, user),
`hyprmoncfgd` (monitor hotplug profiles, user) and
`hyprmoncfgd-rescan.path` (watches `~/.config/hyprmoncfg/profiles/` and
bounces the daemon so plain profile saves apply live) are enabled systemd
USER units. Status: `systemctl --user status <unit>`.
## Monitors
- Use **hyprmoncfg** (`Super+Ctrl+H`, TUI) — it writes
`~/.config/hypr/monitors.conf` (+ workspaces.conf), which hypr-user.conf
sources. Keep those `source =` lines; hyprmoncfg refuses to write otherwise.
- Both the TUI bind and the `hyprmoncfgd` daemon run with
`--hypr-config ~/.config/caelestia/hypr-user.conf --monitors-conf
~/.config/hypr/monitors.conf` (hyprmoncfg doesn't follow nested `source =`
includes). Keep the flags; the daemon's drop-in lives at
`~/.config/systemd/user/hyprmoncfgd.service.d/override.conf`.
## Login / display manager / gaming
- **SDDM** is the display manager. Config drop-ins in `/etc/sddm.conf.d/`:
`10-nosignal.conf` (X11 greeter, uwsm session notes) + `20-sddm-theme.conf`
(themed greeter). The desktop session MUST stay `hyprland-uwsm.desktop`
(uwsm-managed) — plain hyprland.desktop loses caelestia's session env.
- The greeter theme (`/usr/share/sddm/themes/caelestia`) mirrors the desktop
scheme. After changing wallpaper, run **`sudo sddm-theme-sync`** to update
the login screen (it does not auto-follow).
- **Gaming Mode is OPT-IN**: run `sh ~/deckshift/deckshift.sh`, then
`sh ~/.local/share/nosignal/deckshift-login/install-deckshift-login.sh`
(re-run the fix after any deckshift.sh re-run). Then Super+Shift+S enters
the full gamescope/Steam session; Super+Shift+R (inside it) returns. Cold
boots always show the password greeter (one-shot autologin). `[multilib]`
is enabled and `omarchy-pkg-add` & friends are shims in `~/.local/bin`.
## Packages / system
- `yay` for AUR; **Shelly** (`shelly-ui`, Super+I) is the GUI store
(repos + AUR + Flathub). Flathub remote is preconfigured. Settings →
**Additions** installs curated optional software (DeckShift, Spotify,
Once, Obsidian, OBS, Claude Code, Codex, opencode) from official sources
only — extend via `additions.json` in the layer tree, no code changes.
- The firewall is **ufw**, enabled with Omarchy-style defaults — it is easy
to miss because nothing advertises it: `sudo ufw status`.
- The `fish` package is REQUIRED by caelestia-meta — do not remove it, even
though bash is the login/terminal shell.
- Btrfs + snapper: every pacman transaction snapshots (snap-pac); snapshots
are BOOTABLE from the Limine menu (limine-snapper-sync, UKI at
/boot/EFI/Linux/nosignal_linux.efi). Worst case: reboot → pick a snapshot.
- `[omarchy]` repo in pacman.conf provides prebuilt limine-snapper tools.
- omarchy-send is installed (LAN file transfer; receive dir `~/Omarchy-Send`,
TUI in launcher, CLI: `omarchy-send -to <alias> <files>`). Its config
contains a private key — never copy/share `~/.config/omarchy-send/`.
## Theme
- Wallpapers: `~/Pictures/Wallpapers/nosignal/` (the OS art set; default
foam-sea.png). The colour scheme is Caelestia's **dynamic** Material scheme,
generated FROM the current wallpaper — change wallpaper via the Caelestia
UI or `caelestia wallpaper -f <file>` and the palette follows. The SDDM
greeter does NOT auto-follow: run `sudo sddm-theme-sync` after.

View file

@ -0,0 +1,34 @@
# additions-extra (Settings → Additions: 7 new items)
Merges seven items into `additions.json` (8 → 15 items).
Adds seven curated optional apps to Settings → Additions. All follow the
manifest policy (**official repos `[extra]`/`[omarchy]`, or official upstream
installers — no AUR, no Flatpak**) and are **GPU-agnostic** in wording and
behaviour (so they're correct on AMD boxes too).
| id | name | source | notes |
|----|------|--------|-------|
| `ollama` | Ollama | `[extra]` | install picks `ollama-cuda` (NVIDIA) / `ollama-rocm` (AMD) / `ollama` (CPU) by detecting the GPU vendor via `lspci` — stays agnostic |
| `lmstudio` | LM Studio | `[omarchy]` | `lmstudio-bin` (Provides/Conflicts `lmstudio`) |
| `dropbox` | Dropbox | `[omarchy]` | daemon + cli + nautilus-dropbox; enables the user service; links account on first start |
| `tailscale` | Tailscale | `[extra]` | installs + enables `tailscaled`; user runs `sudo tailscale up` |
| `pinta` | Pinta | `[omarchy]` | Paint.NET-style image editor |
| `kdenlive` | Kdenlive | `[extra]` | video editor; hardware-accelerated export via ffmpeg |
| `pi` | Pi Coding Agent | `curl pi.dev/install.sh` | AI coding-agent CLI, same family as claude-code/codex/opencode |
## Files
- `additions.json` — the **full merged manifest** (15 items) for the builder to
ship as the canonical Settings→Additions manifest.
- `merge-additions.sh` — idempotent merge of the 7 new items (by id) into an
already-installed system's manifest (skips ones already present). No root.
- `migrations/1781449200-additions-extra.sh` — delegates to it.
## Builder integration
Replace the layer's `additions-installer/additions.json` with the bundled
15-item file (or cherry-pick the 7 new entries). The merge script covers
already-installed machines on `nosignal-update`.
## Icon note
`kdenlive` uses `movie_edit` and `pi` uses `robot_2` — newer Material Symbols.
If the shipped icon font is older, fall back to `movie` and `smart_toy`.

View file

@ -0,0 +1,125 @@
{
"_comment": "NoSignal additions manifest — drives Settings → Additions. Each item: id (slug), name, desc (one line, shown until installed), icon (Material Symbol), check (sh, exit 0 = installed), install (sh, runs in a visible floating terminal — interactive sudo is fine). Sources policy: official pacman repos ([extra]/[omarchy]) or official upstream installers/git only — no AUR, no Flatpak.",
"items": [
{
"id": "deckshift",
"name": "DeckShift Gaming Mode",
"desc": "Steam Big Picture gamescope session (Super+Shift+S)",
"icon": "sports_esports",
"check": "test -x /usr/local/bin/switch-to-gaming",
"install": "set -e; [ -d \"$HOME/deckshift/.git\" ] || git clone https://git.no-signal.uk/nosignal/deckshift.git \"$HOME/deckshift\"; cd \"$HOME/deckshift\"; chmod +x deckshift.sh; ./deckshift.sh; sh \"$HOME/.local/share/nosignal/deckshift-login/install-deckshift-login.sh\""
},
{
"id": "spotify",
"name": "Spotify",
"desc": "Music streaming client",
"icon": "music_note",
"check": "pacman -Q spotify",
"install": "sudo pacman -S --needed --noconfirm spotify"
},
{
"id": "once",
"name": "Once",
"desc": "Basecamp self-hosted web app manager",
"icon": "deployed_code",
"check": "pacman -Q once-bin",
"install": "sudo pacman -S --needed --noconfirm once-bin"
},
{
"id": "obsidian",
"name": "Obsidian",
"desc": "Knowledge base and Markdown notes",
"icon": "edit_note",
"check": "pacman -Q obsidian",
"install": "sudo pacman -S --needed --noconfirm obsidian"
},
{
"id": "obs",
"name": "OBS Studio",
"desc": "Screen recording and streaming (+ hardware encoders)",
"icon": "videocam",
"check": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" check",
"install": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" install"
},
{
"id": "claude-code",
"name": "Claude Code",
"desc": "Anthropic terminal coding agent",
"icon": "smart_toy",
"check": "command -v claude",
"install": "curl -fsSL https://claude.ai/install.sh | bash"
},
{
"id": "codex",
"name": "Codex CLI",
"desc": "OpenAI terminal coding agent",
"icon": "code",
"check": "command -v codex",
"install": "curl -fsSL https://chatgpt.com/codex/install.sh | sh"
},
{
"id": "opencode",
"name": "opencode",
"desc": "Open-source terminal coding agent",
"icon": "data_object",
"check": "command -v opencode",
"install": "curl -fsSL https://opencode.ai/install | bash"
},
{
"id": "ollama",
"name": "Ollama",
"desc": "Run LLMs locally (GPU-accelerated)",
"icon": "neurology",
"check": "pacman -Q ollama",
"install": "gpu=$(lspci -nn 2>/dev/null | grep -iE 'VGA|3D|Display'); if echo \"$gpu\" | grep -qi '10de'; then pkg=ollama-cuda; elif echo \"$gpu\" | grep -qi '1002'; then pkg=ollama-rocm; else pkg=ollama; fi; echo \"Installing $pkg\"; sudo pacman -S --needed --noconfirm \"$pkg\""
},
{
"id": "lmstudio",
"name": "LM Studio",
"desc": "Desktop app to run LLMs locally (GPU-accelerated)",
"icon": "psychology",
"check": "pacman -Q lmstudio-bin || pacman -Q lmstudio",
"install": "sudo pacman -S --needed --noconfirm lmstudio-bin"
},
{
"id": "dropbox",
"name": "Dropbox",
"desc": "Cloud file sync and sharing",
"icon": "cloud_sync",
"check": "pacman -Q dropbox",
"install": "sudo pacman -S --needed --noconfirm dropbox dropbox-cli nautilus-dropbox; systemctl --user enable --now dropbox 2>/dev/null || true; echo 'Dropbox installed — it will open a browser to link your account on first start.'"
},
{
"id": "tailscale",
"name": "Tailscale",
"desc": "Mesh VPN to connect your devices",
"icon": "vpn_lock",
"check": "pacman -Q tailscale",
"install": "sudo pacman -S --needed --noconfirm tailscale; sudo systemctl enable --now tailscaled; echo 'Tailscale installed. Connect with: sudo tailscale up'"
},
{
"id": "pinta",
"name": "Pinta",
"desc": "Simple image editor (Paint.NET-style)",
"icon": "brush",
"check": "pacman -Q pinta",
"install": "sudo pacman -S --needed --noconfirm pinta"
},
{
"id": "kdenlive",
"name": "Kdenlive",
"desc": "Video editor (hardware-accelerated export)",
"icon": "movie_edit",
"check": "pacman -Q kdenlive",
"install": "sudo pacman -S --needed --noconfirm kdenlive"
},
{
"id": "pi",
"name": "Pi Coding Agent",
"desc": "Interactive AI coding agent (multi-provider)",
"icon": "robot_2",
"check": "command -v pi",
"install": "curl -fsSL https://pi.dev/install.sh | sh"
}
]
}

View file

@ -0,0 +1,25 @@
#!/bin/sh
# merge-additions.sh — idempotent. User-level (no root).
#
# Adds this component's additions.json items (by id) into the installed
# Settings -> Additions manifest, skipping ones already present. For the ISO the
# builder should instead ship this additions.json as the canonical manifest.
set -eu
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
LIVE="$HERE/../additions-installer/additions.json"
SRC="$HERE/additions.json"
[ -f "$LIVE" ] || { echo "live additions.json not found at $LIVE" >&2; exit 1; }
[ -f "$SRC" ] || { echo "component additions.json missing" >&2; exit 1; }
cp -a "$LIVE" "$LIVE.bak.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
python3 - "$LIVE" "$SRC" <<'PY'
import json,sys
live_p,src_p=sys.argv[1:3]
live=json.load(open(live_p)); src=json.load(open(src_p))
have={i["id"] for i in live["items"]}
added=[i for i in src["items"] if i["id"] not in have]
live["items"].extend(added)
json.dump(live,open(live_p,"w"),indent=2,ensure_ascii=False); open(live_p,"a").write("\n")
print("merged:", ", ".join(i["id"] for i in added) if added else "(none new)")
print("total items:", len(live["items"]))
PY
echo "additions-extra: ok"

View file

@ -0,0 +1,120 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Io
import Caelestia.Config
import qs.components
import qs.services
import qs.modules.nexus.common
// NoSignal: the Additions settings page (replaces the upstream Plugins
// placeholder). Optional software installed on demand from official sources
// (pacman repos / upstream installers no AUR, no Flatpak). Items come from
// the additions.json manifest via the status cache written by
// nosignal-additions; Install runs in a visible floating terminal so
// git/sudo/pacman output and prompts stay in front of the user.
PageBase {
id: root
title: qsTr("Additions")
property var status: ({})
readonly property var items: status.items || []
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: root.cappedWidth
spacing: Tokens.spacing.extraSmall / 2
// The Process objects live INSIDE the layout (its `data` accepts
// non-visual objects) PageBase's default property is a single
// `Item`, so declaring them at page level kills the whole shell.
// Same pattern as UpdatesPage / the upstream AboutPage.
// Read the cached status (instant).
Process {
id: readProc
running: true
command: ["sh", "-c", "cat \"${XDG_STATE_HOME:-$HOME/.local/state}/nosignal/additions-status.json\" 2>/dev/null"]
stdout: StdioCollector {
onStreamFinished: {
try {
root.status = JSON.parse(text);
} catch (e) {
root.status = {};
}
}
}
}
// Refresh the cache on demand (re-runs every item's check).
Process {
id: checkProc
command: ["sh", "-c", "\"$HOME/.local/bin/nosignal-additions\" status >/dev/null 2>&1"]
onExited: readProc.running = true
}
// Run an installer in a visible floating terminal.
Process {
id: installProc
property string addId: ""
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "\"$HOME/.local/bin/nosignal-additions\" install " + addId + "; printf '\\nPress Enter to close...'; read _"]
onExited: readProc.running = true
}
SectionHeader {
text: qsTr("Optional software")
}
Repeater {
model: root.items
NavRow {
required property var modelData
required property int index
first: index === 0
last: index === root.items.length - 1
icon: modelData.icon || "extension"
label: modelData.name
status: modelData.installed ? qsTr("Installed") : modelData.desc
onClicked: {
if (!modelData.installed && !installProc.running) {
installProc.addId = modelData.id;
installProc.running = true;
}
}
}
}
InfoRow {
visible: root.items.length === 0
first: true
last: true
label: qsTr("No additions manifest")
value: "—"
}
SectionHeader {
text: qsTr("Actions")
}
NavRow {
first: true
last: true
icon: "refresh"
label: qsTr("Re-check installed state")
status: installProc.running ? qsTr("Install running in terminal…") : (checkProc.running ? qsTr("Checking…") : qsTr("Refreshes the list above"))
onClicked: {
if (!checkProc.running)
checkProc.running = true;
}
}
}
}

View file

@ -0,0 +1,91 @@
# additions-installer — Settings → Additions (optional software on demand)
Turns the Settings app's placeholder **Plugins** section
into an **Additions** page: a curated list of optional software with
one-click installs.
## Sources policy
**No AUR. No Flatpak.** Official pacman repos or official upstream
installers/git only:
| Item | Source |
|------|--------|
| DeckShift Gaming Mode | official git — `https://git.no-signal.uk/nosignal/deckshift.git``deckshift.sh`**deckshift-login installer** (the one-shot-autologin fix, always run after deckshift.sh) |
| Spotify | `[omarchy]` pacman repo (proprietary — no upstream git exists) |
| Once (Basecamp) | `[omarchy]` pacman repo (`once-bin`) |
| Obsidian | Arch `[extra]` |
| OBS Studio | Arch `[extra]` (`obs-studio`) + **GPU-detected encoder runtimes** via `obs-extras.sh` (see below) |
| Claude Code | official installer — `curl -fsSL https://claude.ai/install.sh \| bash` |
| Codex CLI | official installer — `curl -fsSL https://chatgpt.com/codex/install.sh \| sh` |
| opencode | official installer — `curl -fsSL https://opencode.ai/install \| bash` |
## OBS hardware encoders (obs-extras.sh)
First-round testing found `obs-studio` alone ships only the x264
software encoder path. The ISO lands on arbitrary GPUs (AMD / Intel /
NVIDIA, any mix), so the OBS entry delegates check AND install to
**`obs-extras.sh`**, which detects GPUs via `lspci` per machine:
- **AMD** — VAAPI (H.264/HEVC/AV1) ships inside mesa: nothing to add.
- **Intel** — installs `intel-media-driver` + `vpl-gpu-rt` (QSV).
- **NVIDIA** — NVENC comes with the proprietary driver; deliberately
not auto-installed (driver branch is a system decision — see
gaming-enablement); prints a pointer if the driver is absent.
Because the *check* also requires the detected runtimes, an OBS
installed without them shows as not-installed — clicking Install
completes the set idempotently.
## How it works (same mechanism as the Updates page)
- **`additions.json`** — the manifest, single source of truth. Per item:
`id`, `name`, `desc`, `icon` (Material Symbol), `check` (sh, exit 0 =
installed), `install` (sh). Edit this file to add/remove items — no
code changes needed.
- **`nosignal-additions`** (`~/.local/bin`) — `status` re-runs every check
and writes `~/.local/state/nosignal/additions-status.json`;
`install <id>` runs the item's installer then re-statuses;` list`.
- **`AdditionsPage.qml`** — reads the status cache; each item is a row
(Installed ✓ or description + click-to-install). Installs run in a
visible floating terminal (`kitty --class TUI.float`) so git/sudo/
pacman prompts stay in front of the user. Modeled line-for-line on
UpdatesPage (Process objects inside the layout — see the comment).
- **`patch-additions-page.sh`** — root-run, idempotent: installs the
page QML, swaps the *remaining* System `PlaceholderComp` (the Plugins
stub) for the page, and relabels the menu entry
Plugins → Additions / "Install optional software".
**Ordering: requires the Updates-page patch first** — before it,
the first placeholder is the Updates stub (guarded: warns and skips).
Backups at `*.pre-nosignal-additions`; warns and degrades gracefully on
upstream drift.
- **pacman hook** (`nosignal-additions-page.hook`) re-applies after every
caelestia-shell upgrade.
## Packaging
- Ship the component in the layer tree + COPY-list; migration
`1781420400-additions-installer.sh` (sorts after the cliamp-music migration; add to the
baked `applied` list).
- The patch can be baked at image build (after the Updates-page patch).
- Do NOT pre-install any of the additions — the page is the opt-in.
## Version pins / drift
- Registry patch pinned against caelestia-shell **2.0.2** (same pin as
the Updates page; both patch scripts warn-and-degrade on drift).
- The three `curl | sh` installers and the DeckShift git URL are
upstream-controlled endpoints; if one moves, only `additions.json`
needs editing.
## Test
1. Settings → System shows **Additions** (not "Plugins"); page lists 8
items with sensible installed/not-installed state.
2. Install something small (e.g. Obsidian) → floating terminal, sudo
prompt, pacman runs → row flips to "Installed" after the terminal
closes.
3. "Re-check installed state" refreshes after installing/removing
something from a terminal.
4. DeckShift entry: full chain (clone → deckshift.sh → deckshift-login
fix), then the Gaming Mode checklist items apply (Super+Shift+S etc.).

View file

@ -0,0 +1,125 @@
{
"_comment": "NoSignal additions manifest — drives Settings → Additions. Each item: id (slug), name, desc (one line, shown until installed), icon (Material Symbol), check (sh, exit 0 = installed), install (sh, runs in a visible floating terminal — interactive sudo is fine). Sources policy: official pacman repos ([extra]/[omarchy]) or official upstream installers/git only — no AUR, no Flatpak.",
"items": [
{
"id": "deckshift",
"name": "DeckShift Gaming Mode",
"desc": "Steam Big Picture gamescope session (Super+Shift+S)",
"icon": "sports_esports",
"check": "test -x /usr/local/bin/switch-to-gaming",
"install": "set -e; [ -d \"$HOME/deckshift/.git\" ] || git clone https://git.no-signal.uk/nosignal/deckshift.git \"$HOME/deckshift\"; cd \"$HOME/deckshift\"; chmod +x deckshift.sh; ./deckshift.sh; sh \"$HOME/.local/share/nosignal/deckshift-login/install-deckshift-login.sh\""
},
{
"id": "spotify",
"name": "Spotify",
"desc": "Music streaming client",
"icon": "music_note",
"check": "pacman -Q spotify",
"install": "sudo pacman -S --needed --noconfirm spotify"
},
{
"id": "once",
"name": "Once",
"desc": "Basecamp self-hosted web app manager (installs + starts Docker)",
"icon": "deployed_code",
"check": "pacman -Q once-bin",
"install": "sudo pacman -S --needed --noconfirm once-bin docker; sudo systemctl enable --now docker.service; sudo usermod -aG docker \"$USER\"; echo 'Once + Docker installed and Docker is running. Log out and back in (or run: newgrp docker) so docker works without sudo.'"
},
{
"id": "obsidian",
"name": "Obsidian",
"desc": "Knowledge base and Markdown notes",
"icon": "edit_note",
"check": "pacman -Q obsidian",
"install": "sudo pacman -S --needed --noconfirm obsidian"
},
{
"id": "obs",
"name": "OBS Studio",
"desc": "Screen recording and streaming (+ hardware encoders)",
"icon": "videocam",
"check": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" check",
"install": "sh \"$HOME/.local/share/nosignal/additions-installer/obs-extras.sh\" install"
},
{
"id": "claude-code",
"name": "Claude Code",
"desc": "Anthropic terminal coding agent",
"icon": "smart_toy",
"check": "command -v claude",
"install": "curl -fsSL https://claude.ai/install.sh | bash"
},
{
"id": "codex",
"name": "Codex CLI",
"desc": "OpenAI terminal coding agent",
"icon": "code",
"check": "command -v codex",
"install": "curl -fsSL https://chatgpt.com/codex/install.sh | sh"
},
{
"id": "opencode",
"name": "opencode",
"desc": "Open-source terminal coding agent",
"icon": "data_object",
"check": "command -v opencode",
"install": "curl -fsSL https://opencode.ai/install | bash"
},
{
"id": "ollama",
"name": "Ollama",
"desc": "Run LLMs locally (GPU-accelerated)",
"icon": "neurology",
"check": "pacman -Q ollama",
"install": "gpu=$(lspci -nn 2>/dev/null | grep -iE 'VGA|3D|Display'); if echo \"$gpu\" | grep -qi '10de'; then pkg=ollama-cuda; elif echo \"$gpu\" | grep -qi '1002'; then pkg=ollama-rocm; else pkg=ollama; fi; echo \"Installing $pkg\"; sudo pacman -S --needed --noconfirm \"$pkg\""
},
{
"id": "lmstudio",
"name": "LM Studio",
"desc": "Desktop app to run LLMs locally (GPU-accelerated)",
"icon": "psychology",
"check": "pacman -Q lmstudio-bin || pacman -Q lmstudio",
"install": "sudo pacman -S --needed --noconfirm lmstudio-bin"
},
{
"id": "dropbox",
"name": "Dropbox",
"desc": "Cloud file sync and sharing",
"icon": "cloud_sync",
"check": "pacman -Q dropbox",
"install": "sudo pacman -S --needed --noconfirm dropbox dropbox-cli nautilus-dropbox; systemctl --user enable --now dropbox 2>/dev/null || true; echo 'Dropbox installed — it will open a browser to link your account on first start.'"
},
{
"id": "tailscale",
"name": "Tailscale",
"desc": "Mesh VPN to connect your devices",
"icon": "vpn_lock",
"check": "pacman -Q tailscale",
"install": "sudo pacman -S --needed --noconfirm tailscale; sudo systemctl enable --now tailscaled; echo 'Tailscale installed. Connect with: sudo tailscale up'"
},
{
"id": "pinta",
"name": "Pinta",
"desc": "Simple image editor (Paint.NET-style)",
"icon": "brush",
"check": "pacman -Q pinta",
"install": "sudo pacman -S --needed --noconfirm pinta"
},
{
"id": "kdenlive",
"name": "Kdenlive",
"desc": "Video editor (hardware-accelerated export)",
"icon": "movie_edit",
"check": "pacman -Q kdenlive",
"install": "sudo pacman -S --needed --noconfirm kdenlive"
},
{
"id": "pi",
"name": "Pi Coding Agent",
"desc": "Interactive AI coding agent (multi-provider)",
"icon": "robot_2",
"check": "command -v pi",
"install": "curl -fsSL https://pi.dev/install.sh | sh"
}
]
}

View file

@ -0,0 +1,51 @@
#!/bin/sh
# install-additions-installer.sh — Settings → Additions page: install
# optional software on demand from official sources (no AUR, no Flatpak).
#
# - nosignal-additions -> ~/.local/bin (status cache + installer runner)
# - additions.json -> stable on-system copy (the manifest)
# - AdditionsPage.qml -> patched into caelestia-shell (sudo)
# - pacman hook -> re-applies the patch after shell upgrades
#
# Safe to re-run (idempotent). Needs sudo for the QML patch + hook.
set -eu
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
BIN="$HOME/.local/bin"
SHARE="$HOME/.local/share/nosignal/additions-installer"
# 1. Backend + manifest.
mkdir -p "$BIN" "$SHARE"
install -m 0755 "$SRC/nosignal-additions" "$BIN/nosignal-additions"
install -m 0644 "$SRC/additions.json" "$SHARE/additions.json"
install -m 0755 "$SRC/obs-extras.sh" "$SHARE/obs-extras.sh"
# 2. Stable on-system copies for the pacman hook to point at.
install -m 0644 "$SRC/AdditionsPage.qml" "$SHARE/AdditionsPage.qml"
install -m 0755 "$SRC/patch-additions-page.sh" "$SHARE/patch-additions-page.sh"
# 3. Apply the QML patch now.
sudo sh "$SHARE/patch-additions-page.sh"
# 4. Pacman hook: caelestia-shell upgrades revert both registries — re-patch.
HOOK=/etc/pacman.d/hooks/nosignal-additions-page.hook
sudo mkdir -p /etc/pacman.d/hooks
sudo tee "$HOOK" > /dev/null <<EOF
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = nosignal-shell
Target = caelestia-shell
[Action]
Description = Re-applying NoSignal Additions settings page...
When = PostTransaction
Exec = /bin/sh $SHARE/patch-additions-page.sh
EOF
echo ":: pacman hook installed -> $HOOK"
# 5. Seed the status cache so the page has data on first open.
"$BIN/nosignal-additions" status >/dev/null 2>&1 || true
echo "Done. Restart the shell (Ctrl+Super+Alt+R) to see Settings -> Additions."

View file

@ -0,0 +1,58 @@
#!/bin/sh
# nosignal-additions — backend for Settings → Additions (optional software).
# The manifest (additions.json, next to this component's on-system copy) is
# the single source of truth: id/name/desc/icon per item, a `check` command
# (exit 0 = installed) and an `install` command (run in a visible terminal).
#
# nosignal-additions status rewrite the status cache (JSON)
# nosignal-additions list list manifest items (id<TAB>name)
# nosignal-additions install <id> run the item's installer, then re-status
#
# Sources policy: official pacman repos or official upstream installers/git
# only — no AUR, no Flatpak.
set -eu
SHARE="$HOME/.local/share/nosignal/additions-installer"
MANIFEST="${NOSIGNAL_ADDITIONS_MANIFEST:-$SHARE/additions.json}"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/nosignal"
STATE="$STATE_DIR/additions-status.json"
[ -f "$MANIFEST" ] || { echo "nosignal-additions: manifest not found: $MANIFEST" >&2; exit 1; }
status() {
mkdir -p "$STATE_DIR"
{
printf '{"checked":"%s","items":[' "$(date -Iseconds)"
sep=""
jq -c '.items[]' "$MANIFEST" | while IFS= read -r item; do
check=$(printf '%s' "$item" | jq -r '.check')
if sh -c "$check" >/dev/null 2>&1; then inst=true; else inst=false; fi
printf '%s' "$sep"
printf '%s' "$item" | jq -c --argjson inst "$inst" 'del(.check, .install) + {installed: $inst}'
sep=","
done
printf ']}\n'
} > "$STATE.tmp"
jq -e . "$STATE.tmp" >/dev/null # never replace the cache with broken JSON
mv "$STATE.tmp" "$STATE"
}
install_one() {
id="$1"
item=$(jq -ce --arg id "$id" '.items[] | select(.id == $id)' "$MANIFEST") \
|| { echo "nosignal-additions: unknown addition: $id" >&2; exit 1; }
name=$(printf '%s' "$item" | jq -r '.name')
cmd=$(printf '%s' "$item" | jq -r '.install')
printf '\033[1;34m::\033[0m installing %s\n' "$name"
sh -ec "$cmd"
printf '\033[1;34m::\033[0m %s — installer finished\n' "$name"
status
}
case "${1:-}" in
status) status ;;
list) jq -r '.items[] | "\(.id)\t\(.name)"' "$MANIFEST" ;;
install) [ -n "${2:-}" ] || { echo "usage: nosignal-additions install <id>" >&2; exit 2; }
install_one "$2" ;;
*) echo "usage: nosignal-additions status | list | install <id>" >&2; exit 2 ;;
esac

View file

@ -0,0 +1,58 @@
#!/bin/sh
# obs-extras.sh — OBS Studio with the right hardware encoders for THIS box.
# The Additions page ships on every install and GPUs differ (AMD / Intel /
# NVIDIA, any mix — e.g. AMD discrete + Intel iGPU), so the
# encoder runtime packages are DETECTED per machine, not hardcoded.
#
# obs-extras.sh check exit 0 only if OBS + everything this box's GPUs
# need is installed (drives the Additions row state)
# obs-extras.sh install install OBS + the detected encoder runtimes
#
# Per-vendor encoder support on Arch:
# AMD — VAAPI (H.264/HEVC/AV1) ships inside mesa (base system): no extra
# package since the libva-mesa-driver merge.
# Intel — intel-media-driver (VAAPI) + vpl-gpu-rt (QuickSync/QSV runtime).
# NVIDIA — NVENC comes with the proprietary driver (nvidia-utils or a
# legacy branch). Deliberately NOT auto-installed here: the driver
# branch is a system-level decision (see gaming-enablement's
# NVIDIA notes) — we print a pointer instead.
set -eu
gpus=$(lspci -nn 2>/dev/null | grep -Ei 'vga|3d|display' || true)
needed=""
case "$gpus" in *[Ii]ntel*) needed="intel-media-driver vpl-gpu-rt" ;; esac
check() {
pacman -Q obs-studio >/dev/null 2>&1 || return 1
for p in $needed; do
pacman -Q "$p" >/dev/null 2>&1 || return 1
done
return 0
}
do_install() {
# shellcheck disable=SC2086 — $needed is a deliberate word-split list
sudo pacman -S --needed --noconfirm obs-studio $needed
case "$gpus" in
*[Aa][Mm][Dd]*|*ATI*)
echo ":: AMD GPU: VAAPI encoders ship in mesa — already present." ;;
esac
case "$gpus" in
*[Nn][Vv][Ii][Dd][Ii][Aa]*)
if pacman -Q nvidia-utils >/dev/null 2>&1; then
echo ":: NVIDIA GPU: NVENC available via the installed driver."
else
echo "NOTE: NVIDIA GPU detected but no proprietary driver — NVENC needs"
echo " it (nvidia-utils, or a legacy branch for pre-Turing cards)."
fi ;;
esac
[ -n "$needed" ] && echo ":: Intel GPU: VAAPI + QuickSync runtimes installed."
echo ":: restart OBS to pick up new encoders."
}
case "${1:-}" in
check) check ;;
install) do_install ;;
*) echo "usage: obs-extras.sh check|install" >&2; exit 2 ;;
esac

View file

@ -0,0 +1,62 @@
#!/bin/sh
# patch-additions-page.sh — register the NoSignal Additions page in
# caelestia-shell. Runs as root (via sudo from the installer, or via the
# pacman hook after every caelestia-shell upgrade, which reverts both
# registry files). Idempotent.
#
# What it does:
# 1. copies AdditionsPage.qml (next to this script) into the shell's
# pages/ dir (untracked file — pacman upgrades leave it alone)
# 2. swaps the REMAINING "System" PlaceholderComp in PageCompRegistry.qml
# (the Plugins stub) for a StackPage hosting AdditionsPage.
# REQUIRES the Updates-page patch to be applied first — before it,
# the first placeholder is the Updates stub, and this patch would grab
# the wrong slot (guarded below).
# 3. relabels the menu entry in PageRegistry.qml: "Plugins" → "Additions".
#
# NEXUS is overridable for testing the regexes against copies.
set -eu
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
NEXUS=${NEXUS:-/etc/xdg/quickshell/caelestia/modules/nexus}
COMPREG="$NEXUS/PageCompRegistry.qml"
PAGEREG="$NEXUS/PageRegistry.qml"
[ -f "$COMPREG" ] || { echo "caelestia-shell not found at $NEXUS — nothing to patch"; exit 0; }
install -m 0644 "$SELF_DIR/AdditionsPage.qml" "$NEXUS/pages/AdditionsPage.qml"
# --- component registry: Plugins placeholder -> AdditionsPage ---------------
if grep -q 'AdditionsPage' "$COMPREG"; then
echo ":: component registry already patched"
elif ! grep -q 'UpdatesPage' "$COMPREG"; then
echo "WARNING: Updates patch not applied — the first placeholder" >&2
echo " is the Updates stub, not Plugins. Run the updates-panel patch" >&2
echo " first, then re-run $(basename "$0")." >&2
else
cp -n "$COMPREG" "$COMPREG.pre-nosignal-additions"
perl -0pi -e 's/Component \{\s*\n\s*PlaceholderComp \{\}\s*\n\s*\},/Component {\n \/\/ Additions (NoSignal)\n StackPage {\n Component {\n AdditionsPage {}\n }\n }\n },/' "$COMPREG"
if grep -q 'AdditionsPage' "$COMPREG"; then
echo ":: patched $COMPREG (Additions page registered)"
else
echo "WARNING: patch did not apply — upstream PageCompRegistry.qml changed" >&2
echo " shape. The Additions page is installed but not registered;" >&2
echo " update the regex in $(basename "$0")." >&2
fi
fi
# --- menu registry: relabel Plugins -> Additions -----------------------------
if [ -f "$PAGEREG" ]; then
if grep -q '"Additions"' "$PAGEREG"; then
echo ":: menu entry already relabeled"
else
cp -n "$PAGEREG" "$PAGEREG.pre-nosignal-additions"
perl -pi -e 's/label: qsTr\("Plugins"\),/label: qsTr("Additions"),/; s/description: qsTr\("Manage plugins"\),/description: qsTr("Install optional software"),/' "$PAGEREG"
if grep -q '"Additions"' "$PAGEREG"; then
echo ":: relabeled menu entry Plugins -> Additions"
else
echo "WARNING: relabel did not apply (upstream PageRegistry.qml changed)." >&2
echo " Cosmetic only — the page still works under the old label." >&2
fi
fi
fi

View file

@ -0,0 +1,67 @@
# app-theme-awareness
User-level, no root.
## Goal
Make external apps (Chrome/Chromium, Electron, Firefox, GTK, Qt) **follow
Caelestia's light/dark mode** automatically — dark when Caelestia is dark.
## Why they don't today
Modern apps read one signal: `org.freedesktop.appearance``color-scheme`,
published by **xdg-desktop-portal**. By default that value is **0
("no preference")** because nothing sets it — `gsettings
org.gnome.desktop.interface color-scheme` is `'default'`. So every app defaults
to light. All portal pieces are already installed (`xdg-desktop-portal`,
`-gtk`, `-hyprland`); `libadwaita` is present.
## How this works
- **`nosignal-app-theme-sync`**: reads `caelestia scheme get` ("Mode: dark|
light") and sets:
- `gsettings org.gnome.desktop.interface color-scheme``prefer-dark` /
`prefer-light`. xdg-desktop-portal-gtk republishes this as the freedesktop
appearance `color-scheme`, which **Chrome, Electron, Firefox, GTK4/libadwaita
and Qt6 honor** (UI + web `prefer-color-scheme`).
- `gtk-application-prefer-dark-theme` in `gtk-3.0`/`gtk-4.0` `settings.ini`
for legacy GTK3 apps that ignore the portal.
- **`nosignal-app-theme.path` + `.service`** (user units, mirroring NoSignal's
`hyprmoncfgd-rescan.path`): watch `~/.local/state/caelestia/scheme.json` and
re-run the sync on every scheme change, and run it once at login. So flipping
Caelestia light↔dark flips the apps live.
- **`portals.conf`**: pins the Settings portal backend to `gtk` (keeping
Hyprland's portal default for screencast), so the appearance value is served
reliably. Installed only if the user has no `portals.conf`.
`install-app-theme-awareness.sh` does all of it idempotently — entirely
user-level (no root, no pacman hook).
## Per-app notes
- **Chrome/Chromium (Wayland):** follows the portal automatically. If the UI
stays light, set `chrome://settings` → Appearance → **GTK / "Use system"**.
- **Qt:** `QT_QPA_PLATFORMTHEME=qtengine` is already set; Qt6 honors the portal.
Old Qt5 apps may need a dark qt5ct/Kvantum style (out of scope).
- **GTK3 dark fidelity:** prefer-dark uses Adwaita's built-in dark rendering. For
a fuller dark GTK3 theme the builder can add `gnome-themes-extra` (ships
`Adwaita-dark`) — optional.
## Builder integration
Ship the sync script in the layer bin, the two user units (enabled by default,
like the other NoSignal user units), and seed `portals.conf`. Ideally Caelestia
itself could set `color-scheme` when its scheme flips; until then these units
bridge it. Mode is read from `caelestia scheme get`, so it tracks the dynamic
Material scheme.
## Verify (after install)
```
caelestia scheme set -m light # or via the Caelestia UI
gsettings get org.gnome.desktop.interface color-scheme # -> 'prefer-light'
caelestia scheme set -m dark
gsettings get org.gnome.desktop.interface color-scheme # -> 'prefer-dark'
# Chrome/GTK apps follow within a moment.
```
## Files
- `nosignal-app-theme-sync` — mode → portal/GTK sync.
- `nosignal-app-theme.service` / `.path` — login run + watch-on-change.
- `portals.conf` — Settings backend = gtk.
- `install-app-theme-awareness.sh` — idempotent user-level installer.
- `migrations/1781442000-app-theme-awareness.sh` — delegates to it.

View file

@ -0,0 +1,39 @@
#!/bin/sh
# install-app-theme-awareness.sh — idempotent. User-level (no root).
#
# External apps (Chrome, Electron, Firefox, GTK, Qt) don't follow
# the desktop theme because nothing publishes a color-scheme preference — the
# portal reports "no preference" (0). This installs a sync that mirrors
# Caelestia's light/dark mode to the freedesktop appearance portal + GTK, and
# units that re-run it on every scheme change.
set -eu
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
BIN="$HOME/.local/bin"
UNITS="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
PORTAL="${XDG_CONFIG_HOME:-$HOME/.config}/xdg-desktop-portal"
install -Dm0755 "$HERE/nosignal-app-theme-sync" "$BIN/nosignal-app-theme-sync"
install -Dm0644 "$HERE/nosignal-app-theme.service" "$UNITS/nosignal-app-theme.service"
install -Dm0644 "$HERE/nosignal-app-theme.path" "$UNITS/nosignal-app-theme.path"
echo "installed sync script + user units"
# portals.conf: only create if the user doesn't already have one (don't clobber)
if [ ! -e "$PORTAL/portals.conf" ]; then
install -Dm0644 "$HERE/portals.conf" "$PORTAL/portals.conf"
echo "installed $PORTAL/portals.conf (Settings=gtk)"
else
echo "note: $PORTAL/portals.conf exists — leaving it; ensure it has org.freedesktop.impl.portal.Settings=gtk"
fi
# enable the watcher + run an initial sync (needs a running user systemd bus)
if systemctl --user show-environment >/dev/null 2>&1; then
systemctl --user daemon-reload
systemctl --user enable --now nosignal-app-theme.path nosignal-app-theme.service || true
echo "enabled nosignal-app-theme.{path,service}"
else
echo "note: no user systemd bus here — units installed; they start at next login"
"$BIN/nosignal-app-theme-sync" || true
fi
echo "app-theme-awareness: ok"

View file

@ -0,0 +1,39 @@
#!/bin/sh
# nosignal-app-theme-sync — make external apps follow Caelestia's light/dark mode.
#
# Reads Caelestia's current mode (`caelestia scheme get` -> "Mode: dark|light")
# and publishes it so toolkit apps follow:
# * gsettings color-scheme -> xdg-desktop-portal(-gtk) org.freedesktop.appearance
# => Chrome/Chromium, Electron, Firefox, GTK4/libadwaita, Qt6 all follow.
# * GTK3 prefer-dark flag in gtk-3.0/gtk-4.0 settings.ini for legacy GTK apps
# that don't read the portal.
# Idempotent; safe to run repeatedly. Invoked at login and on scheme change by
# the nosignal-app-theme .service/.path units.
set -u
mode=$(caelestia scheme get 2>/dev/null | awk -F': *' '/Mode:/{print tolower($2); exit}')
case "$mode" in
dark) cs=prefer-dark; pd=true ;;
light) cs=prefer-light; pd=false ;;
*) cs=default; pd=false ;; # unknown -> no preference
esac
# 1. master switch (portal): Chrome / Electron / Firefox / GTK4 / Qt6
if command -v gsettings >/dev/null 2>&1; then
gsettings set org.gnome.desktop.interface color-scheme "$cs" 2>/dev/null || true
fi
# 2. legacy GTK3 (+GTK4) prefer-dark flag for apps that ignore the portal
for v in 3.0 4.0; do
d="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-$v"; f="$d/settings.ini"
mkdir -p "$d"
[ -f "$f" ] || printf '[Settings]\n' > "$f"
grep -q '^\[Settings\]' "$f" || printf '[Settings]\n' >> "$f"
if grep -q '^gtk-application-prefer-dark-theme' "$f"; then
sed -i "s/^gtk-application-prefer-dark-theme.*/gtk-application-prefer-dark-theme=$pd/" "$f"
else
printf 'gtk-application-prefer-dark-theme=%s\n' "$pd" >> "$f"
fi
done
echo "app-theme: caelestia mode=${mode:-unknown} -> color-scheme=$cs, gtk prefer-dark=$pd"

View file

@ -0,0 +1,9 @@
[Unit]
Description=Watch Caelestia scheme and re-sync external app theme on change
[Path]
PathChanged=%h/.local/state/caelestia/scheme.json
Unit=nosignal-app-theme.service
[Install]
WantedBy=default.target

View file

@ -0,0 +1,9 @@
[Unit]
Description=Sync external app theme (portal/GTK) to Caelestia's light/dark mode
[Service]
Type=oneshot
ExecStart=%h/.local/bin/nosignal-app-theme-sync
[Install]
WantedBy=default.target

View file

@ -0,0 +1,8 @@
# NoSignal: keep Hyprland's portal as default (screencast/screenshot) but let
# xdg-desktop-portal-gtk serve the Settings interface, so the
# org.freedesktop.appearance color-scheme (driven by gsettings) is published
# reliably to apps. Installed to ~/.config/xdg-desktop-portal/portals.conf
# only if no user portals.conf already exists.
[preferred]
default=hyprland;gtk
org.freedesktop.impl.portal.Settings=gtk

View file

@ -0,0 +1,24 @@
# base-default-packages (installed by default, not opt-in)
The install command needs root — run it once.
Packages that should ship **installed by default** in the base build (distinct
from the opt-in Settings → Additions). Official repos only; GPU-agnostic.
| Package | Provides | Repo | Why |
|---------|----------|------|-----|
| `github-cli` | `gh` | extra | GitHub CLI — default dev tooling (PRs, issues, repo ops, auth) |
## Builder integration
Add the package(s) above to the ISO base package set. `install-base-default-
packages.sh` covers already-installed machines (idempotent `pacman -S --needed`)
and is wired via the migration below.
## Apply on an existing box
```
sudo pacman -S --needed github-cli
```
## Files
- `install-base-default-packages.sh` — idempotent installer (root).
- `migrations/1781452800-base-default-packages.sh` — delegates to it.

View file

@ -0,0 +1,20 @@
#!/bin/sh
# install-base-default-packages.sh — idempotent. Needs root (pacman).
#
# Ensures packages that should be installed BY DEFAULT in the base build (not
# opt-in Additions). For the ISO, the builder should add these to the base
# package set; this covers already-installed systems via nosignal-update.
set -eu
# Base default packages (official repos; GPU-agnostic):
PKGS="github-cli"
if [ "$(id -u)" -eq 0 ]; then
pacman -S --needed --noconfirm $PKGS
elif command -v sudo >/dev/null 2>&1; then
sudo pacman -S --needed --noconfirm $PKGS
else
echo "need root to install: $PKGS" >&2
exit 1
fi
echo "base default packages ensured: $PKGS"

View file

@ -0,0 +1,3 @@
# NoSignal: flip the CachyOS repos from the settings toggle without a password.
# Pinned to the fixed, root-owned helper, which itself only accepts detect/status/enable/disable.
%wheel ALL=(ALL) NOPASSWD: /usr/local/bin/nosignal-cachy-repo detect, /usr/local/bin/nosignal-cachy-repo status, /usr/local/bin/nosignal-cachy-repo enable, /usr/local/bin/nosignal-cachy-repo disable

View file

@ -0,0 +1,73 @@
// CachyRepoToggleRow.qml (NoSignal) Settings -> Services toggle to enable /
// disable the CachyOS pacman repositories. Auto-detects the best x86-64 microarch
// tier the CPU supports (v4 > v3). Live state polls `nosignal-cachy-repo status`
// (no root). enable/disable run in a floating terminal so the pacman/download
// output stays visible; the grant is passwordless via sudo -n
// (/etc/sudoers.d/02-nosignal-cachy). enable adds repos, converts userspace to
// the optimized builds (pacman -Suu, pacman pinned stock), and installs the
// linux-cachyos kernel; disable reverts to stock and removes the cachy kernel
// (keeping stock `linux` bootable). A reboot switches the running kernel.
//
// Untracked file under modules/nexus/common auto-discovered as the type
// `CachyRepoToggleRow` via `import qs.modules.nexus.common`. Survives caelestia
// upgrades; pairs with the one-line insert in ServicesPage.qml.
import QtQuick
import Quickshell.Io
import qs.modules.nexus.common
ToggleRow {
id: root
property bool repoOn: false // NB: not `enabled` (reserved Item property)
text: qsTr("CachyOS repositories")
subtext: repoOn
? qsTr("On — CachyOS optimized builds + linux-cachyos kernel (reboot to run it)")
: qsTr("Switch to CachyOS optimized builds + linux-cachyos kernel (reboot after)")
onToggled: {
if (checked)
enableProc.running = true;
else
disableProc.running = true;
reconcile.restart();
}
// --- live state -----------------------------------------------------------
Process {
id: statusProc
command: ["/usr/local/bin/nosignal-cachy-repo", "status"]
stdout: StdioCollector {
onStreamFinished: {
root.repoOn = text.trim() === "enabled";
root.checked = root.repoOn; // drive switch from real state
}
}
}
// --- actions (passwordless; shown in a terminal for pacman output) ---------
Process {
id: enableProc
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo enable; printf '\\nPress Enter to close...'; read _"]
onExited: reconcile.restart()
}
Process {
id: disableProc
command: ["kitty", "--class", "TUI.float", "-e", "sh", "-c", "sudo -n /usr/local/bin/nosignal-cachy-repo disable; printf '\\nPress Enter to close...'; read _"]
onExited: reconcile.restart()
}
// --- polling --------------------------------------------------------------
Timer {
interval: 5000
repeat: true
running: true
triggeredOnStart: true
onTriggered: statusProc.running = true
}
Timer {
id: reconcile
interval: 2000
onTriggered: statusProc.running = true
}
}

View file

@ -0,0 +1,79 @@
# cachyos-repo-switch — CachyOS optimized repos + kernel switch (sudoless, auto-tier)
Works **both directions** — ON → reboot into `linux-cachyos`; OFF → reboot back to stock.
## What it is
One toggle in **Settings → Services** (under "Passwordless sudo") that switches the
system **to/from** CachyOS-optimized builds **and** the `linux-cachyos` kernel —
passwordless, auto-detecting the best x86-64 tier (v4 > v3), fully unattended
(`--noconfirm`). Refuses below x86-64-v3.
## Pieces
- **`nosignal-cachy-repo`** → `/usr/local/bin``detect` / `status` / `enable` /
`disable` (+ `--dry-run`).
- **`02-nosignal-cachy`** → `/etc/sudoers.d` (0440, `visudo -c`-validated at install)
`%wheel` NOPASSWD, pinned to the four exact subcommands of the fixed helper.
- **`CachyRepoToggleRow.qml`** → `nexus/common/` — baked into the pinned
nosignal-shell fork; this copy + `patch-servicespage.sh` are the migration
path for old stock-caelestia installs.
- **`patch-servicespage.sh`** — inserts the toggle into `ServicesPage.qml` (only used
by the nosignal-update migration; the builder skips it via NOSIGNAL_SKIP_SHELL_PATCH).
## Two design fixes baked in — DO NOT reintroduce the bugs
1. **Architecture (critical).** CachyOS v3/v4 packages have arch `x86_64_v3`/`x86_64_v4`.
Upstream relies on its OWN pacman (reads `Architecture = auto` as "include v3/v4").
We keep **stock pacman** (reads `auto` as just `x86_64`) → it would arch-reject every
v3/v4 package and the conversion would silently do nothing. So `enable` sets
`Architecture` EXPLICITLY to the tier (`x86_64 x86_64_v3 x86_64_v4` for v4,
`x86_64 x86_64_v3` for v3); `disable` restores `Architecture = auto`.
2. **Targeted revert (no whole-base pull).** `disable` reinstalls ONLY packages whose
local-DB `Packager` is `CachyOS …` (excluding the cachy keyring/mirrorlist pkgs,
removed separately) — never the full base. Nothing converted ⇒ nothing to do.
## Why stock pacman (and `--ignore pacman`)
Upstream `cachyos-repo.sh` is interactive, ends with `pacman -Syu`, and swaps in a
CachyOS `pacman`. We reuse its signed keyring/mirrorlists + `.awk` stanza files but
drive pacman ourselves (stock, unattended). The CachyOS `pacman` build stamps an
`%INSTALLED_DB%` key into the local DB that stock pacman then warns about on every op,
so `enable` runs `pacman -Suu --ignore pacman` to pin pacman stock. **Keep that flag.**
## Kernel guard
`enable` installs `linux-cachyos` + `-headers` (the limine-mkinitcpio-hook generates
the boot entry; stock `linux` stays as a fallback). `disable` removes the cachy kernel
ONLY after confirming stock `linux` is installed — never strips the last bootable
kernel. A **reboot** is needed after ON (to run the new kernel) and after OFF (to leave
it); pick the entry at the Limine menu.
## Live-fetch vs vendored (build decision: LIVE-FETCH)
`enable`/`disable` fetch upstream `cachyos-repo.tar.xz` (keyring + mirrorlists + `.awk`).
Enabling CachyOS is inherently online (it pulls optimized builds + the kernel from
CachyOS mirrors), so there is no offline scenario to vendor for — live-fetch needs no
bundled artifacts and never goes stale. If a future air-gapped image needs it, vendor
the tarball and drop the fetch in `fetch_upstream()`.
## CachyOS-exclusive packages don't revert (by design)
Any package with no stock equivalent stays a CachyOS build on OFF and is reported, not
removed (e.g. `quickshell-git`, `gamescope-session-git`
harmless; self-clear on the next `yay -Sua`). The `linux-cachyos` kernel is the one
exception (removed explicitly, guarded as above). Snapshot entries keep historical
cachy UKIs under the Snapshots submenu (cosmetic; age out as snapshots rotate).
## Closure deps
`curl`, `tar`/`xz`, `gawk` (all via Arch `base`), `kitty`, plus `limine` +
`limine-mkinitcpio-hook` for the kernel boot entry — all already on the NoSignal base.
## Install (root)
```
sudo sh ~/.local/share/nosignal/cachyos-repo-switch/install-cachyos-repo-switch.sh
```
Then reload caelestia (or log out/in) so the toggle loads.
## Use
- Settings → Services → **CachyOS repositories**: on opens a floating terminal showing
the full pacman transaction (passwordless); off reverts to stock.
- Or terminal: `sudo nosignal-cachy-repo enable` / `disable`,
`nosignal-cachy-repo status` / `detect` (add `--dry-run` to preview).
## Files
- `nosignal-cachy-repo`, `02-nosignal-cachy`, `install-cachyos-repo-switch.sh`,
`CachyRepoToggleRow.qml`, `patch-servicespage.sh`.

View file

@ -0,0 +1,58 @@
#!/bin/sh
# install-cachyos-repo-switch.sh — idempotent. REQUIRES ROOT.
#
# Installs the CachyOS repo + kernel switch feature:
# 1. /usr/local/bin/nosignal-cachy-repo (detect/status/enable/disable CLI)
# 2. /etc/sudoers.d/02-nosignal-cachy (sudoless grant, validated first)
# 3. patches caelestia ServicesPage to show the toggle (via patch-servicespage.sh)
#
# Does NOT enable the CachyOS repos — that's a deliberate user action via the
# toggle (it is heavy, networked and reboots the kernel).
set -eu
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
[ "$(id -u)" -eq 0 ] || { echo "must run as root (writes /usr/local/bin + sudoers.d)" >&2; exit 1; }
# 1. CLI helper
install -m 0755 "$SELF_DIR/nosignal-cachy-repo" /usr/local/bin/nosignal-cachy-repo
echo ":: installed /usr/local/bin/nosignal-cachy-repo"
# 1b. standalone DB repair tool. A normal -Syu while
# CachyOS is enabled pulls the CachyOS pacman, which stamps `%INSTALLED_DB%`
# into the local DB; stock pacman then warns about it on every op. The
# corrected helper above pins pacman durably (IgnorePkg) while enabled and
# strips the field on disable, but ship the standalone repair tool too — and
# one-shot-clean any contamination already present (no-op on a fresh box).
install -m 0755 "$SELF_DIR/nosignal-cachy-db-clean" /usr/local/bin/nosignal-cachy-db-clean
echo ":: installed /usr/local/bin/nosignal-cachy-db-clean"
if grep -lrx '%INSTALLED_DB%' /var/lib/pacman/local/*/desc >/dev/null 2>&1; then
echo ":: local DB has %INSTALLED_DB% contamination — cleaning now…"
/usr/local/bin/nosignal-cachy-db-clean || true
fi
# 2. sudoers drop-in — validate with `visudo -c` BEFORE it reaches /etc/sudoers.d
# (a malformed sudoers can lock out sudo entirely). install to a temp path,
# check, then move into place at mode 0440.
TMP_SUDO=$(mktemp /tmp/02-nosignal-cachy.XXXXXX)
install -m 0440 "$SELF_DIR/02-nosignal-cachy" "$TMP_SUDO"
if visudo -cf "$TMP_SUDO" >/dev/null 2>&1; then
install -m 0440 -o root -g root "$TMP_SUDO" /etc/sudoers.d/02-nosignal-cachy
echo ":: installed /etc/sudoers.d/02-nosignal-cachy (validated)"
else
echo "ERROR: 02-nosignal-cachy failed visudo -c — NOT installing the sudoers drop-in." >&2
echo " (the toggle will prompt for a password instead of running passwordless)" >&2
fi
rm -f "$TMP_SUDO"
# 3. panel toggle (caelestia ServicesPage). Non-fatal if the shell isn't present.
# Skipped when NOSIGNAL_SKIP_SHELL_PATCH is set — the NoSignal builder bakes
# CachyRepoToggleRow + the page insert into the pinned nosignal-shell fork, so
# the patch is redundant there. Migrations of old stock-caelestia boxes
# leave it unset and patch.
if [ -n "${NOSIGNAL_SKIP_SHELL_PATCH:-}" ]; then
echo ":: skipping ServicesPage patch (NOSIGNAL_SKIP_SHELL_PATCH — fork bakes the toggle)"
elif [ -x "$SELF_DIR/patch-servicespage.sh" ]; then
sh "$SELF_DIR/patch-servicespage.sh" || echo "WARNING: ServicesPage patch failed (CLI still usable from a terminal)" >&2
fi
echo ":: done. Toggle it in Settings -> Services, or: sudo nosignal-cachy-repo enable"

View file

@ -0,0 +1,54 @@
#!/bin/bash
# nosignal-cachy-db-clean — repair tool for boxes already contaminated by a
# CachyOS pacman run.
#
# Symptom this fixes: every pacman/yay op prints a flood of
# warning: <pkg>: unknown key '%INSTALLED_DB%' in local database
# `%INSTALLED_DB%` is a field the CachyOS `pacman` build writes into the local DB;
# stock Arch pacman doesn't understand it and warns about it on EVERY operation.
# It is harmless (the DB is structurally valid — `pacman -Dk` passes — and
# transactions resolve fine), but it looks exactly like database corruption.
#
# This strips the `%INSTALLED_DB%` field from every /var/lib/pacman/local/*/desc.
# Each block is exactly: a `%INSTALLED_DB%` line + one value line + a blank
# separator. Idempotent — only rewrites files that contain the field; safe to
# re-run. Use --dry-run to preview. REQUIRES ROOT (writes the local DB) unless
# --dry-run.
set -u
DB="${PACMAN_LOCAL_DB:-/var/lib/pacman/local}"
DRY=0
[[ "${1:-}" == "--dry-run" ]] && DRY=1
if [[ $DRY -eq 0 && $EUID -ne 0 ]]; then
echo "Run as root (writes $DB), e.g.: sudo $0 — or preview with: $0 --dry-run" >&2
exit 1
fi
mapfile -t hits < <(grep -lrx '%INSTALLED_DB%' "$DB"/*/desc 2>/dev/null)
if [[ ${#hits[@]} -eq 0 ]]; then
echo "Local pacman DB already clean — no %INSTALLED_DB% fields found."
exit 0
fi
echo "Found %INSTALLED_DB% in ${#hits[@]} package entr(y/ies)."
n=0
for desc in "${hits[@]}"; do
if [[ $DRY -eq 1 ]]; then
echo "+ would strip: $desc"
n=$((n+1)); continue
fi
tmp="$desc.nsclean.$$"
if awk '/^%INSTALLED_DB%$/{s=1;next} s&&/^$/{s=0;next} s{next} {print}' "$desc" > "$tmp" \
&& chmod --reference="$desc" "$tmp" 2>/dev/null; then
mv -f "$tmp" "$desc"; n=$((n+1))
else
rm -f "$tmp"; echo " ! failed to rewrite: $desc" >&2
fi
done
if [[ $DRY -eq 1 ]]; then
echo "(dry-run) would have cleaned $n entr(y/ies). Re-run as root without --dry-run."
else
echo "Cleaned $n local-DB entr(y/ies). Verify with: pacman -Dk and a quiet pacman -Q"
fi

View file

@ -0,0 +1,306 @@
#!/bin/bash
# nosignal-cachy-repo — enable/disable the CachyOS pacman repositories, auto-
# selecting the best x86-64 microarchitecture tier the CPU supports (v4 > v3).
# Optimized CachyOS repos require x86-64-v3 or newer; below that this refuses.
#
# Driven by the sudoless GUI toggle:
# sudo -n /usr/local/bin/nosignal-cachy-repo {enable|disable|status|detect} [--dry-run]
#
# enable: trust keys + install mirrorlists + add repo stanzas, then run
# `pacman -Suu --noconfirm` to switch the system over to the CachyOS optimized
# builds — UNATTENDED, so a single toggle flip auto-converts (the GUI still shows
# the transaction in the floating terminal). --noconfirm auto-accepts any
# replacements/removals pacman proposes. We do NOT swap in the upstream CachyOS
# `pacman` package — it is pinned stock via --ignore pacman (keeps the local DB
# clean). enable also installs the CachyOS kernel (linux-cachyos).
# disable strips the repos, then reverts ONLY the CachyOS-built packages back to
# their stock Arch builds (-Suuy for version diffs + a targeted reinstall of the
# same-version rebuilds, identified by Packager "CachyOS …" in the local DB) and
# removes the keyring/key — a true round-trip back to stock, UNATTENDED. Touches
# only what was actually converted (nothing converted => instant, no base pull).
# A CachyOS-exclusive package with no stock equivalent is auto-skipped + REPORTED
# (not auto-removed). The CachyOS KERNEL is the exception: disable removes
# linux-cachyos, but only after confirming stock `linux` is installed so the box
# stays bootable.
set -u
PACCONF="/etc/pacman.conf"
PACBAK="/etc/pacman.conf.nosignal-cachy.bak"
CACHY_TARBALL="https://mirror.cachyos.org/cachyos-repo.tar.xz"
MIRROR="https://mirror.cachyos.org/repo/x86_64/cachyos"
CACHY_KEY="F3B607488DB35A47"
KEYSERVER="keyserver.ubuntu.com"
DRYRUN=0
ARGS=()
for a in "$@"; do
if [[ "$a" == "--dry-run" ]]; then DRYRUN=1; else ARGS+=("$a"); fi
done
set -- ${ARGS+"${ARGS[@]}"}
CMD="${1:-}"
run() { echo "+ $*"; [[ $DRYRUN -eq 1 ]] && return 0; "$@"; }
detect_tier() {
local h; h=$(/lib/ld-linux-x86-64.so.2 --help 2>/dev/null)
if grep -q "x86-64-v4 (supported" <<<"$h"; then echo v4
elif grep -q "x86-64-v3 (supported" <<<"$h"; then echo v3
else echo none; fi
}
is_enabled() { grep -qE '^\[cachyos' "$PACCONF" 2>/dev/null; }
# --- durable stock-pacman pin -------------------------------------------------
# `--ignore pacman` only pins pacman for THIS helper's own transaction. While the
# CachyOS repos are enabled, a routine `pacman -Syu` / `yay -Syu` / nosignal-update
# upgrades pacman to the CachyOS build, which stamps `%INSTALLED_DB%` into every
# local-DB entry it touches — stock pacman then warns about that unknown key on
# EVERY op (a flood that looks exactly like DB corruption). So while enabled we
# pin pacman in pacman.conf via IgnorePkg too; disable removes the pin. NoSignal
# ships no IgnorePkg by default, so we manage a single standalone line.
ensure_ignore_pacman() {
grep -qE '^[[:space:]]*IgnorePkg[[:space:]]*=.*\bpacman\b' "$PACCONF" && return 0
if grep -qE '^[[:space:]]*IgnorePkg[[:space:]]*=' "$PACCONF"; then
run sed -i -E 's/^([[:space:]]*IgnorePkg[[:space:]]*=.*)$/\1 pacman/' "$PACCONF"
else
run sed -i -E '/^\[options\]/a IgnorePkg = pacman' "$PACCONF"
fi
}
remove_ignore_pacman() {
# remove a standalone `IgnorePkg = pacman` line we added…
run sed -i -E '/^[[:space:]]*IgnorePkg[[:space:]]*=[[:space:]]*pacman[[:space:]]*$/d' "$PACCONF"
# …or drop just the `pacman` token from a shared IgnorePkg list
run sed -i -E 's/^([[:space:]]*IgnorePkg[[:space:]]*=.*[[:space:]])pacman([[:space:],]|$)/\1\2/' "$PACCONF"
}
# Strip the CachyOS `%INSTALLED_DB%` field from the pacman LOCAL DB (left behind
# whenever a CachyOS pacman ran while enabled — e.g. a normal -Syu). The field is
# informational only; stock pacman doesn't use it and just warns. Removing it
# silences the warning flood. Each block is exactly `%INSTALLED_DB%` + one value
# line + a blank separator. Idempotent: only rewrites desc files that contain it.
strip_installed_db() {
local db="/var/lib/pacman/local" n=0 desc tmp hits
hits=$(grep -lrx '%INSTALLED_DB%' "$db"/*/desc 2>/dev/null) || true
[[ -z "$hits" ]] && { echo "Local pacman DB already clean (no %INSTALLED_DB%)."; return 0; }
while IFS= read -r desc; do
[[ -z "$desc" ]] && continue
if [[ $DRYRUN -eq 1 ]]; then echo "+ strip %INSTALLED_DB% from $desc"; n=$((n+1)); continue; fi
tmp="$desc.nsclean.$$"
if awk '/^%INSTALLED_DB%$/{s=1;next} s&&/^$/{s=0;next} s{next} {print}' "$desc" > "$tmp" \
&& chmod --reference="$desc" "$tmp" 2>/dev/null; then
mv -f "$tmp" "$desc"; n=$((n+1))
else
rm -f "$tmp"
fi
done <<< "$hits"
echo "Stripped %INSTALLED_DB% from $n local-DB entr(y/ies); stock pacman is quiet again."
}
need_root() {
[[ $EUID -eq 0 ]] && return 0
echo "Error: must run as root (the toggle uses: sudo -n $0 $CMD)" >&2
exit 1
}
# Fetch + extract the upstream repo tarball (for the maintained .awk stanza
# files + current keyring/mirrorlist package versions). Echoes the dir.
fetch_upstream() {
local d; d=$(mktemp -d /tmp/nosignal-cachy.XXXXXX) || return 1
if [[ $DRYRUN -eq 1 ]]; then echo "$d"; return 0; fi
curl -fsSL "$CACHY_TARBALL" | tar -xJ -C "$d" || return 1
echo "$d"
}
case "$CMD" in
detect) detect_tier ;;
status) if is_enabled; then echo enabled; exit 0; else echo disabled; exit 1; fi ;;
enable)
need_root
if ! is_enabled; then
tier=$(detect_tier)
if [[ "$tier" == none ]]; then
echo "Error: CPU supports neither x86-64-v3 nor v4; CachyOS optimized repos" >&2
echo " unavailable. Nothing changed." >&2
exit 2
fi
echo "Detected microarchitecture tier: x86-64-$tier"
up=$(fetch_upstream) || { echo "Error: could not fetch CachyOS repo tarball (network?)." >&2; exit 4; }
trap 'rm -rf "$up"' EXIT
src="$up/cachyos-repo"
# 1) trust the CachyOS signing key
run pacman-key --recv-keys "$CACHY_KEY" --keyserver "$KEYSERVER"
run pacman-key --lsign-key "$CACHY_KEY"
# 2) install keyring + mirrorlists (versions tracked from upstream script;
# deliberately EXCLUDE the upstream 'pacman-*' replacement — keep stock pacman)
pkgs=()
if [[ $DRYRUN -eq 1 ]]; then
pkgs=(cachyos-keyring-XX cachyos-mirrorlist-XX cachyos-v3-mirrorlist-XX cachyos-v4-mirrorlist-XX)
else
mapfile -t pkgs < <(grep -oE '[a-z0-9.+_-]+\.pkg\.tar\.zst' "$src/cachyos-repo.sh" \
| grep -vE '^pacman-' | sort -u)
fi
urls=(); for p in "${pkgs[@]}"; do urls+=("$MIRROR/$p"); done
run pacman -U --noconfirm "${urls[@]}"
# 3) add the repo stanzas via upstream's maintained awk (tier-appropriate).
case "$tier" in
v4) awkf="install-v4-repo.awk" ;;
v3) awkf="install-repo.awk" ;;
esac
run cp -a "$PACCONF" "$PACBAK"
run gawk -i inplace -f "$src/$awkf" "$PACCONF"
# The upstream awk sets `Architecture = auto`, which STOCK pacman reads as
# just x86_64 — so it would REJECT every x86-64-v3/v4 package ("does not
# have a valid architecture") and the conversion would silently do nothing.
# (CachyOS's own pacman build reads `auto` as v3/v4; we keep stock pacman.)
# So set the tier arch EXPLICITLY.
case "$tier" in
v4) arch="x86_64 x86_64_v3 x86_64_v4" ;;
v3) arch="x86_64 x86_64_v3" ;;
esac
run sed -i "s/^Architecture = .*/Architecture = $arch/" "$PACCONF"
run pacman -Sy
if [[ $DRYRUN -eq 0 ]] && ! is_enabled; then
echo "Error: CachyOS repos not present in $PACCONF after setup." >&2
exit 3
fi
echo "CachyOS repos added (x86-64-$tier)."
else
echo "CachyOS repos already present — upgrading to optimized builds…"
fi
# Pull the optimized builds, UNATTENDED (--noconfirm) so a single toggle flip
# auto-converts the system without a prompt. The GUI still shows the full
# transaction in the floating terminal for visibility. NB: --noconfirm
# auto-accepts replacements/removals pacman proposes — that's the trade-off
# for hands-off conversion.
#
# --ignore pacman: the cachyos repo ships its OWN pacman build; without this
# the -Suu swaps stock pacman for it, which stamps a `%INSTALLED_DB%` key into
# every local-DB entry that stock pacman then warns about on every op. Pin
# pacman to the stock build so the DB stays clean (matches the keyring/mirror-
# list exclusion in step 2 — stock pacman is kept throughout).
#
# Make that pin DURABLE: --ignore only covers this transaction, but the repos
# stay enabled afterwards, so a later routine -Syu would pull the CachyOS pacman
# and contaminate the local DB with %INSTALLED_DB%. Pin pacman in pacman.conf
# for as long as CachyOS is enabled (disable removes the pin).
ensure_ignore_pacman
run pacman -Suu --noconfirm --ignore pacman
# Install the CachyOS KERNEL. The repo switch above converts USERSPACE only;
# the kernel is a separate, explicit install. linux-cachyos is CachyOS's
# default (sched-ext + BORE, LTO, v3/v4-tuned). The limine-mkinitcpio-hook
# auto-generates the boot entry — no manual limine.conf editing. --needed keeps
# a re-flip idempotent; stock `linux` is left in place as a fallback entry.
run pacman -S --noconfirm --needed linux-cachyos linux-cachyos-headers
echo "CachyOS optimized builds + linux-cachyos kernel installed."
echo ">> REBOOT and pick the linux-cachyos entry at the Limine menu to run it."
;;
disable)
need_root
if ! is_enabled; then echo "CachyOS repos already disabled."; exit 0; fi
up=$(fetch_upstream) || up=""
[[ -n "$up" ]] && trap 'rm -rf "$up"' EXIT
# 1) strip the CachyOS repo stanzas (pacman now sees only the stock repos)
run cp -a "$PACCONF" "$PACBAK"
if [[ -n "$up" && ( $DRYRUN -eq 1 || -f "$up/cachyos-repo/remove-repo.awk" ) ]]; then
run gawk -i inplace -f "$up/cachyos-repo/remove-repo.awk" "$PACCONF"
fi
if [[ $DRYRUN -eq 0 ]] && is_enabled; then # belt-and-braces if any remain
run sed -i '/^\[cachyos/,/^$/d' "$PACCONF"
fi
# restore the stock Architecture (enable set it to the v3/v4 tier explicitly)
run sed -i 's/^Architecture = .*/Architecture = auto/' "$PACCONF"
# drop the durable stock-pacman pin enable added (no longer needed off cachy)
remove_ignore_pacman
# 2) TARGETED REVERT — put ONLY the CachyOS-built packages back on stock.
# -Suuy refreshes the stock DBs and downgrades/upgrades any cachy package
# whose version differs from stock (its Packager flips back to Arch). Then
# force-reinstall the REMAINING same-version CachyOS rebuilds — found by
# their Packager ("CachyOS <admin@cachyos.org>") recorded in the local DB —
# so we touch ONLY what was actually converted, never the whole base
# (nothing converted => nothing to do). UNATTENDED (--noconfirm).
# A CachyOS-built package with no stock equivalent (e.g. a linux-cachyos
# kernel) is auto-skipped and REPORTED — not auto-removed (could be the
# running kernel); the user decides.
run pacman -Suuy --noconfirm
# Remove the CachyOS kernel — but ONLY once stock `linux` is confirmed present,
# so the box never loses its last bootable kernel. If stock linux is somehow
# missing, install it FIRST; if it still can't be installed, KEEP linux-cachyos.
# The limine-mkinitcpio-hook drops the boot entry automatically on removal.
# (linux-cachyos is CachyOS-exclusive, so the targeted revert below would
# otherwise just report it as an orphan and leave it installed + running.)
if [[ $DRYRUN -eq 1 ]]; then
echo "+ (if linux-cachyos present) ensure stock linux installed, then:"
echo "+ pacman -Rns --noconfirm linux-cachyos linux-cachyos-headers"
elif pacman -Qq linux-cachyos >/dev/null 2>&1; then
if ! pacman -Qq linux >/dev/null 2>&1; then
echo "Stock 'linux' missing — installing it before removing the CachyOS kernel…"
run pacman -S --noconfirm --needed linux linux-headers
fi
if pacman -Qq linux >/dev/null 2>&1; then
run pacman -Rns --noconfirm linux-cachyos linux-cachyos-headers
echo "Removed the CachyOS kernel; stock 'linux' remains as the boot entry."
echo ">> REBOOT into stock 'linux' at the Limine menu to stop running the CachyOS kernel."
else
echo "WARNING: could not ensure stock 'linux' is installed —"
echo " KEEPING linux-cachyos so the box stays bootable. Remove it manually"
echo " only after a stock kernel is installed."
fi
fi
if [[ $DRYRUN -eq 1 ]]; then
echo "+ pacman -S --noconfirm <CachyOS-built pkgs (by Packager) that exist in stock>"
echo "+ (CachyOS-exclusive pkgs are reported, not reinstalled or removed)"
else
# CachyOS-built packages (by Packager), EXCLUDING the cachy keyring/mirrorlist
# packages themselves — those are removed in step 3, not reinstalled.
mapfile -t cachy_built < <(LC_ALL=C pacman -Qi 2>/dev/null \
| awk -F': ' '/^Name +:/{n=$2} /^Packager +:/{if ($2 ~ /CachyOS/) print n}' \
| grep -vxE 'cachyos-(keyring|mirrorlist|v3-mirrorlist|v4-mirrorlist)')
if [[ ${#cachy_built[@]} -eq 0 ]]; then
echo "No CachyOS-built packages remain — nothing to reinstall."
else
mapfile -t avail < <(pacman -Slq)
declare -A in_stock=(); for p in "${avail[@]}"; do in_stock["$p"]=1; done
reinstall=(); orphan=()
for p in "${cachy_built[@]}"; do
if [[ -n "${in_stock[$p]:-}" ]]; then reinstall+=("$p"); else orphan+=("$p"); fi
done
[[ ${#reinstall[@]} -gt 0 ]] && run pacman -S --noconfirm "${reinstall[@]}"
if [[ ${#orphan[@]} -gt 0 ]]; then
echo
echo "NOTE: ${#orphan[@]} CachyOS-built package(s) have no stock equivalent"
echo " (CachyOS-exclusive) — left in place, review/remove manually:"
printf ' %s\n' "${orphan[@]}"
fi
fi
fi
# 3) remove the CachyOS keyring/mirrorlists + trust key (best-effort)
run sh -c "pacman -Rns --noconfirm cachyos-keyring cachyos-mirrorlist cachyos-v3-mirrorlist cachyos-v4-mirrorlist 2>/dev/null || true"
run sh -c "pacman-key --delete $CACHY_KEY 2>/dev/null || true"
# 4) clean up any %INSTALLED_DB% the CachyOS pacman stamped into the local DB
# while enabled (otherwise stock pacman warns about it on every op forever).
strip_installed_db
echo "CachyOS disabled; native packages reverted to stock Arch builds."
;;
*)
echo "Usage: $0 {detect|status|enable|disable} [--dry-run]" >&2
exit 1
;;
esac

View file

@ -0,0 +1,44 @@
#!/bin/sh
# patch-servicespage.sh — add the "CachyOS repositories" toggle to the caelestia
# Settings -> Services page. Runs as root (installer, or the pacman hook after a
# caelestia-shell upgrade reverts ServicesPage.qml). Idempotent.
#
# 1. install CachyRepoToggleRow.qml into nexus/common/ (untracked -> survives upgrades)
# 2. insert `CachyRepoToggleRow {}` after the "Smart colour scheme" ToggleRow in
# ServicesPage.qml (same anchor the sudo toggle uses; both land together)
#
# NoSignal bakes both the component and the page insert into the pinned
# nosignal-shell fork, so this patch is only for migration of older
# stock-caelestia installs (run by the nosignal-update migration).
set -eu
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
NEXUS=${NEXUS:-/etc/xdg/quickshell/caelestia/modules/nexus}
COMMON="$NEXUS/common"
PAGE="$NEXUS/pages/ServicesPage.qml"
[ -f "$PAGE" ] || { echo "caelestia ServicesPage not found at $PAGE — nothing to patch"; exit 0; }
# 1. component type (auto-discovered via `import qs.modules.nexus.common`)
install -m 0644 "$SELF_DIR/CachyRepoToggleRow.qml" "$COMMON/CachyRepoToggleRow.qml"
# 2. insert the row (idempotent)
if grep -q 'CachyRepoToggleRow' "$PAGE"; then
echo ":: ServicesPage already has the CachyOS toggle"
exit 0
fi
grep -q 'smartScheme = checked' "$PAGE" || {
echo "WARNING: anchor (Smart colour scheme ToggleRow) not found in ServicesPage — skipping insert" >&2
exit 0
}
cp -n "$PAGE" "$PAGE.pre-nosignal-cachy" 2>/dev/null || true
perl -0pi -e 's/(onToggled: GlobalConfig\.services\.smartScheme = checked\n\s*\})/$1\n\n CachyRepoToggleRow {\n Layout.fillWidth: true\n }/' "$PAGE"
if grep -q 'CachyRepoToggleRow' "$PAGE"; then
echo ":: patched ServicesPage (CachyOS toggle added)"
else
echo "WARNING: ServicesPage insert did not take; restoring" >&2
[ -f "$PAGE.pre-nosignal-cachy" ] && cp "$PAGE.pre-nosignal-cachy" "$PAGE"
exit 1
fi

View file

@ -0,0 +1,60 @@
# caelestia-lock-faillock
## Symptom
User locked the screen; the Caelestia lock screen **would not accept the
correct password**. Only recovery was a hard **reboot**.
## Evidence (prior-boot journal, `journalctl --user -b -1`)
```
qs[…]: PAM _pam_init_handlers: no default config other
qs[…]: pam_unix(passwd:auth): authentication failure; … user=q (×3)
qs[…]: pam_faillock(passwd:auth): Consecutive login failures for user q account temporarily locked
```
(`qs` = Quickshell = the Caelestia shell hosting the lock.)
## Root cause (lock-out mechanism — high confidence)
- The lock authenticates via `PamContext { config: "passwd" }`
(`modules/lock/Pam.qml:14`) → the **`passwd` PAM service**.
- On this build `/etc/pam.d/passwd` was extended to `auth include system-auth`,
and **system-auth runs `pam_faillock`**.
- Failed unlocks are counted by faillock; past the threshold it **temporarily
locks the account**, after which *every* attempt — including the correct
password — is refused until the `/run/faillock` tally clears (reboot, or
`faillock --reset`). That is exactly the reboot-to-recover the user hit.
## The initial failure trigger
Two possible causes of the first failures (before faillock engaged):
1. **Keyboard layout** on the lock surface (this install is **GB**; if the lock
comes up US-layout, `@ " # £ \ | ~` mistype → auth fails).
2. **PAM stack init** (`no default config other`; module ordering under a
uid-1000 Quickshell process).
## Fix
1. **Stop the lock from locking the account.** Ship a dedicated, **faillock-free**
PAM service `/etc/pam.d/caelestia` (provided: `etc-pam.d-caelestia`;
installed by `install-caelestia-lock-pam.sh`, root).
2. **Repoint the lock at it.** `modules/lock/Pam.qml`: `config: "passwd"`
`config: "caelestia"`.
A desktop lock must fail **closed-but-recoverable** (re-prompt), never lock the
account such that the correct password is refused.
## Builder integration (important)
`modules/lock/Pam.qml` lives under `/etc/xdg/quickshell/caelestia`, which is
re-applied by a pacman hook after every `caelestia-shell` upgrade. The `config:`
repoint must therefore be added to NoSignal's **quickshell-patch + pacman-hook**
mechanism — the same one that re-applies the Updates page, Additions page, and
`NetworkConnection.qml` patches — as a 4th patched file. Do **not** hand-edit it
in place (a shell upgrade would revert it). Expected behaviour: lock → wrong
password ×5 (keeps re-prompting, account stays usable) → correct password
unlocks; the lock-surface layout is GB.
## Recovery for the user, no reboot
TTY (Ctrl+Alt+F2) login → `sudo faillock --user "$USER" --reset` → back to the
session (Ctrl+Alt+F1) and unlock. `Super+Alt+L` recovers a *hung* lock but does
**not** clear a faillock lock.
## Files
- `etc-pam.d-caelestia` — proposed faillock-free PAM service.
- `install-caelestia-lock-pam.sh` — idempotent installer (root).
- `migrations/1781431200-caelestia-lock-pam.sh` — delegates to it.

View file

@ -0,0 +1,20 @@
#%PAM-1.0
# Caelestia lock-screen authentication (NoSignal).
#
# Deliberately does NOT use pam_faillock: a desktop screen-lock must never lock
# the user out of their OWN running session. With faillock in the path (the old
# "passwd"->system-auth route), a few failed unlocks tripped a temporary account
# lock that then refused the *correct* password until /run/faillock cleared on
# reboot. See README.md.
#
# pam_unix verifies the password supplied via the PAM conversation (the lock UI)
# using the setuid unix_chkpwd helper, so it works for the uid-1000 Quickshell
# process. Add pam_systemd_home below if/when systemd-homed users are supported.
#
# F8a: deliberately NO `nullok` — a screen lock must never accept an empty
# password. (Only changes behaviour for empty-password accounts, which are
# correctly rejected; normal password auth is unaffected.)
auth required pam_unix.so
account required pam_unix.so
password required pam_unix.so
session required pam_unix.so

View file

@ -0,0 +1,19 @@
#!/bin/sh
# install-caelestia-lock-pam.sh — idempotent. REQUIRES ROOT.
#
# Thin wrapper kept for back-compat (older migrations / docs reference it).
# The real work — DELIVER the faillock-free PAM service into the directory the
# lock actually reads (shellDir/assets/pam.d/caelestia) AND repoint Pam.qml — is
# done by patch-lock-pam.sh, which is also what the caelestia-shell pacman hook
# runs after every upgrade. Delegating keeps a single source of truth and avoids
# the old F1 bug where this script wrote /etc/pam.d/caelestia (a path PamContext
# never reads), leaving the lock on the faillock'd assets/pam.d/passwd service.
set -eu
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
if [ "$(id -u)" -ne 0 ]; then
echo "must run as root" >&2
exit 1
fi
exec sh "$HERE/patch-lock-pam.sh"

View file

@ -0,0 +1,72 @@
#!/bin/sh
# patch-lock-pam.sh — make the Caelestia lock screen use a faillock-free PAM
# service. Two parts, both idempotent:
#
# 1. DELIVER the faillock-free `caelestia` service into the directory the lock
# actually reads — Quickshell.shellDir + "/assets/pam.d" — i.e.
# /etc/xdg/quickshell/caelestia/assets/pam.d/caelestia. (Placing it in
# /etc/pam.d/caelestia, which PamContext never reads, makes the lock
# fall through to assets/pam.d/passwd — still faillock'd — and reject even
# the correct password.)
# 2. REPOINT the lock's PamContext at it: config: "passwd" -> "caelestia".
#
# Runs as root (via the installer, or the pacman hook after every caelestia-shell
# upgrade, which restores the shell tree under /etc/xdg — so BOTH the asset file
# and the repoint must be re-applied here each time, or an upgrade drops them).
#
# Symptom (before this fix): the lock screen rejects the CORRECT password; only a
# reboot escapes. The stock lock authenticates via PamContext { config: "passwd",
# configDirectory: shellDir + "/assets/pam.d" } -> assets/pam.d/passwd, which
# calls pam_faillock; a few failed unlocks trip a temporary account lock that
# then refuses every attempt (right password included) until /run/faillock clears.
#
# TARGET (Pam.qml) is overridable for testing the change against a copy; the
# asset destination is derived from it (shellDir = TARGET/../../..).
set -eu
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
SRC_PAM="$HERE/etc-pam.d-caelestia" # the faillock-free service we ship
TARGET=${TARGET:-/etc/xdg/quickshell/caelestia/modules/lock/Pam.qml}
[ -f "$TARGET" ] || { echo "caelestia-shell lock not found at $TARGET — nothing to patch"; exit 0; }
# shellDir = the caelestia shell root = three levels up from modules/lock/Pam.qml.
SHELLDIR=$(CDPATH= cd -- "$(dirname -- "$TARGET")/../.." && pwd)
ASSETS_PAMD="$SHELLDIR/assets/pam.d"
# --- 1. deliver the faillock-free service where the lock reads it -------------
if [ -f "$SRC_PAM" ]; then
install -d -m 0755 "$ASSETS_PAMD"
install -m 0644 "$SRC_PAM" "$ASSETS_PAMD/caelestia"
echo ":: installed $ASSETS_PAMD/caelestia (faillock-free lock auth)"
else
echo "WARNING: source PAM service $SRC_PAM missing — cannot deliver the lock service." >&2
fi
# --- 2. repoint the lock's PamContext at it ----------------------------------
# Already pointed at our service?
if grep -q 'config:[[:space:]]*"caelestia"' "$TARGET"; then
echo ":: Pam.qml already points at the caelestia lock service"
exit 0
fi
# Only act if the stock "passwd" service is referenced.
if ! grep -q 'config:[[:space:]]*"passwd"' "$TARGET"; then
echo "WARNING: Pam.qml does not reference config: \"passwd\" — upstream shape changed." >&2
echo " Leaving it untouched; review the lock PamContext for the new" >&2
echo " service name and update the regex in $(basename "$0")." >&2
exit 0
fi
cp -n "$TARGET" "$TARGET.pre-nosignal" 2>/dev/null || true
# Repoint only the PAM service name; keep everything else as-is.
sed -i 's/config:\([[:space:]]*\)"passwd"/config:\1"caelestia"/g' "$TARGET"
if grep -q 'config:[[:space:]]*"caelestia"' "$TARGET"; then
echo ":: patched $TARGET (lock now uses the faillock-free caelestia PAM service)"
else
echo "WARNING: patch did not apply — Pam.qml left untouched." >&2
exit 0
fi

View file

@ -0,0 +1,44 @@
# cheatsheet-keymap-path
User-level, no root.
## Symptom / risk
The Super+K cheatsheet is generated by `nosignal-keybinds-gen` from the keymap
markdown. Its source-resolution order is:
```
$1 arg → $NOSIGNAL_KEYMAP_MD → $HOME/Downloads/NoSignal-keybindings.md
→ $HOME/.local/share/nosignal/NoSignal-keybindings.md
```
`~/Downloads` is checked **before** the canonical layer copy. Today that copy is
absent so it falls through and works — but any stale `NoSignal-keybindings.md`
left in `~/Downloads` (a common scratch location) would **silently shadow** the
real keymap, so the cheatsheet would show wrong/old keys. The `nosignal-keybinds`
header comment also still says the doc lives in `~/Downloads`.
## Fix
- `nosignal-keybinds-gen`: drop the `~/Downloads` candidate so the canonical
`~/.local/share/nosignal/NoSignal-keybindings.md` is the only implicit source.
The explicit `$1` arg and `$NOSIGNAL_KEYMAP_MD` override are kept for dev use.
- `nosignal-keybinds`: correct the header comment to point at the canonical path.
`fix-cheatsheet-keymap-path.sh` patches the on-system scripts idempotently
(awk for the candidate line + a sanity `sh -n` before saving; sed for the
comment), then refreshes the cached `keybinds.list`. No-op if already fixed.
## Builder integration
Make the canonical change in the layer's `nosignal-keybinds-gen` and
`nosignal-keybinds` sources directly; this component is the equivalent patch for
an already-installed system.
## Verify
```
grep -n 'NoSignal-keybindings.md' "$(readlink -f "$(command -v nosignal-keybinds-gen)")"
# only the ~/.local/share/nosignal path should remain (no ~/Downloads)
nosignal-keybinds-gen | head -3 # still generates the keymap
```
## Files
- `fix-cheatsheet-keymap-path.sh` — idempotent patcher.
- `migrations/1781445600-cheatsheet-keymap-path.sh` — delegates to it.

View file

@ -0,0 +1,51 @@
#!/bin/sh
# fix-cheatsheet-keymap-path.sh — idempotent. User-level (no root).
#
# The Super+K cheatsheet generator resolves its keymap doc from
# $HOME/Downloads/NoSignal-keybindings.md *before* the canonical
# $HOME/.local/share/nosignal/NoSignal-keybindings.md. A stale copy left in
# ~/Downloads would silently shadow the real keymap. The nosignal-keybinds
# header comment also still claims the doc lives in ~/Downloads.
#
# Fix: drop the ~/Downloads candidate from nosignal-keybinds-gen (the $1 arg and
# $NOSIGNAL_KEYMAP_MD env override remain for dev use), and correct the comment.
# Edits the real files behind the ~/.local/bin symlinks. Re-runnable.
set -eu
resolve() { command -v "$1" >/dev/null 2>&1 && readlink -f -- "$(command -v "$1")"; }
GEN=$(resolve nosignal-keybinds-gen || true)
NK=$(resolve nosignal-keybinds || true)
# 1. nosignal-keybinds-gen: remove the ~/Downloads candidate line.
if [ -n "${GEN:-}" ] && grep -q 'Downloads/NoSignal-keybindings.md' "$GEN"; then
tmp=$(mktemp)
awk '
/for cand in "\$HOME\/Downloads\/NoSignal-keybindings\.md"/ { print " for cand in \\"; next }
{ print }
' "$GEN" > "$tmp"
# sanity: canonical path still present, Downloads gone, still parses
if grep -q '\.local/share/nosignal/NoSignal-keybindings.md' "$tmp" \
&& ! grep -q 'Downloads/NoSignal-keybindings.md' "$tmp" \
&& sh -n "$tmp" 2>/dev/null; then
cat "$tmp" > "$GEN"; echo "patched: $GEN (dropped ~/Downloads candidate)"
else
echo "WARN: gen rewrite failed sanity — left $GEN untouched" >&2
fi
rm -f "$tmp"
else
echo "nosignal-keybinds-gen already canonical (or not found) — skipped"
fi
# 2. nosignal-keybinds: fix the header comment path.
if [ -n "${NK:-}" ] && grep -q '~/Downloads/NoSignal-keybindings.md' "$NK"; then
sed -i 's#~/Downloads/NoSignal-keybindings.md#~/.local/share/nosignal/NoSignal-keybindings.md#g' "$NK"
echo "patched: $NK (comment path corrected)"
else
echo "nosignal-keybinds comment already correct (or not found) — skipped"
fi
# refresh the cached cheatsheet list so the change takes effect immediately
[ -n "${GEN:-}" ] && "$GEN" > "$HOME/.local/share/nosignal/keybinds.list.tmp" 2>/dev/null \
&& mv "$HOME/.local/share/nosignal/keybinds.list.tmp" "$HOME/.local/share/nosignal/keybinds.list" 2>/dev/null || true
echo "cheatsheet-keymap-path: ok"

View file

@ -0,0 +1,54 @@
# cheatsheet-tidy — Super+K panel shows key + action only, fitted
Reworks the cheatsheet output so the Super+K panel stays compact.
## Symptom
The Super+K panel was cluttered and overflowed: a `[Section]` tag column
ate ~20 chars of every line, actions carried doc-provenance notes like
"(Omarchy key)", and lines ran up to 200 chars in a
72-char fuzzel panel — long actions were simply cut off mid-word.
## Fix
Full replacements of the two keybinds-help scripts (kept small on purpose —
the markdown stays the single source of truth and is untouched):
- **`nosignal-keybinds-gen`** — output is now two columns, key + action:
- section tag column dropped (the markdown keeps its sections);
- doc-only parentheticals stripped from actions — anything matching
provenance patterns (`Omarchy`, `change N`, `(was …`, `moved`,
`NoSignal`, `dev/debug`); meaningful ones like
"(logout / shutdown / reboot)" and "(alias, old key)" stay;
- actions ellipsis-truncated to `$NOSIGNAL_KEYS_COLS` (default 98) so
every line fits the panel.
- **`nosignal-keybinds`** — fuzzel `--width` 72 → 100, in sync with the
generator budget (98 = 100 margin). Comment in each file points at
the other.
Result on the current keymap: 108 rows, all ≤ 98 chars, 6 lightly
ellipsis-truncated, none cut off by the panel.
The installer also refreshes the flat layer-root copies of both scripts
so a re-run of `install-keybinds-help.sh` cannot
regress the panel.
## Packaging
Replace the two scripts in the keybinds-help source with
these versions — this component then becomes part of keybinds-help rather
than a permanent overlay. No package/config delta, no keybind changes.
## Test
1. `Super+K` → every visible line is `KEY action`, no `[Section]`
tags, no "(Omarchy key)"-style noise, no line wider than the panel.
2. Type to filter (e.g. "workspace") — still searchable.
3. Select a line → still copies the shortcut to the clipboard.
4. `nosignal-keybinds-gen | awk '{print length($0)}' | sort -rn | head -1`
≤ 98.
## Notes
Cached list regenerated; max line length 98/98;
row count identical before/after (108).

View file

@ -0,0 +1,40 @@
#!/bin/sh
# install-cheatsheet-tidy.sh — tidy the Super+K cheatsheet panel: key +
# action only, every line fits the panel.
#
# Ships full replacements of the keybinds-help scripts:
# - nosignal-keybinds-gen -> ~/.local/bin/ (no tag column, strips doc-only
# parentheticals, truncates actions to the panel budget)
# - nosignal-keybinds -> ~/.local/bin/ (fuzzel width 72 -> 100, kept in
# sync with the generator budget)
#
# Also refreshes the layer-root source copies when present, so a
# re-run of install-keybinds-help.sh cannot regress the panel.
# Safe to re-run (idempotent). No sudo needed.
set -eu
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
BIN="$HOME/.local/bin"
ROOT=$(dirname -- "$SRC") # the layer root when run from the layer tree
mkdir -p "$BIN"
install -m 0755 "$SRC/nosignal-keybinds-gen" "$BIN/nosignal-keybinds-gen"
install -m 0755 "$SRC/nosignal-keybinds" "$BIN/nosignal-keybinds"
echo ":: installed tidied cheatsheet scripts -> $BIN"
# Keep the keybinds-help source copies in step (they live flat at the layer root).
# Check both the tree this component sits in and the installed layer location,
# so running from a handoff folder still fixes the on-system layer copies.
for d in "$ROOT" "$HOME/.local/share/nosignal"; do
for f in nosignal-keybinds-gen nosignal-keybinds; do
if [ -f "$d/$f" ] && ! cmp -s "$SRC/$f" "$d/$f"; then
install -m 0755 "$SRC/$f" "$d/$f"
echo ":: refreshed layer copy -> $d/$f"
fi
done
done
# Refresh the cached list so the next Super+K shows the new format at once.
"$BIN/nosignal-keybinds-gen" > "$HOME/.local/share/nosignal/keybinds.list" 2>/dev/null || true
echo "Done. Super+K now shows key + action only, fitted to the panel."

View file

@ -0,0 +1,46 @@
#!/bin/sh
# nosignal-keybinds — searchable on-screen keybinding cheatsheet for NoSignal.
# The keymap is NOT stored here: it is generated from the markdown doc by
# nosignal-keybinds-gen on every launch, so the cheatsheet can never drift from
# ~/Downloads/NoSignal-keybindings.md. The generated list is cached so the help
# still works if the markdown is temporarily missing.
#
# Read-only: typing filters the list; selecting a line copies that shortcut to
# the clipboard (if wl-copy is present) and never executes anything.
#
# --width 100 must stay in sync with the generator's $NOSIGNAL_KEYS_COLS default
# (98 = 100 minus margin) — the generator truncates actions to that budget so
# every line fits the panel.
set -u
DATA_DIR="$HOME/.local/share/nosignal"
LIST="$DATA_DIR/keybinds.list"
mkdir -p "$DATA_DIR"
# Find the generator next to this script (no PATH dependency), else fall back.
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
GEN="$SELF_DIR/nosignal-keybinds-gen"
[ -x "$GEN" ] || GEN="nosignal-keybinds-gen"
# Regenerate from the markdown when available; otherwise keep the cached list.
if "$GEN" > "$LIST.tmp" 2>/dev/null && [ -s "$LIST.tmp" ]; then
mv "$LIST.tmp" "$LIST"
else
rm -f "$LIST.tmp"
fi
if [ ! -s "$LIST" ]; then
command -v notify-send >/dev/null 2>&1 &&
notify-send -u critical "NoSignal keys" "Cheatsheet unavailable — keymap markdown not found."
exit 1
fi
selection=$(fuzzel --dmenu \
--prompt "keys> " \
--width 100 \
--lines 20 \
--no-icons < "$LIST" 2>/dev/null)
if [ -n "${selection:-}" ] && command -v wl-copy >/dev/null 2>&1; then
printf '%s' "$selection" | wl-copy
fi

View file

@ -0,0 +1,82 @@
#!/bin/sh
# nosignal-keybinds-gen — parse the NoSignal keymap markdown and emit display-ready
# cheatsheet lines on stdout. The markdown is the SINGLE SOURCE OF TRUTH; the
# on-screen help (nosignal-keybinds) is always derived from it, so the two cannot
# drift. Reads every "| keys | action |" table row except the "Source map"
# section.
#
# Panel format: two columns only — key, action — sized so every
# line fits the fuzzel panel:
# - no section tag column (visual noise in the popup; the markdown keeps it)
# - doc-only parentheticals are stripped from actions: provenance notes like
# "(Omarchy key)", "(was hover-only)" belong in the doc,
# not the panel. Meaningful ones ("(logout / shutdown / reboot)") stay.
# - actions are ellipsis-truncated so no line exceeds $NOSIGNAL_KEYS_COLS
# characters (default 98 — the nosignal-keybinds fuzzel width of 100 minus
# margin). Keep the two in sync.
#
# Usage: nosignal-keybinds-gen [path-to-markdown]
# Source resolution order: $1 arg → $NOSIGNAL_KEYMAP_MD → known locations.
set -eu
MD="${1:-${NOSIGNAL_KEYMAP_MD:-}}"
if [ -z "$MD" ]; then
for cand in "$HOME/Downloads/NoSignal-keybindings.md" \
"$HOME/.local/share/nosignal/NoSignal-keybindings.md"; do
[ -f "$cand" ] && { MD="$cand"; break; }
done
fi
[ -n "${MD:-}" ] && [ -f "$MD" ] || {
echo "nosignal-keybinds-gen: keymap markdown not found (set \$NOSIGNAL_KEYMAP_MD)" >&2
exit 1
}
awk -v cols="${NOSIGNAL_KEYS_COLS:-98}" '
function trim(s){ gsub(/\*\*/, "", s); gsub(/^[ \t]+|[ \t]+$/, "", s); return s }
# ---- section headings -------------------------------------------------------
/^## / {
insrc = (substr($0, 4) ~ /^Source map/) # not keybinds: skip the section
next
}
insrc { next }
# ---- table rows -------------------------------------------------------------
/^\|/ {
if ($0 ~ /^[|: -]+$/) next # markdown separator row
n = split($0, c, "|") # c[1] is empty (text before first |)
keys = c[2]; gsub(/`/, "", keys); keys = trim(keys)
if (keys == "" || keys == "Keys" || keys == "File") next
desc = ""
for (i = 3; i < n; i++) { # join any remaining columns
cell = c[i]; gsub(/`/, "", cell); cell = trim(cell)
if (cell != "") desc = (desc == "") ? cell : desc " — " cell
}
# strip doc-only parentheticals (provenance/changelog), keep the rest
out = ""; rest = desc
while (match(rest, /\([^()]*\)/)) {
pre = substr(rest, 1, RSTART - 1)
par = substr(rest, RSTART, RLENGTH)
rest = substr(rest, RSTART + RLENGTH)
if (par ~ /Omarchy|change [0-9]+|^\(was |moved |NoSignal|dev\/debug/) par = ""
out = out pre par
}
desc = out rest
gsub(/ +/, " ", desc) # collapse gaps left by stripping
gsub(/[ \t]+$/, "", desc)
sub(/ — $/, "", desc)
KEY[++r] = keys; DSC[r] = desc
if (length(keys) > wk) wk = length(keys)
}
END {
budget = cols - wk - 2 # action space after the key column
for (i = 1; i <= r; i++) {
d = DSC[i]
if (length(d) > budget) d = substr(d, 1, budget - 1) "…"
printf "%-*s %s\n", wk, KEY[i], d
}
}
' "$MD"

View file

@ -0,0 +1,57 @@
# cliamp-music — CLIAmp as the default music player
Adds a default music player — NoSignal previously had none.
## What
[CLIAmp](https://github.com/search?q=cliamp) — "a retro terminal music
player inspired by Winamp 2.x" — is what Omarchy ships for terminal
music (their `omarchy-launch-or-focus-tui cliamp` bind), and it is
prebuilt in the `[omarchy]` pacman repo NoSignal already carries
(v1.57.0 at time of writing; deps pull in ffmpeg and yt-dlp).
- **Package:** `cliamp` from `[omarchy]` (repo install, no AUR build).
- **Default player:** `cliamp.desktop` (launches in kitty, class
`cliamp`) + `xdg-mime default` for 12 common audio types, mirroring
the style of omarchy's `install/config/mimetypes.sh` (which sets no
audio defaults itself). Opening an audio file anywhere now starts
CLIAmp.
- **Keybind:** `Super+M` → launch-or-focus CLIAmp, floating 1100×700
(same shape as the other NoSignal TUI tools). Before this change both
`Super+M` and `Super+Shift+M` toggled the shell's music panel (an
MPRIS controller, not a player); the panel keeps `Super+Shift+M`.
Launch-or-focus is inlined (`hyprctl dispatch focuswindow … || kitty …`)
since NoSignal does not ship omarchy's helper — note `hyprctl` exits 0
even when no window matches, so the check greps for `ok`.
- **Keymap doc:** `Super+M` row updated (the Super+K cheatsheet
regenerates from it on next open).
## Files
| File | Role |
|------|------|
| `install-cliamp-music.sh` | idempotent installer — package (sudo), desktop entry, MIME defaults, marked bind block, keymap doc row |
| `cliamp.desktop` | desktop entry: `kitty --class cliamp -e cliamp %F` + audio MimeType list |
| `hyprland-cliamp.conf` | `# >>> cliamp music player >>>` marked block (float rules + Super+M), appended to hypr-user.conf |
## Packaging
- Add `cliamp` to the package list (from `[omarchy]`).
- Bake the desktop entry, the MIME defaults (skeleton
`~/.config/mimeapps.list`), the marked block (verbatim, markers
included — the installer greps for it), and the keymap doc row.
## Test
1. `Super+M` → CLIAmp opens floating; `Super+M` again focuses it
(no second instance). `Super+Shift+M` still toggles the music panel.
2. Open an `.mp3`/`.flac` from the file manager → opens in CLIAmp.
3. `xdg-mime query default audio/mpeg``cliamp.desktop`.
4. Super+K lists `Super+M Music player — CLIAmp…`.
## Notes
cliamp 1.57.0-1 installed; audio/mpeg and audio/flac
default to cliamp.desktop; runtime binds confirm Super+M →
launch-or-focus CLIAmp and Super+Shift+M → music panel; keymap doc row
present.

View file

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=CLIAmp
GenericName=Music Player
Comment=Retro terminal music player inspired by Winamp 2.x
Exec=kitty --class cliamp -e cliamp %F
Icon=audio-x-generic
Terminal=false
Categories=AudioVideo;Audio;Player;
MimeType=audio/mpeg;audio/mp4;audio/x-m4a;audio/aac;audio/flac;audio/x-flac;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/wav;audio/x-wav;audio/webm;

View file

@ -0,0 +1,9 @@
# >>> cliamp music player >>>
# CLIAmp (Winamp-style TUI, [omarchy] repo) is the default music player.
# Super+M launches/focuses it (was a second alias for the music panel toggle —
# the panel keeps Super+Shift+M). Floats like the other NoSignal TUI tools.
windowrule = float true, match:class cliamp
windowrule = size 1100 700, match:class cliamp
unbind = Super, M
bind = Super, M, exec, sh -c 'hyprctl dispatch focuswindow "class:^cliamp$" | grep -q ok || kitty --class cliamp -e cliamp'
# <<< cliamp music player <<<

View file

@ -0,0 +1,61 @@
#!/bin/sh
# install-cliamp-music.sh — CLIAmp as the default music player.
#
# - cliamp -> installed from the [omarchy] repo (sudo)
# - cliamp.desktop -> ~/.local/share/applications (launch in kitty)
# - XDG MIME defaults -> audio/* opens CLIAmp (omarchy mimetypes style)
# - Super+M -> launch-or-focus CLIAmp, floating
# (Super+Shift+M keeps the shell music panel)
# - keymap doc -> Super+M row updated
#
# Safe to re-run (idempotent). Needs sudo only for the package install.
set -eu
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
APPS="$HOME/.local/share/applications"
HYPRUSER="$HOME/.config/caelestia/hypr-user.conf"
KEYMAP="$HOME/.local/share/nosignal/NoSignal-keybindings.md"
# 1. Package (prebuilt in the [omarchy] repo — no AUR build).
if ! command -v cliamp >/dev/null 2>&1; then
sudo pacman -S --needed --noconfirm cliamp
fi
# 2. Desktop entry (ours wins over any packaged one — same desktop id).
mkdir -p "$APPS"
install -m 0644 "$SRC/cliamp.desktop" "$APPS/cliamp.desktop"
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$APPS" || true
# 3. Default player for audio files (mirrors omarchy's mimetypes.sh style).
if command -v xdg-mime >/dev/null 2>&1; then
for m in audio/mpeg audio/mp4 audio/x-m4a audio/aac audio/flac audio/x-flac \
audio/ogg audio/x-vorbis+ogg audio/x-opus+ogg audio/wav audio/x-wav \
audio/webm; do
xdg-mime default cliamp.desktop "$m"
done
echo ":: audio/* now opens CLIAmp"
fi
# 4. Super+M bind + float rule (marked block, end of hypr-user.conf).
if [ -f "$HYPRUSER" ]; then
if grep -q '>>> cliamp music player >>>' "$HYPRUSER"; then
echo ":: hypr-user.conf already has the cliamp block"
else
printf '\n' >> "$HYPRUSER"
cat "$SRC/hyprland-cliamp.conf" >> "$HYPRUSER"
echo ":: Super+M -> CLIAmp (block appended to hypr-user.conf)"
fi
command -v hyprctl >/dev/null 2>&1 && hyprctl reload >/dev/null 2>&1 || true
else
echo "NOTE: $HYPRUSER not found — append hyprland-cliamp.conf to your Hyprland user config."
fi
# 5. Keymap doc (single source of truth for the Super+K cheatsheet).
if [ -f "$KEYMAP" ] && ! grep -q 'CLIAmp' "$KEYMAP"; then
sed -i 's#^| `Super+M` | Music (alias) |#| `Super+M` | Music player — CLIAmp, floating (default for audio files) |#' "$KEYMAP"
grep -q 'CLIAmp' "$KEYMAP" \
&& echo ":: keymap doc updated (Super+M row)" \
|| echo "NOTE: keymap doc row not found — document Super+M manually in $KEYMAP"
fi
echo "Done. Super+M opens CLIAmp; audio files open in it by default."

View file

@ -0,0 +1,38 @@
# dashboard-key — Super+D opens the dashboard / calendar
caelestia's calendar lives in its **dashboard**, which by default opens only by
**hovering the top screen edge** (`Config.dashboard.showOnHover` +
`modules/drawers/Interactions.qml`). Easy to miss and fiddly. This adds a
keybind.
| Key | Action |
|-----|--------|
| `Super+D` | Toggle the dashboard (calendar + media/performance widgets) |
| `Super+Shift+D` | Communication special workspace (moved here from `Super+D`) |
`Super+D` was caelestia's `$kbCommunication` (communication scratchpad
workspace). To avoid losing it, it moves to `Super+Shift+D`. The dashboard is
toggled via the drawer IPC (`qs -c caelestia ipc call drawers toggle
dashboard`) — no shell edits, no package, works offline.
## Files
- binds appended to `~/.config/caelestia/hypr-user.conf` (idempotent marked block)
## Install
```sh
sh install-dashboard-key.sh
```
## Builder notes — fold into omarchy-keys
Add to `omarchy-keys/omarchy-keys-user.conf`:
```
unbind = Super, D
bind = Super, D, exec, qs -c caelestia ipc call drawers toggle dashboard
bind = Super+Shift, D, exec, caelestia toggle communication
```
And document `Super+D` (dashboard) + `Super+Shift+D` (communication) in
`NoSignal-keybindings.md`. The bar's `calendar_month` icon stays decorative
(making it clickable needs a shell-level change, deliberately avoided).

View file

@ -0,0 +1,37 @@
#!/bin/sh
# install-dashboard-key.sh — Super+D toggles the dashboard (calendar + widgets).
# Idempotent.
#
# The dashboard (which holds caelestia's calendar) was reachable only by
# hovering the top screen edge. This adds a keybind. Super+D was caelestia's
# `$kbCommunication` (communication special-workspace toggle), so that moves to
# Super+Shift+D — nothing is lost.
set -eu
HYPRUSER="$HOME/.config/caelestia/hypr-user.conf"
MARK='dashboard-key: Super+D dashboard'
if [ ! -f "$HYPRUSER" ]; then
echo "NOTE: $HYPRUSER not found — add the Super+D bind manually."
elif grep -qF "$MARK" "$HYPRUSER"; then
echo ":: Super+D already bound in $HYPRUSER"
else
cat >> "$HYPRUSER" <<'EOF'
# >>> dashboard-key: Super+D dashboard >>>
# Super+D toggles the dashboard (calendar + media/perf widgets), previously
# reachable only by hovering the top edge. Super+D was the communication
# special-workspace toggle ($kbCommunication) — moved to Super+Shift+D.
unbind = Super, D
bind = Super, D, exec, qs -c caelestia ipc call drawers toggle dashboard # Dashboard / calendar
bind = Super+Shift, D, exec, caelestia toggle communication # Communication workspace (moved from Super+D)
# <<< dashboard-key: Super+D dashboard <<<
EOF
echo ":: bound Super+D -> dashboard; communication -> Super+Shift+D"
fi
if command -v hyprctl >/dev/null 2>&1 && hyprctl version >/dev/null 2>&1; then
hyprctl reload >/dev/null 2>&1 && echo ":: reloaded Hyprland"
fi
echo "Done. Super+D opens the calendar/dashboard; Super+Shift+D = communication."

View file

@ -0,0 +1,140 @@
# deckshift-login — full-session DeckShift gaming, password at boot
Keeps DeckShift's gaming model exactly as designed — a **dedicated, full
gamescope session** (`gamescope-session-steam-nm`), switched by rewriting
SDDM's autologin session and restarting SDDM — but fixes the login behaviour:
| Event | Stock DeckShift | With this component |
|-------|-----------------|---------------------|
| Cold boot | autologin, no password ever | **normal SDDM password login** |
| Desktop → Gaming (`Super+Shift+S`) | autologin into gaming session | same (one-shot autologin) |
| Gaming → Desktop (`Super+Shift+R` / Steam > Exit to Desktop) | autologin back to desktop | same (one-shot autologin) |
| SDDM crash / manual restart | autologin | password login |
## How it works
Stock DeckShift keeps a **permanent** `[Autologin]` drop-in
(`/etc/sddm.conf.d/zz-gaming-session.conf`, `Relogin=true`) and its
`gaming-session-switch` helper just flips the `Session=` line — so the box
never shows a password screen again.
This component makes the autologin **one-shot**:
- `gaming-session-switch` (drop-in replacement, same path + interface, so
DeckShift's `switch-to-gaming` / `switch-to-desktop` / `os-session-select`
and the existing sudoers rule keep working) writes the autologin drop-in
fresh on each switch (`Relogin=false`) **and arms a marker in `/run`**.
- `sddm-autologin-gate` runs as `ExecStartPre=` on `sddm.service` (systemd
drop-in): marker present → consume it, keep the autologin for that single
restart; marker absent (cold boot, crash, manual restart — `/run` is tmpfs)
→ delete the autologin drop-in, password greeter shows.
Also adds the `Super+Shift+S → /usr/local/bin/switch-to-gaming` Hyprland bind:
DeckShift appends its bind to `~/.config/hypr/bindings.conf`, an **Omarchy**
path that doesn't exist on caelestia, so on this system DeckShift silently
skips the keybind — this component adds it to
`~/.config/caelestia/hypr-user.conf` instead (replacing caelestia's
`Super+Shift+S` screenshot-freeze; region shots stay on
Print / Shift+Print / Super+Shift+Alt+S). No exit bind is needed on the
desktop side: exiting happens inside the gaming session (DeckShift's evdev
keybind monitor handles `Super+Shift+R`, and Steam > Power > Exit to Desktop
calls `/usr/lib/os-session-select`).
## Files
```
gaming-session-switch -> /usr/local/bin/ (replaces DeckShift's; root 755)
switch-to-desktop -> /usr/local/bin/ (replaces DeckShift's; root 755)
sddm-autologin-gate -> /usr/local/bin/ (root 755)
deckshift-autologin-gate.conf-> /etc/systemd/system/sddm.service.d/
os-session-select -> /usr/lib/ (DeckShift's original, restored
if something overwrote it)
install-deckshift-login.sh idempotent installer (needs sudo)
```
**`deckshift.sh` itself is never modified** — this component only overlays
files that deckshift.sh has already installed (same paths, same interfaces,
the existing sudoers rules keep applying). That is the contract: the ISO
mimics Omarchy's SDDM setup, stock DeckShift installs onto it unchanged, and
this overlay is applied after it.
**Why `switch-to-desktop` is overlaid too**: DeckShift's original pkills gamescope *before* its final
`systemctl restart sddm`. Killing gamescope ends the login session, and
logind then kills the session's processes — including the script itself (it
runs inside the gaming session, launched by the keybind monitor) — so the
restart never executes. Stock DeckShift survives this because its permanent
`Relogin=true` autologin re-logs the user in anyway; with one-shot autologin
it stranded the user at the greeter. The overlay keeps all of DeckShift's
cleanup (power-profile restore, suspend unmask, Bluetooth, portal marker,
Steam shutdown) but queues the SDDM restart as a detached root job — the
same pattern DeckShift's own `os-session-select` uses, which is why Steam's
"Exit to Desktop" worked all along — and lets the service stop tear down
gamescope.
The installer also **removes the withdrawn nested gaming-mode experiment**
(`nosignal-gaming-mode*` scripts, its launcher entry, and its
`# >>> gaming-mode (nested) >>>` bind block) if the box ever had it.
## Install (opt-in, post-install — in this order)
```sh
# 1. DeckShift first (gaming session, switch scripts, sudoers, keybind monitor)
sh ~/deckshift/deckshift.sh
# 2. then the login fix
sh ~/.local/share/nosignal/deckshift-login/install-deckshift-login.sh
```
**Re-run step 2 after any re-run of `deckshift.sh`** — deckshift.sh restores
its permanent-autologin `gaming-session-switch` and recreates the standing
autologin drop-in.
## Test
1. Reboot → SDDM **password** prompt (session: "Hyprland (uwsm-managed)").
2. `Super+Shift+S` → screen flickers (VT switch + SDDM restart) → Steam Big
Picture in a full gamescope session. No password asked.
3. Steam > Power > Exit to Desktop (or `Super+Shift+R`) → back to the Hyprland
desktop. No password asked.
4. Reboot again → password prompt is back (the switch autologin didn't stick).
5. `sudo systemctl restart sddm` from the desktop → password prompt (gate
cleans up; nothing autologins outside a real switch).
## Known quirks
- SDDM remembers the **last session** per user, so after a reboot from inside
Gaming Mode the greeter may preselect "Gaming Mode (ChimeraOS)" — pick
"Hyprland (uwsm-managed)" from the session menu.
- The switch necessarily restarts the whole graphical session: open desktop
apps close when entering Gaming Mode. That is inherent to DeckShift's
full-session design (and why it works better for games than a nested
window: gamescope owns the display, HDR/VRR/input pass through cleanly).
## Builder notes — how to put this in the ISO (opt-in, NOT pre-enabled)
1. **Bake the Omarchy-style SDDM setup — REQUIRED.** DeckShift's
switching is built on SDDM; the ISO must boot to an SDDM password greeter
with `hyprland-uwsm` as the default session and **no autologin baked**.
2. Copy `deckshift-login/` into the layer (lands at
`~/.local/share/nosignal/deckshift-login/`) + the copy-list line in
`nosignal-update/install-nosignal-update.sh`:
```sh
[ -d "$SRC/deckshift-login" ] && cp -a "$SRC/deckshift-login" "$DEST/"
```
3. Ship migration `1781395200-deckshift-login.sh` as-is — it is
**conditional**: it only (re-)applies where DeckShift is actually installed
(`/usr/local/bin/switch-to-gaming` exists), so `nosignal-update` keeps
opted-in boxes patched and is a no-op everywhere else.
4. Do **NOT** run deckshift.sh or this installer at build time, do **NOT**
bake the `# >>> deckshift gaming keys >>>` block into the shipped
`hypr-user.conf`, and do **NOT** bake steam/gamescope/DeckShift packages.
Gaming stays opt-in: the user runs `deckshift.sh` then this installer.
5. Prerequisites already in the base: `[multilib]` + the `omarchy-*` shims
(gaming-enablement) — deckshift.sh depends on both.
6. Drop the withdrawn `gaming-mode/` (nested) component entirely if any copy
is still in the builder tree; this component supersedes it.
7. Document in `NoSignal-keybindings.md`: `Super+Shift+S` = Gaming Mode
**only after opting in** (until then it stays caelestia's
screenshot-freeze); `Super+Shift+R` works inside Gaming Mode only.
8. Verify on a clean VM: install → password login; run deckshift.sh + this
installer → switch in/out of Gaming Mode with no password; reboot →
password login is back.

View file

@ -0,0 +1,7 @@
# /etc/systemd/system/sddm.service.d/deckshift-autologin-gate.conf
# One-shot autologin gate for DeckShift session switching: keeps the SDDM
# autologin drop-in only when a desktop<->gaming switch armed it (marker in
# /run); every other SDDM start (cold boot included) shows the password
# greeter. See /usr/local/bin/sddm-autologin-gate.
[Service]
ExecStartPre=/usr/local/bin/sddm-autologin-gate

View file

@ -0,0 +1,49 @@
#!/bin/bash
# gaming-session-switch — DeckShift-compatible session-switch helper, patched
# for password-at-boot. Same path + same {gaming|desktop} interface as
# DeckShift's original (the existing sudoers rule and switch-to-gaming /
# switch-to-desktop / os-session-select callers all keep working).
#
# DeckShift's original keeps a PERMANENT SDDM autologin drop-in and only flips
# its Session= line, so the machine never shows a password screen. This version
# writes the drop-in fresh on every switch and arms a one-shot marker in /run;
# sddm-autologin-gate (ExecStartPre on sddm.service) consumes the marker so the
# autologin applies to THAT restart only. Cold boots, crashes, and manual SDDM
# restarts find no marker and get the normal password greeter.
set -u
CONF="/etc/sddm.conf.d/zz-gaming-session.conf"
MARKER="/run/deckshift-session-switch"
if [[ $EUID -ne 0 ]]; then
echo "Error: must run as root (callers use: sudo -n $0 ...)" >&2
exit 1
fi
# The desktop user comes from sudo (all DeckShift callers invoke via sudo -n).
USER_NAME="${SUDO_USER:-}"
if [[ -z "$USER_NAME" ]]; then
USER_NAME=$(loginctl list-sessions --no-legend 2>/dev/null | awk '$3 != "sddm" && $3 != "" { print $3; exit }')
fi
if [[ -z "$USER_NAME" ]]; then
echo "Error: cannot determine the user to autologin" >&2
exit 1
fi
case "${1:-}" in
gaming) SESSION="gamescope-session-steam-nm" ;;
desktop) SESSION="hyprland-uwsm" ;;
*)
echo "Usage: $0 {gaming|desktop}" >&2
exit 1
;;
esac
cat > "$CONF" << EOF
[Autologin]
User=${USER_NAME}
Session=${SESSION}
Relogin=false
EOF
touch "$MARKER"
echo "Session set to: $1 mode (one-shot autologin armed)"

View file

@ -0,0 +1,99 @@
#!/bin/sh
# install-deckshift-login.sh — full-session DeckShift gaming with a password
# at boot. Keeps DeckShift's design (dedicated gamescope session, switched via
# SDDM restart) but makes the autologin ONE-SHOT: it only fires for the
# desktop<->gaming switch itself; every cold boot shows the password greeter.
#
# OPT-IN, POST-INSTALL component: the ISO ships this folder in the layer but
# does NOT run it. Order on the installed system:
# 1. run deckshift.sh (installs the gaming session, switch scripts, sudoers)
# 2. run THIS script (patches the login behaviour + adds the Hyprland bind)
# Re-run this script after any re-run of deckshift.sh (deckshift.sh restores
# its permanent-autologin versions of these files).
#
# Needs sudo for: /usr/local/bin, /usr/lib, /etc/systemd, /etc/sddm.conf.d.
set -eu
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
HYPRUSER="$HOME/.config/caelestia/hypr-user.conf"
MARK='deckshift gaming keys'
# 0. DeckShift must already be installed.
if [ ! -x /usr/local/bin/switch-to-gaming ] || [ ! -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ]; then
echo "ERROR: DeckShift is not installed (run deckshift.sh first, then re-run this)."
exit 1
fi
# 1. Replace the session-switch helper with the one-shot-autologin version.
sudo install -m 0755 "$SRC/gaming-session-switch" /usr/local/bin/gaming-session-switch
echo ":: patched gaming-session-switch (one-shot autologin per switch)"
# 2. Install the SDDM start gate (script + sddm.service drop-in).
sudo install -m 0755 "$SRC/sddm-autologin-gate" /usr/local/bin/sddm-autologin-gate
sudo install -d -m 0755 /etc/systemd/system/sddm.service.d
sudo install -m 0644 "$SRC/deckshift-autologin-gate.conf" /etc/systemd/system/sddm.service.d/deckshift-autologin-gate.conf
sudo systemctl daemon-reload
echo ":: installed sddm autologin gate (password greeter on every cold boot)"
# 3. Drop the standing autologin drop-in; from now on it only exists
# transiently between "switch requested" and "SDDM restarted".
if [ -f /etc/sddm.conf.d/zz-gaming-session.conf ]; then
sudo rm -f /etc/sddm.conf.d/zz-gaming-session.conf
echo ":: removed standing SDDM autologin drop-in"
fi
# 4. Ensure /usr/lib/os-session-select is DeckShift's handler (restores it if
# the withdrawn nested-gaming experiment overwrote it).
if ! cmp -s "$SRC/os-session-select" /usr/lib/os-session-select 2>/dev/null; then
sudo install -m 0755 "$SRC/os-session-select" /usr/lib/os-session-select
echo ":: restored DeckShift os-session-select (Steam Exit to Desktop)"
fi
# 4b. Overlay switch-to-desktop: DeckShift's original pkills gamescope before
# its final `systemctl restart sddm`; the session teardown kills the
# script (it runs inside the gaming session) and the restart never fires —
# with one-shot autologin that strands the user at the greeter. Our
# version queues the restart as a detached root job instead (the proven
# os-session-select pattern), so Super+Shift+R returns to the desktop.
sudo install -m 0755 "$SRC/switch-to-desktop" /usr/local/bin/switch-to-desktop
echo ":: overlaid switch-to-desktop (queued SDDM restart, fixes Super+Shift+R exit)"
# 5. Remove the withdrawn nested-gaming-mode bits if this box ever had them.
rm -f "$HOME/.local/bin/nosignal-gaming-mode" "$HOME/.local/bin/nosignal-gaming-mode-exit" \
"$HOME/.local/share/applications/gaming-mode.desktop"
if [ -f "$HYPRUSER" ] && grep -qF 'gaming-mode (nested)' "$HYPRUSER"; then
sed -i '/# >>> gaming-mode (nested) >>>/,/# <<< gaming-mode (nested) <<</d' "$HYPRUSER"
echo ":: removed withdrawn nested gaming-mode binds"
fi
# 6. Hyprland bind: Super+Shift+S -> switch-to-gaming. The BASE config now ships a
# self-guarding Super+Shift+S bind (omarchy-keys-user.conf) that launches the
# gaming session only if DeckShift is installed and does nothing otherwise, so
# this installer no longer adds the bind itself. We only add a GUARDED bind as a
# fallback if no switch-to-gaming bind exists at all (e.g. an older base without
# it) — never the old unguarded form, which would break Super+Shift+S on a box
# where the gaming session is missing. Exit from Gaming Mode is handled INSIDE
# the session (DeckShift's Super+Shift+R monitor, or Steam > Power > Exit).
if [ ! -f "$HYPRUSER" ]; then
echo "NOTE: $HYPRUSER not found — base provides the guarded Super+Shift+S bind."
elif grep -qF 'switch-to-gaming' "$HYPRUSER"; then
echo ":: gaming keybind already present (base provides the guarded Super+Shift+S)"
else
cat >> "$HYPRUSER" << 'EOF'
# >>> deckshift gaming keys >>>
# Super+Shift+S = Gaming Mode IF DeckShift is installed; otherwise does nothing.
# Self-guards on the gaming session file + switch-to-gaming helper. Exit happens
# inside the session (Super+Shift+R, or Steam > Power > Exit to Desktop).
unbind = Super+Shift, S
bind = Super+Shift, S, exec, sh -c '[ -x /usr/local/bin/switch-to-gaming ] && [ -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ] && exec /usr/local/bin/switch-to-gaming'
# <<< deckshift gaming keys <<<
EOF
echo ":: added guarded Super+Shift+S -> switch-to-gaming (base bind was absent)"
fi
if command -v hyprctl >/dev/null 2>&1 && hyprctl version >/dev/null 2>&1; then
hyprctl reload >/dev/null 2>&1 || true
fi
echo "Done. Cold boot = password login. Super+Shift+S = Gaming Mode (full session);"
echo "Super+Shift+R or Steam > Exit to Desktop returns without a login screen."

View file

@ -0,0 +1,15 @@
#!/bin/bash
# /usr/lib/os-session-select — Steam's "Exit to Desktop" handler. This is
# DeckShift's original version, verbatim (shipped here so the installer can
# restore it if something else overwrote it). It flips the session back to
# desktop, shuts Steam down, and restarts SDDM; with the patched
# gaming-session-switch the autologin back to the desktop is one-shot.
rm -f /tmp/.gaming-session-active
sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || {
echo "Warning: Failed to update session config"
}
timeout 5 steam -shutdown 2>/dev/null || true
sleep 1
nohup sudo -n systemctl restart sddm &>/dev/null &
disown
exit 0

View file

@ -0,0 +1,20 @@
#!/bin/bash
# sddm-autologin-gate — ExecStartPre for sddm.service (one-shot autologin).
#
# A DeckShift session switch arms autologin right before restarting SDDM:
# gaming-session-switch writes the autologin drop-in AND touches the marker.
# This gate runs every time SDDM starts:
# - marker present -> a switch is in flight: consume the marker, keep the
# drop-in, SDDM autologins into the target session once;
# - marker absent -> cold boot / crash / manual restart: delete any stale
# drop-in so the normal password greeter shows.
# /run is tmpfs, so the marker can never survive a reboot.
CONF="/etc/sddm.conf.d/zz-gaming-session.conf"
MARKER="/run/deckshift-session-switch"
if [[ -f "$MARKER" ]]; then
rm -f "$MARKER"
else
rm -f "$CONF"
fi
exit 0

View file

@ -0,0 +1,64 @@
#!/bin/bash
# switch-to-desktop — deckshift-login overlay of DeckShift's original.
#
# WHY THIS OVERLAY EXISTS: the original pkilled gamescope and slept BEFORE its
# final `systemctl restart sddm`. Killing gamescope ends the login session, and
# logind then kills the session's processes — including this very script (it
# is launched by the in-session keybind monitor) — so the restart never ran.
# Stock DeckShift got away with that because its PERMANENT Relogin=true
# autologin re-logged the user in anyway; with deckshift-login's one-shot
# autologin it left the SDDM greeter instead.
#
# Fix, copied from the os-session-select path that already works: do the
# cleanup, arm the one-shot switch, shut Steam down, then queue the SDDM
# restart as a detached root job (nohup + disown). The restart job runs inside
# PID 1, so it completes even though stopping SDDM kills this script and the
# rest of the gaming session — no manual pkill of gamescope needed.
if [[ ! -f /tmp/.gaming-session-active ]]; then
exit 0
fi
rm -f /tmp/.gaming-session-active
# SYNCHRONOUS POWER RESTORE — done first because the trap-based restore in
# gamescope-session-nm-wrapper can be SIGKILL'd by `systemctl restart sddm`
# before it completes, leaving CPU governor / power profile stuck at
# "performance". (Verbatim from DeckShift's original.)
SAVED_STATE_FILE="$HOME/.cache/deckshift/saved-state"
if [[ -f "$SAVED_STATE_FILE" ]]; then
# shellcheck disable=SC1090
source "$SAVED_STATE_FILE"
for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo "${PRE_GAMING_CPU_GOVERNOR:-powersave}" > "$gov" 2>/dev/null
done
if command -v powerprofilesctl &>/dev/null && [[ -n "${PRE_GAMING_POWER_PROFILE:-}" ]]; then
sudo -n powerprofilesctl set "$PRE_GAMING_POWER_PROFILE" 2>/dev/null || \
powerprofilesctl set "$PRE_GAMING_POWER_PROFILE" 2>/dev/null
fi
rm -f "$SAVED_STATE_FILE"
fi
# Unmask suspend targets + daemon-reload (verbatim from DeckShift's original).
sudo -n systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null
sudo -n systemctl unmask --runtime sleep.target suspend.target hibernate.target hybrid-sleep.target 2>/dev/null
sudo -n systemctl daemon-reload 2>/dev/null
# Arm the one-shot autologin back to the desktop BEFORE anything can kill us.
sudo -n /usr/local/bin/gaming-session-switch desktop 2>/dev/null || true
# Re-enable Bluetooth (verbatim from DeckShift's original).
sudo -n /usr/bin/rfkill unblock bluetooth 2>/dev/null || true
sudo -n /usr/bin/systemctl start bluetooth.service 2>/dev/null || true
# Portal-recovery marker for the next Hyprland startup (from the original;
# moved earlier so it is written even if session teardown kills this script).
touch /tmp/.deckshift-just-returned 2>/dev/null || true
# Give Steam a clean shutdown, then hand over to the SDDM restart. Stopping
# sddm.service tears down the whole gaming session (gamescope included) — the
# same mechanism the working Steam "Exit to Desktop" path relies on.
timeout 5 steam -shutdown 2>/dev/null || true
sleep 1
sudo -n chvt 2 2>/dev/null || true
nohup sudo -n systemctl restart sddm &>/dev/null &
disown
exit 0

View file

@ -0,0 +1,63 @@
# display-manager-sddm — switch greetd → SDDM (for DeckShift gaming mode)
DeckShift's desktop↔gaming-mode switching is built on **SDDM**: `switch-to-gaming`
rewrites SDDM's config to point at the Gamescope/Steam session and restarts SDDM
to auto-log into it (and back). NoSignal ships **greetd + tuigreet**, which has no
equivalent, so this change replaces the display manager with SDDM.
## What it does
- Installs `sddm` (+ `xorg-server` for the reliable X11 greeter — the Hyprland
**session** stays Wayland; only SDDM's greeter uses X).
- Installs `/etc/sddm.conf.d/10-nosignal.conf` (X11 greeter, Numlock on, notes on
the preferred session).
- Disables `greetd.service`, enables `sddm.service`. **Takes effect on reboot.**
## Critical: preserve the uwsm session
greetd launched Hyprland via uwsm:
`uwsm start -e -D Hyprland hyprland.desktop`. The matching session entry is
**`/usr/share/wayland-sessions/hyprland-uwsm.desktop`** ("Hyprland
(uwsm-managed)"). Under SDDM, pick **that** session, not plain
`hyprland.desktop` (which runs `start-hyprland` directly and loses the
uwsm/systemd-managed session caelestia relies on). SDDM remembers the last
session per user.
## Install (live)
```sh
sh install-display-manager-sddm.sh # sudo; reboot afterwards
```
To revert: `sudo systemctl disable sddm; sudo systemctl enable greetd`.
## Builder notes — display-manager swap (package + service delta)
This is **not** a no-delta change. For the ISO:
- **Packages:** add `sddm` (+ `xorg-server` if using the X11 greeter, OR `weston`
if using the Wayland greeter — pick one). Remove `greetd`, `greetd-tuigreet`,
`greetd-agreety` from the image package list (or keep as fallback).
- **Services:** enable `sddm.service` instead of `greetd.service` in the image.
- **Greeter display server (decision):**
- *X11 greeter* (this component's default): most reliable, but adds
`xorg-server` to an otherwise Wayland-only image.
- *Wayland greeter*: keeps the image X-free — set `DisplayServer=wayland` +
`[Wayland] CompositorCommand=weston --shell=kiosk` and add `weston`.
- **Session default:** ensure `hyprland-uwsm.desktop` is the preferred desktop
session (uwsm launch — matches the old greetd `--cmd`). Don't let plain
`hyprland.desktop` win.
- **Login behaviour:** greetd here did **not** autologin (tuigreet prompts).
SDDM keeps a password prompt unless `[Autologin]` is set. DeckShift configures
its own autologin/session entries on top — let it.
- **DeckShift autologin (interaction to know about):** DeckShift writes
`/etc/sddm.conf.d/zz-gaming-session.conf` with `[Autologin] User=… /
Session=hyprland-uwsm / Relogin=true`. Once DeckShift runs, this **overrides
the password prompt** — the box boots straight into the desktop, and the
desktop↔gaming switch flips `Session=` then restarts SDDM (autologin, no
greeter). That's the intended Steam-Deck-style behaviour. If the ISO wants a
password on normal boots, ship a higher-priority SDDM drop-in or adjust
DeckShift's `gaming-session-switch` to only autologin while in gaming mode.
- Pairs with gaming-enablement (multilib + omarchy-pkg-add). Together
these two make NoSignal DeckShift/Steam-Deck capable.
- The base's plymouth/limine boot splash is unaffected.

View file

@ -0,0 +1,32 @@
#!/bin/sh
# install-display-manager-sddm.sh — switch the display manager from greetd to
# SDDM, so DeckShift's SDDM-based desktop<->gaming session switching works.
# Idempotent. Needs sudo. Takes effect on next reboot.
set -eu
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
# 1. Install SDDM (+ xorg-server for the reliable X11 greeter; the Hyprland
# session itself stays Wayland). For an X-free image use the Wayland greeter
# instead (see sddm-10-nosignal.conf) and drop xorg-server here.
echo ":: installing sddm + xorg-server (sudo)..."
sudo pacman -S --needed --noconfirm sddm xorg-server
# 2. Config drop-in (default desktop session = the uwsm-managed Hyprland).
sudo install -D -m 0644 "$SRC/sddm-10-nosignal.conf" /etc/sddm.conf.d/10-nosignal.conf
echo ":: installed /etc/sddm.conf.d/10-nosignal.conf"
# 3. Swap the active display-manager service: greetd -> sddm.
if systemctl is-enabled greetd.service >/dev/null 2>&1; then
sudo systemctl disable greetd.service
echo ":: disabled greetd.service"
fi
sudo systemctl enable sddm.service
echo ":: enabled sddm.service (display-manager.service -> sddm)"
cat <<'EOF'
Done. Reboot to log in via SDDM.
- At the SDDM session picker choose "Hyprland (uwsm-managed)" once; SDDM remembers it.
- DeckShift's switch-to-gaming / switch-to-desktop then drive SDDM to flip
between your desktop and the Gamescope/Steam session.
EOF

View file

@ -0,0 +1,17 @@
# NoSignal SDDM config (installed to /etc/sddm.conf.d/10-nosignal.conf).
[General]
# X11 greeter — the reliable, well-trodden path (needs xorg-server). The Hyprland
# SESSION still runs on Wayland; only SDDM's own greeter uses X here.
# For an X-free image instead, use the Wayland greeter:
# DisplayServer=wayland
# [Wayland]
# CompositorCommand=weston --shell=kiosk (adds `weston` to the image)
DisplayServer=x11
Numlock=on
[Users]
# The desktop session to prefer. hyprland-uwsm.desktop launches Hyprland through
# uwsm exactly like the old greetd config — keep this (NOT plain hyprland.desktop),
# or caelestia's uwsm/systemd-managed session env won't apply.
# (SDDM remembers the last-used session per user; DeckShift overrides this when
# switching to/from its Gamescope gaming session.)

View file

@ -0,0 +1,61 @@
# NoSignal — fish → bash migration (Omarchy bash setup)
Switches this Caelestia/NoSignal box from the fish shell to **bash, configured to
match [Omarchy](https://github.com/basecamp/omarchy)**. Self-contained so it
survives a wipe — copy this folder back with the rest of Downloads and run the
installer.
## Install
```sh
sh ~/Downloads/fish-to-bash/install-fish-to-bash.sh
```
Idempotent. Open a **new** terminal afterwards to land in bash.
## What it does
| Step | Effect |
|------|--------|
| Vendor `bash/` tree | Copied verbatim from Omarchy to `~/.local/share/omarchy/default/bash/` (`rc`, `shell`, `aliases`, `envs`, `init`, `functions`, `inputrc`, `completions`, `fns/*`) |
| `~/.bashrc` | Omarchy's template; sources the tree above. Existing `~/.bashrc` backed up to `~/.bashrc.pre-omarchy` |
| kitty | `~/.config/kitty/kitty.conf`: `shell fish``shell bash` |
| foot | `~/.config/foot/foot.ini`: `shell=fish``shell=bash` |
| Hyprland helpers | `wsaction.fish` & `configs.fish` replaced with bash ports (same filenames, bash shebang) so the stock keybinds keep working |
## Why the layout matches Omarchy exactly
The vendored files are byte-for-byte Omarchy and live at Omarchy's own path
(`~/.local/share/omarchy/...`). That means: (a) the prompt/aliases/history/
completion behaviour is identical to Omarchy, and (b) you can refresh them from
upstream by re-copying `default/bash/` with no edits. `envs` sets
`OMARCHY_PATH` and prepends `~/.local/bin` to `PATH` (so the keybind-help
scripts resolve too). References to omarchy-only tools (`omarchy`, `mise`,
`opencode`, `tdl`, `gum`, …) are all guarded with `command -v` or are function
definitions, so missing tools are harmless until you actually call them.
## NoSignal-specific additions (the only deviations from stock Omarchy)
- `~/.bashrc` exports `EDITOR=nvim` if unset (Omarchy sets this in `uwsm/env`,
which we don't manage here).
- `~/.bashrc` runs `cat ~/.local/state/caelestia/sequences.txt` so the terminal
picks up Caelestia's dynamic colour scheme.
## Notes / things that changed from the old fish config
- **`ga` / `gd` now mean Omarchy's git-worktree helpers**, not the old fish
abbreviations (`git add .` / `git diff`). Other git shortcuts: `g`, `gcm`,
`gcam`, `gcad`.
- **direnv** was hooked in the old fish config; Omarchy's bash doesn't include
it. Add `eval "$(direnv hook bash)"` to the bottom of `~/.bashrc` if you want
it back.
- The **fish package is left installed** (it may be pulled in as a Caelestia
dependency). Nothing interactive uses it anymore. Remove it only after
confirming nothing else needs it: `sudo pacman -Rns fish`.
- The fish config dir (`~/.config/fish`, a Caelestia symlink) is left in place
but unused.
## Requirements
`bash`, and ideally `starship`, `zoxide`, `eza`, `fzf`, `bat` for the full
Omarchy experience (all guarded — bash works without them).

View file

@ -0,0 +1,60 @@
# File system
if command -v eza &> /dev/null; then
alias ls='eza -lh --group-directories-first --icons=auto'
alias lsa='ls -a'
alias lt='eza --tree --level=2 --long --icons --git'
alias lta='lt -a'
fi
if [[ "$TERM" == "xterm-kitty" ]]; then
alias ff="fzf --preview 'case \$(file --mime-type -b {}) in image/*) kitty icat --clear --transfer-mode=memory --stdin=no --place=\${FZF_PREVIEW_COLUMNS}x\${FZF_PREVIEW_LINES}@0x0 {} ;; *) bat --style=numbers --color=always {} ;; esac'"
else
alias ff="fzf --preview 'bat --style=numbers --color=always {}'"
fi
alias eff='$EDITOR "$(ff)"'
sff() { if [ $# -eq 0 ]; then echo "Usage: sff <destination> (e.g. sff host:/tmp/)"; return 1; fi; local file; file=$(find . -type f -printf '%T@\t%p\n' | sort -rn | cut -f2- | ff) && [ -n "$file" ] && scp "$file" "$1"; }
if command -v zoxide &> /dev/null; then
alias cd="zd"
zd() {
if (( $# == 0 )); then
builtin cd ~ || return
elif [[ -d $1 ]]; then
builtin cd "$1" || return
else
if ! z "$@"; then
echo "Error: Directory not found"
return 1
fi
printf "\U000F17A9 "
pwd
fi
}
fi
open() (
xdg-open "$@" >/dev/null 2>&1 &
)
# Directories
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
# Tools
alias c='opencode'
alias cx='printf "\033[2J\033[3J\033[H" && claude --permission-mode bypassPermissions'
alias d='docker'
alias r='rails'
alias t='tmux attach || tmux new -s Work'
alias ic='tdl c'
alias ix='tdl cx'
alias icx='tdl c cx'
n() { if [ "$#" -eq 0 ]; then command nvim . ; else command nvim "$@"; fi; }
# Git
alias g='git'
alias gcm='git commit -m'
alias gcam='git commit -a -m'
alias gcad='git commit -a --amend'

View file

@ -0,0 +1,64 @@
_omarchy_complete() {
COMPREPLY=()
local cur="${COMP_WORDS[COMP_CWORD]}"
local omarchy_path bin_dir
omarchy_path=$(command -v omarchy 2>/dev/null) || return 0
bin_dir=$(dirname -- "$(readlink -f -- "$omarchy_path" 2>/dev/null || printf '%s' "$omarchy_path")")
[[ -d $bin_dir ]] || return 0
local prefix="omarchy"
local i part
for ((i = 1; i < COMP_CWORD; i++)); do
part="${COMP_WORDS[i]}"
[[ -z $part || $part == -* ]] && continue
prefix+="-$part"
done
local -A seen=()
local candidates=()
local file basename rest next
shopt -s nullglob
for file in "$bin_dir/$prefix"-*; do
[[ -f $file && -x $file ]] || continue
basename="${file##*/}"
rest="${basename#"$prefix"-}"
next="${rest%%-*}"
if [[ -n $next && -z ${seen[$next]:-} ]]; then
seen[$next]=1
candidates+=("$next")
fi
done
shopt -u nullglob
if (( COMP_CWORD == 1 )); then
candidates+=("commands")
fi
if [[ ${COMP_WORDS[1]:-} == "commands" ]] && (( COMP_CWORD >= 2 )); then
candidates+=("--all" "--json" "--markdown" "--check")
fi
if (( ${#candidates[@]} == 0 )) && [[ -x $bin_dir/$prefix ]]; then
local args enum
args=$(grep -m 1 '^# omarchy:args=<' "$bin_dir/$prefix" 2>/dev/null)
enum="${args#*<}"
enum="${enum%%>*}"
if [[ $enum == *"|"* && $enum != *" "* ]]; then
read -r -a candidates <<<"${enum//|/ }"
fi
fi
if (( ${#candidates[@]} > 0 )); then
local IFS=$'\n'
COMPREPLY=($(compgen -W "${candidates[*]}" -- "$cur"))
fi
}
complete -o default -F _omarchy_complete omarchy
# Hide individual omarchy-* binaries from initial-word command completion;
# the unified `omarchy` dispatcher is the user-facing entry point.
complete -I -A command -X 'omarchy-*'

View file

@ -0,0 +1,11 @@
# Editor used by CLI
export SUDO_EDITOR="$EDITOR"
export BAT_THEME=ansi
# Color man pages with bat
export MANROFFOPT="-c"
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
# Duplicated from .config/uwsm/env so SSH works too
export OMARCHY_PATH=$HOME/.local/share/omarchy
export PATH=$OMARCHY_PATH/bin:$PATH:$HOME/.local/bin

View file

@ -0,0 +1,3 @@
# Compression
compress() { tar -czf "${1%/}.tar.gz" "${1%/}"; }
alias decompress="tar -xzf"

View file

@ -0,0 +1,59 @@
# Write iso file to sd card
iso2sd() {
if (( $# < 1 )); then
echo "Usage: iso2sd <input_file> [output_device]"
echo "Example: iso2sd ~/Downloads/ubuntu-25.04-desktop-amd64.iso /dev/sda"
return 1
fi
local iso="$1"
local drive="$2"
if [[ -z $drive ]]; then
local available_sds=$(lsblk -dpno NAME | grep -E '/dev/sd')
if [[ -z $available_sds ]]; then
echo "No SD drives found and no drive specified"
return 1
fi
drive=$(omarchy-drive-select "$available_sds")
if [[ -z $drive ]]; then
echo "No drive selected"
return 1
fi
fi
sudo dd bs=4M status=progress oflag=sync if="$iso" of="$drive"
sudo eject "$drive"
}
# Format an entire drive for a single partition using exFAT
format-drive() {
if (( $# != 2 )); then
echo "Usage: format-drive <device> <name>"
echo "Example: format-drive /dev/sda 'My Stuff'"
echo -e "\nAvailable drives:"
lsblk -d -o NAME -n | awk '{print "/dev/"$1}'
else
echo "WARNING: This will completely erase all data on $1 and label it '$2'."
read -rp "Are you sure you want to continue? (y/N): " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
sudo wipefs -a "$1"
sudo dd if=/dev/zero of="$1" bs=1M count=100 status=progress
sudo parted -s "$1" mklabel gpt
sudo parted -s "$1" mkpart primary 1MiB 100%
sudo parted -s "$1" set 1 msftdata on
partition="$([[ $1 == *"nvme"* ]] && echo "${1}p1" || echo "${1}1")"
sudo partprobe "$1" || true
sudo udevadm settle || true
sudo mkfs.exfat -n "$2" "$partition"
echo "Drive $1 formatted as exFAT and labeled '$2'."
fi
fi
}

View file

@ -0,0 +1,19 @@
fip() {
(( $# < 2 )) && echo "Usage: fip <host> <port1> [port2] ..." && return 1
local host="$1"
shift
for port in "$@"; do
ssh -f -N -L "$port:localhost:$port" "$host" && echo "Forwarding localhost:$port -> $host:$port"
done
}
dip() {
(( $# == 0 )) && echo "Usage: dip <port1> [port2] ..." && return 1
for port in "$@"; do
pkill -f "ssh.*-L $port:localhost:$port" && echo "Stopped forwarding port $port" || echo "No forwarding on port $port"
done
}
lip() {
pgrep -af "ssh.*-L [0-9]+:localhost:[0-9]+" || echo "No active forwards"
}

View file

@ -0,0 +1,97 @@
# Create a Tmux Dev Layout with editor, ai, and terminal
# Usage: tdl <c|cx|codex|other_ai> [<second_ai>]
tdl() {
[[ -z $1 ]] && { echo "Usage: tdl <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tdl."; return 1; }
local current_dir="${PWD}"
local editor_pane ai_pane ai2_pane
local ai="$1"
local ai2="$2"
# Use TMUX_PANE for the pane we're running in (stable even if active window changes)
editor_pane="$TMUX_PANE"
# Name the current window after the base directory name
tmux rename-window -t "$editor_pane" "$(basename "$current_dir")"
# Split window vertically - top 85%, bottom 15% (target editor pane explicitly)
tmux split-window -v -p 15 -t "$editor_pane" -c "$current_dir"
# Split editor pane horizontally - AI on right 30% (capture new pane ID directly)
ai_pane=$(tmux split-window -h -p 30 -t "$editor_pane" -c "$current_dir" -P -F '#{pane_id}')
# If second AI provided, split the AI pane vertically
if [[ -n $ai2 ]]; then
ai2_pane=$(tmux split-window -v -t "$ai_pane" -c "$current_dir" -P -F '#{pane_id}')
tmux send-keys -t "$ai2_pane" "$ai2" C-m
fi
# Run ai in the right pane
tmux send-keys -t "$ai_pane" "$ai" C-m
# Run nvim in the left pane
tmux send-keys -t "$editor_pane" "$EDITOR ." C-m
# Select the nvim pane for focus
tmux select-pane -t "$editor_pane"
}
# Create multiple tdl windows with one per subdirectory in the current directory
# Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]
tdlm() {
[[ -z $1 ]] && { echo "Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tdlm."; return 1; }
local ai="$1"
local ai2="$2"
local base_dir="$PWD"
local first=true
# Rename the session to the current directory name (replace dots/colons which tmux disallows)
tmux rename-session "$(basename "$base_dir" | tr '.:' '--')"
for dir in "$base_dir"/*/; do
[[ -d $dir ]] || continue
local dirpath="${dir%/}"
if $first; then
# Reuse the current window for the first project
tmux send-keys -t "$TMUX_PANE" "cd '$dirpath' && tdl $ai $ai2" C-m
first=false
else
local pane_id=$(tmux new-window -c "$dirpath" -P -F '#{pane_id}')
tmux send-keys -t "$pane_id" "tdl $ai $ai2" C-m
fi
done
}
# Create a multi-pane swarm layout with the same command started in each pane (great for AI)
# Usage: tsl <pane_count> <command>
tsl() {
[[ -z $1 || -z $2 ]] && { echo "Usage: tsl <pane_count> <command>"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tsl."; return 1; }
local count="$1"
local cmd="$2"
local current_dir="${PWD}"
local -a panes
tmux rename-window -t "$TMUX_PANE" "$(basename "$current_dir")"
panes+=("$TMUX_PANE")
while (( ${#panes[@]} < count )); do
local new_pane
local split_target="${panes[-1]}"
new_pane=$(tmux split-window -h -t "$split_target" -c "$current_dir" -P -F '#{pane_id}')
panes+=("$new_pane")
tmux select-layout -t "${panes[0]}" tiled
done
for pane in "${panes[@]}"; do
tmux send-keys -t "$pane" "$cmd" C-m
done
tmux select-pane -t "${panes[0]}"
}

View file

@ -0,0 +1,33 @@
# Transcoding helpers have moved to omarchy-transcode.
transcode-video-1080p() {
omarchy-transcode "$1" mp4 1080p
}
transcode-video-4K() {
omarchy-transcode "$1" mp4 4k
}
transcode-video-gif() {
omarchy-transcode "$1" gif 1080p
}
img2jpg() {
omarchy-transcode "$1" jpg high
}
img2jpg-small() {
omarchy-transcode "$1" jpg low
}
img2jpg-medium() {
omarchy-transcode "$1" jpg medium
}
img2jpg-large() {
omarchy-transcode "$1" jpg high
}
img2png() {
omarchy-transcode "$1" png high
}

View file

@ -0,0 +1,36 @@
# Create a new worktree and branch from within current git directory.
ga() {
if [[ -z "$1" ]]; then
echo "Usage: ga [branch name]"
return 1
fi
local branch="$1"
local base="$(basename "$PWD")"
local wt_path="../${base}--${branch}"
git worktree add -b "$branch" "$wt_path"
mise trust "$wt_path"
cd "$wt_path"
}
# Remove worktree and branch from within active worktree directory.
gd() {
if gum confirm "Remove worktree and branch?"; then
local cwd base branch root worktree
cwd="$(pwd)"
worktree="$(basename "$cwd")"
# split on first `--`
root="${worktree%%--*}"
branch="${worktree#*--}"
# Protect against accidentally nuking a non-worktree directory
if [[ "$root" != "$worktree" ]]; then
cd "../$root"
git worktree remove "$cwd" --force || return 1
git branch -D "$branch"
fi
fi
}

View file

@ -0,0 +1 @@
for f in $OMARCHY_PATH/default/bash/fns/*; do source "$f"; done

View file

@ -0,0 +1,30 @@
if command -v mise &> /dev/null; then
eval "$(mise activate bash)"
fi
if [[ $- == *i* ]] && [[ ${TERM:-} != "dumb" ]] && command -v starship &> /dev/null; then
eval "$(starship init bash)"
fi
if command -v zoxide &> /dev/null; then
eval "$(zoxide init bash)"
fi
if command -v try &> /dev/null; then
try() {
unset -f try
eval "$(SHELL=/bin/bash command try init ~/Work/tries)"
try "$@"
}
fi
if command -v fzf &> /dev/null; then
if [[ -f /usr/share/fzf/completion.bash ]]; then
source /usr/share/fzf/completion.bash
fi
if [[ -f /usr/share/fzf/key-bindings.bash ]]; then
source /usr/share/fzf/key-bindings.bash
fi
fi
source "$OMARCHY_PATH/default/bash/completions"

View file

@ -0,0 +1,47 @@
set meta-flag on
set input-meta on
set output-meta on
set convert-meta off
set completion-ignore-case on
set completion-prefix-display-length 2
set show-all-if-ambiguous on
set show-all-if-unmodified on
# Arrow keys match what you've typed so far against your command history
"\e[A": history-search-backward
"\e[B": history-search-forward
"\e[C": forward-char
"\e[D": backward-char
# Immediately add a trailing slash when autocompleting symlinks to directories
set mark-symlinked-directories on
# Do not autocomplete hidden files unless the pattern explicitly begins with a dot
set match-hidden-files off
# Show all autocomplete results at once
set page-completions off
# If there are more than 200 possible completions for a word, ask to show them all
set completion-query-items 200
# Show extra file information when completing, like `ls -F` does
set visible-stats on
# Be more intelligent when autocompleting by also looking at the text after
# the cursor. For example, when the current line is "cd ~/src/mozil", and
# the cursor is on the "z", pressing Tab will not autocomplete it to "cd
# ~/src/mozillail", but to "cd ~/src/mozilla". (This is supported by the
# Readline used by Bash 4.)
set skip-completed-text on
# Coloring for Bash 4 tab completions.
set colored-stats on
# Cycle forward and backward through completion candidates (tab/shift+tab)
# (completion listing and display behavior configured above)
TAB: menu-complete
"\e[Z": menu-complete-backward
# On first Tab, complete the common prefix before cycling candidates
set menu-complete-display-prefix on

View file

@ -0,0 +1,6 @@
source ~/.local/share/omarchy/default/bash/envs
source ~/.local/share/omarchy/default/bash/shell
source ~/.local/share/omarchy/default/bash/aliases
source ~/.local/share/omarchy/default/bash/functions
source ~/.local/share/omarchy/default/bash/init
[[ $- == *i* ]] && bind -f ~/.local/share/omarchy/default/bash/inputrc

View file

@ -0,0 +1,13 @@
# History control
shopt -s histappend
HISTCONTROL=ignoreboth
HISTSIZE=32768
HISTFILESIZE="${HISTSIZE}"
# Autocompletion
if [[ ! -v BASH_COMPLETION_VERSINFO && -f /usr/share/bash-completion/bash_completion ]]; then
source /usr/share/bash-completion/bash_completion
fi
# Ensure command hashing is off for mise
set +h

17
os updates/fish-to-bash/bashrc Executable file
View file

@ -0,0 +1,17 @@
# If not running interactively, don't do anything (leave this at the top of this file)
[[ $- != *i* ]] && return
# NoSignal: default CLI editor (Omarchy normally exports this from uwsm/env).
export EDITOR="${EDITOR:-nvim}"
# All the default Omarchy aliases and functions
# (don't mess with these directly, just overwrite them here!)
source ~/.local/share/omarchy/default/bash/rc
# NoSignal: apply the active Caelestia colour scheme to this terminal.
[[ $- == *i* ]] && cat ~/.local/state/caelestia/sequences.txt 2>/dev/null
# Add your own exports, aliases, and functions here.
#
# Make an alias for invoking commands you use constantly
# alias p='python'

View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Ensure the NoSignal user-override files exist — bash port of configs.fish.
# Installed to ~/.config/hypr/scripts/configs.fish (the path hyprland.conf
# exec's at startup); runs as bash via this shebang.
# Usage: configs <config-dir>
reload=false
dir="$1"
[[ -d "$dir" ]] || mkdir -p "$dir"
if [[ ! -f "$dir/hypr-vars.conf" ]]; then
touch "$dir/hypr-vars.conf"
reload=true
fi
if [[ ! -f "$dir/hypr-user.conf" ]]; then
touch "$dir/hypr-user.conf"
reload=true
fi
$reload && hyprctl reload

View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Workspace dispatcher helper — bash port of Caelestia's wsaction.fish.
# Installed to ~/.config/hypr/scripts/wsaction.fish (the path the stock
# keybinds.conf calls); runs as bash via this shebang.
# Usage: wsaction [-g] <dispatcher> <workspace>
group=0
if [[ "$1" == "-g" ]]; then
group=1
shift
fi
if [[ $# -ne 2 ]]; then
echo "Wrong number of arguments. Usage: wsaction [-g] <dispatcher> <workspace>" >&2
exit 1
fi
dispatcher="$1"
target="$2"
active_ws=$(hyprctl activeworkspace -j | jq -r '.id')
if (( group )); then
# Move to the same slot within workspace group <target>
hyprctl dispatch "$dispatcher" "$(( (target - 1) * 10 + active_ws % 10 ))"
else
# Move to workspace <target> within the current group
hyprctl dispatch "$dispatcher" "$(( (active_ws - 1) / 10 * 10 + target ))"
fi

View file

@ -0,0 +1,68 @@
#!/bin/sh
# install-fish-to-bash.sh — migrate this NoSignal box from fish to bash, matching
# the Omarchy bash setup. Everything ships in this folder so it survives a wipe.
#
# Applies:
# 1. Vendors Omarchy's default/bash tree -> ~/.local/share/omarchy/default/bash
# 2. Installs ~/.bashrc (Omarchy template + Caelestia colour-scheme line)
# 3. Points kitty + foot at bash
# 4. Replaces the two Hyprland helper scripts with bash ports (kept at their
# .fish paths so the stock keybinds.conf / hyprland.conf keep working)
#
# Idempotent (safe to re-run). Backs up an existing ~/.bashrc once.
# Does NOT remove the fish package — see README.md.
set -eu
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
OMARCHY_BASH="$HOME/.local/share/omarchy/default/bash"
# 1. Vendor the Omarchy bash tree.
mkdir -p "$OMARCHY_BASH/fns"
for f in rc shell aliases envs init functions inputrc completions; do
cp "$SRC/bash/$f" "$OMARCHY_BASH/$f"
done
cp "$SRC/bash/fns/"* "$OMARCHY_BASH/fns/"
echo "vendored Omarchy bash tree -> $OMARCHY_BASH"
# 2. Install ~/.bashrc (back up any existing one, once).
if [ -f "$HOME/.bashrc" ] && [ ! -f "$HOME/.bashrc.pre-omarchy" ]; then
cp "$HOME/.bashrc" "$HOME/.bashrc.pre-omarchy"
echo "backed up existing ~/.bashrc -> ~/.bashrc.pre-omarchy"
fi
cp "$SRC/bashrc" "$HOME/.bashrc"
echo "installed ~/.bashrc"
# 3. Terminals -> bash.
KITTY="$HOME/.config/kitty/kitty.conf"
if [ -f "$KITTY" ]; then
if grep -q '^shell ' "$KITTY"; then
sed -i 's/^shell .*/shell bash/' "$KITTY"
else
printf '\nshell bash\n' >> "$KITTY"
fi
echo "kitty -> bash"
fi
FOOT="$HOME/.config/foot/foot.ini"
if [ -f "$FOOT" ]; then
if grep -q '^shell=' "$FOOT"; then
sed -i 's/^shell=.*/shell=bash/' "$FOOT"
else
sed -i '1i shell=bash' "$FOOT"
fi
echo "foot -> bash"
fi
# 4. Hyprland helper scripts -> bash (keep the .fish filenames the config calls).
SCRIPTS="$HOME/.config/hypr/scripts"
if [ -d "$SCRIPTS" ]; then
install -m 0755 "$SRC/hypr-wsaction.bash" "$SCRIPTS/wsaction.fish"
install -m 0755 "$SRC/hypr-configs.bash" "$SCRIPTS/configs.fish"
echo "Hyprland helper scripts -> bash"
fi
# 5. Reload Hyprland if running.
if command -v hyprctl >/dev/null 2>&1 && hyprctl version >/dev/null 2>&1; then
hyprctl reload >/dev/null 2>&1 && echo "reloaded Hyprland"
fi
echo "Done. Open a NEW terminal to land in bash."

View file

@ -0,0 +1,80 @@
# gamemode-toggle-deckshift — Game Mode toggle launches DeckShift
Companion to `gaming-key-guard` (which does the Super+Shift+S keybind). Patches
the package-owned nosignal-shell file; the canonical fix is in the fork.
## Request
The utilities **Game Mode** button (gamepad icon) should launch the DeckShift
gaming session when DeckShift is installed, and do nothing otherwise. Previously
it ran caelestia's cosmetic Game Mode (disable Hyprland animations/blur/gaps).
## Change
`modules/utilities/cards/Toggles.qml`, the `gameMode` `DelegateChoice`:
before
```qml
delegate: Toggle {
icon: "gamepad"
checked: GameMode.enabled
onClicked: GameMode.enabled = !GameMode.enabled
}
```
after (hardened)
```qml
delegate: Toggle {
id: gmTog
property bool launching: false
icon: "gamepad"
disabled: launching
onClicked: {
if (gmTog.launching) return;
gmTog.launching = true;
gmTog.internalChecked = false; // don't latch "on" — it's a launcher
relockTimer.start();
Quickshell.execDetached(["sh", "-c", "[ -x /usr/local/bin/switch-to-gaming ] && [ -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ] && exec /usr/local/bin/switch-to-gaming"]);
}
Timer { id: relockTimer; interval: 5000; onTriggered: gmTog.launching = false }
}
```
- **Kept styled as an off-toggle** (default `isToggle: true`, no `checked`) so it
keeps the muted/outline look of the other toggles. An earlier version set
`isToggle: false`, which flipped the icon to filled/accent — that was the
reported "changed colour"; reverted. `internalChecked` is reset to false on
click so the launcher never latches "on".
- **Debounced.** A fast double-click previously fired `switch-to-gaming` twice →
two `systemctl restart sddm` calls → the first restart consumed the one-shot
autologin marker and the second fell back to the password greeter (the reported
"takes you to the login screen, not gamescope"). The `launching` guard +
`disabled` + 5 s `relockTimer` ensure one launch per press.
- The `sh -c '… && … && exec switch-to-gaming'` guard mirrors the Super+Shift+S
bind and the deckshift-login install-check: nothing happens if DeckShift is absent.
- Adds `import Quickshell` (for `execDetached`; the file only had
`Quickshell.Bluetooth`). `GameMode` (from `qs.services`) is no longer referenced
here — the cosmetic Game Mode is still available via its IPC (`gameMode` target)
if a future build wants to re-expose it elsewhere.
- **Hidden when DeckShift isn't installed.** A `deckshiftProbe` Process
(`test -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop`) sets
`root.deckshiftInstalled` at load, and the `quickToggles` filter drops the
`gameMode` entry when it's false (same pattern as the existing `vpn` special-case)
— so the tile cleanly leaves the grid (no dead button, no gap) on non-gaming
boxes, and appears once DeckShift is installed (after a shell reload). Needs
`import Quickshell.Io` for `Process`.
## Pieces / integration
1. **`Toggles.qml`** — corrected file (drop-in for
`modules/utilities/cards/Toggles.qml` in the nosignal-shell fork). Rebuild the
package; set `NOSIGNAL_SKIP_SHELL_PATCH` for the build.
2. **`install-gamemode-toggle-deckshift.sh`** — idempotent fallback patch (backup +
overwrite). Reverted by the next nosignal-shell upgrade (which carries the fix).
3. **`1781478000-gamemode-toggle-deckshift.sh`** → `nosignal-update/migrations/`
(delegates via `$NOSIGNAL_SRC`).
**Shell restart required:** `qs -c caelestia -n -d` disables the file watcher, so
the patched file is not hot-reloaded — restart the shell (**Ctrl+Super+Alt+R**) or
log out/in after patching.
## Apply on this box now (needs root)
```sh
sudo sh ~/nosignal-handoff/gamemode-toggle-deckshift/install-gamemode-toggle-deckshift.sh
```
then **Ctrl+Super+Alt+R**.

View file

@ -0,0 +1,207 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Bluetooth
import Caelestia.Components
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
import qs.modules.nexus
import qs.modules.bar.popouts as BarPopouts
StyledRect {
id: root
required property DrawerVisibilities visibilities
required property BarPopouts.Wrapper popouts
// NoSignal: the gamepad "Game Mode" toggle launches DeckShift gaming. Hide it
// entirely when DeckShift isn't installed (no gamescope session file) checked
// once at load by deckshiftProbe below.
property bool deckshiftInstalled: false
Process {
id: deckshiftProbe
running: true
command: ["test", "-f", "/usr/share/wayland-sessions/gamescope-session-steam-nm.desktop"]
onExited: (exitCode, exitStatus) => root.deckshiftInstalled = exitCode === 0
}
readonly property var quickToggles: {
const seenIds = new Set();
return Config.utilities.quickToggles.filter(item => {
if (!(item.enabled ?? true))
return false;
if (seenIds.has(item.id)) {
return false;
}
if (item.id === "vpn") {
return GlobalConfig.utilities.vpn.provider.some(p => typeof p === "object" ? (p.enabled === true) : false);
}
// NoSignal: drop the Game Mode tile unless DeckShift is installed.
if (item.id === "gameMode") {
return root.deckshiftInstalled;
}
seenIds.add(item.id);
return true;
});
}
readonly property int splitIndex: Math.ceil(quickToggles.length / 2)
readonly property bool needExtraRow: quickToggles.length > 6
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Quick Toggles")
font: Tokens.font.body.medium
}
QuickToggleRow {
model: root.needExtraRow ? root.quickToggles.slice(0, root.splitIndex) : root.quickToggles
}
QuickToggleRow {
visible: root.needExtraRow
model: root.needExtraRow ? root.quickToggles.slice(root.splitIndex) : []
}
}
component QuickToggleRow: ButtonRow {
property alias model: repeater.model
Layout.fillWidth: true
spacing: Tokens.spacing.small
Repeater {
id: repeater
delegate: DelegateChooser {
role: "id"
DelegateChoice {
roleValue: "wifi"
delegate: Toggle {
icon: "wifi"
checked: Nmcli.wifiEnabled
onClicked: Nmcli.toggleWifi()
}
}
DelegateChoice {
roleValue: "bluetooth"
delegate: Toggle {
icon: "bluetooth"
checked: Bluetooth.defaultAdapter?.enabled ?? false // qmllint disable unresolved-type
onClicked: {
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
if (adapter)
adapter.enabled = !adapter.enabled;
}
}
}
DelegateChoice {
roleValue: "mic"
delegate: Toggle {
icon: "mic"
checked: !Audio.sourceMuted
onClicked: {
const audio = Audio.source?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
}
DelegateChoice {
roleValue: "settings"
delegate: Toggle {
icon: "settings"
inactiveOnColour: Colours.palette.m3onSurfaceVariant
isToggle: false
onClicked: {
root.visibilities.utilities = false;
WindowFactory.create();
}
}
}
DelegateChoice {
roleValue: "gameMode"
delegate: Toggle {
id: gmTog
// NoSignal: launch the DeckShift gaming session if installed;
// otherwise do nothing. (Was caelestia's cosmetic Game Mode.)
// Kept styled as an off-toggle (muted, outline icon) the
// gamepad is a momentary LAUNCHER, not a stateful toggle.
// DEBOUNCED: a fast double-click must not fire two
// `switch-to-gaming` runs two SDDM restarts race the
// one-shot autologin and drop you at the password greeter.
// Guard mirrors the Super+Shift+S bind + the deckshift-login
// install-check, so it no-ops if DeckShift is absent.
property bool launching: false
icon: "gamepad"
disabled: launching
onClicked: {
if (gmTog.launching)
return;
gmTog.launching = true;
gmTog.internalChecked = false; // don't latch "on"
relockTimer.start();
Quickshell.execDetached(["sh", "-c", "[ -x /usr/local/bin/switch-to-gaming ] && [ -f /usr/share/wayland-sessions/gamescope-session-steam-nm.desktop ] && exec /usr/local/bin/switch-to-gaming"]);
}
Timer {
id: relockTimer
interval: 5000
onTriggered: gmTog.launching = false
}
}
}
DelegateChoice {
roleValue: "dnd"
delegate: Toggle {
icon: "notifications_off"
checked: Notifs.dnd
onClicked: Notifs.dnd = !Notifs.dnd
}
}
DelegateChoice {
roleValue: "vpn"
delegate: Toggle {
icon: "vpn_key"
checked: VPN.connected && VPN.status.state !== "needs-auth" && VPN.status.state !== "error"
enabled: !VPN.connecting
isToggle: VPN.status.state !== "needs-auth" && VPN.status.state !== "error"
inactiveOnColour: Colours.palette.m3onSurfaceVariant
onClicked: VPN.toggle()
}
}
}
}
}
component Toggle: IconButton {
inactiveColour: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2)
fillWidth: true
isToggle: true
isRound: true
shapeMorph: true
}
}

View file

@ -0,0 +1,40 @@
#!/bin/sh
# install-gamemode-toggle-deckshift.sh — idempotent. REQUIRES ROOT (writes the
# package-owned shell file under /etc/xdg).
#
# Repoints the utilities "Game Mode" gamepad toggle to launch the DeckShift gaming
# session if DeckShift is installed; otherwise it does nothing. (Was caelestia's
# cosmetic Game Mode toggle — disable animations/blur/gaps.) See README.
#
# Toggles.qml is shipped by the `nosignal-shell` package (baked into the fork). The
# REAL fix is in the fork source — this fallback patch is for already-installed boxes
# and is reverted by the next nosignal-shell upgrade (which carries the fixed file).
# The builder bakes it into the fork and sets NOSIGNAL_SKIP_SHELL_PATCH to skip this.
set -eu
SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
TARGET=/etc/xdg/quickshell/caelestia/modules/utilities/cards/Toggles.qml
if [ -n "${NOSIGNAL_SKIP_SHELL_PATCH:-}" ]; then
echo ":: skipping fallback shell patch (NOSIGNAL_SKIP_SHELL_PATCH — fork bakes the fix)"
exit 0
fi
[ "$(id -u)" -eq 0 ] || { echo "must run as root (writes $TARGET)" >&2; exit 1; }
if [ ! -f "$TARGET" ]; then
echo "WARNING: $TARGET not present — nosignal-shell not installed here; nothing to patch." >&2
exit 0
fi
if cmp -s "$SELF_DIR/Toggles.qml" "$TARGET"; then
echo ":: Game Mode toggle already up to date — nothing to do."
exit 0
fi
# preserve the ORIGINAL (pre-NoSignal) file only on the first patch, so re-running
# with a newer Toggles.qml (e.g. the hardened version) doesn't overwrite the backup
[ -f "$TARGET.prefix.bak" ] || cp -a "$TARGET" "$TARGET.prefix.bak"
install -m 0644 "$SELF_DIR/Toggles.qml" "$TARGET"
echo ":: patched $TARGET (backup: $TARGET.prefix.bak)"
echo ":: restart the shell to apply: Ctrl+Super+Alt+R, or log out/in."

View file

@ -0,0 +1,55 @@
# gaming-enablement — multilib repo + omarchy-pkg-add shim
Makes Omarchy-targeted Steam/gaming install scripts (e.g. **DeckShift**) work on
NoSignal. Two gaps were blocking them:
1. **`[multilib]` was disabled** in `/etc/pacman.conf` — the 32-bit repo every
`lib32-*` package (Steam, Wine, gaming libs) lives in. Without it, pacman/yay
can't see those packages at all.
2. **`omarchy-pkg-add` doesn't exist** on NoSignal — it ships `yay`/Shelly, not the
Omarchy CLI helpers, so scripts die at `omarchy-pkg-add: command not found`.
## What it does
- Uncomments the `[multilib]` block in `/etc/pacman.conf` (leaving
`[multilib-testing]` alone) and runs `pacman -Sy`.
- Installs **Omarchy CLI shims**`~/.local/bin` (NoSignal ships yay + the
caelestia launcher, not the Omarchy helpers DeckShift expects):
| Shim | NoSignal behaviour |
|------|------------------|
| `omarchy-pkg-add` | `yay -S --needed --noconfirm "$@"` (idempotent installs) |
| `omarchy-install-gaming-steam` | `yay -S steam` + best-effort first launch (needs multilib) |
| `omarchy-hw-nvidia-gsp` | exit 0 if an NVIDIA GPU is present (modern/GSP branch) |
| `omarchy-hw-nvidia-without-gsp` | exit 1 (defaults to modern branch; see note) |
| `omarchy-restart-walker` | no Walker here — refresh desktop DB, succeed |
The NVIDIA shims default any present card to the modern (`nvidia-utils`)
branch; pre-Turing cards need `nvidia-580xx-*` by hand. On this AMD+Intel box
both correctly report "no NVIDIA".
## Install
```sh
sh install-gaming-enablement.sh # needs sudo for the pacman.conf edit + sync
```
After this, `./deckshift.sh` (and similar) find their deps and proceed. Note:
DeckShift also *optionally* calls `omarchy-install-gaming-steam`,
`omarchy-hw-nvidia-gsp`, `omarchy-restart-walker` — those are `command -v`-guarded
in the script, so they're skipped harmlessly (only `omarchy-pkg-add` was fatal).
## Builder notes
For a gaming-capable ISO:
- **Enable `[multilib]` in the image's `/etc/pacman.conf` by default** (uncomment
the two lines) and sync in the build.
- **Ship `omarchy-pkg-add`** in `~/.local/bin` (or `/usr/local/bin`). Consider
shipping the other commonly-referenced Omarchy helpers
(`omarchy-install-gaming-steam`, `omarchy-hw-nvidia-*`, `omarchy-restart-walker`)
as shims if broad Omarchy-script compatibility is wanted.
- This pairs naturally with shipping `gamemode` / `lib32-*` Steam deps in the
base if gaming is a first-class use case.
This is a **package/repo-config delta** (unlike the other changes) — it touches
the package list / pacman.conf rather than dropping a self-contained component.

View file

@ -0,0 +1,31 @@
#!/bin/sh
# install-gaming-enablement.sh — enable the multilib repo + install the
# omarchy-pkg-add shim, so Omarchy-targeted Steam/gaming install scripts (e.g.
# DeckShift) work on NoSignal. Idempotent. Needs sudo for the pacman.conf edit and
# database sync (it will prompt).
set -eu
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
BIN="$HOME/.local/bin"
# 1. Enable [multilib] — the 32-bit repo every lib32-* package lives in.
if pacman-conf --repo-list 2>/dev/null | grep -qx multilib; then
echo ":: [multilib] already enabled"
else
echo ":: enabling [multilib] in /etc/pacman.conf (sudo)..."
# Uncomment ONLY the [multilib] block (leaves [multilib-testing] alone).
sudo sed -i '/^#\[multilib\]/,/^#Include/ s/^#//' /etc/pacman.conf
sudo pacman -Sy
echo ":: [multilib] enabled and databases synced"
fi
# 2. Omarchy CLI shims (NoSignal ships yay/caelestia, not the Omarchy helpers).
mkdir -p "$BIN"
for shim in omarchy-pkg-add omarchy-install-gaming-steam \
omarchy-hw-nvidia-gsp omarchy-hw-nvidia-without-gsp \
omarchy-restart-walker; do
install -m 0755 "$SRC/$shim" "$BIN/$shim"
echo ":: installed $shim -> $BIN/$shim"
done
echo "Done. multilib + Omarchy shims ready — re-run your gaming installer."

View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# omarchy-hw-nvidia-gsp — NoSignal shim. Exit 0 if an NVIDIA GPU is present (and
# thus should use the modern, GSP-firmware driver branch: nvidia-utils). Exit 1
# if no NVIDIA GPU. DeckShift only calls this when it has already detected an
# NVIDIA card, to choose modern vs legacy.
#
# Limitation: this defaults any present NVIDIA card to the modern/GSP branch
# (correct for Turing/RTX-20 and newer, ~2018+). It does not distinguish very
# old pre-Turing cards — see omarchy-hw-nvidia-without-gsp.
if lspci -d 10de:: 2>/dev/null | grep -qiE 'VGA|3D|Display'; then
exit 0
fi
exit 1

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# omarchy-hw-nvidia-without-gsp — NoSignal shim. Exit 0 only for legacy pre-GSP
# NVIDIA (Maxwell/Pascal/Volta → the nvidia-580xx driver branch).
#
# This shim cannot reliably tell GPU generations apart, so it defaults to the
# modern branch (always exit 1; omarchy-hw-nvidia-gsp handles present cards). If
# you actually run a pre-Turing NVIDIA card, install nvidia-580xx-utils /
# nvidia-580xx-dkms by hand.
exit 1

Some files were not shown because too many files have changed in this diff Show more