Add CoreDesktop documentation

User pages: installing, configure, updating, software and settings,
sandboxed AI agents and troubleshooting. Contributor pages: how
CoreDesktop works, update hooks, migrations, configure items and
sibling projects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 16:01:09 -06:00
co-authored by Claude Opus 5.5
parent 5d7c38ffb9
commit 683030c759
14 changed files with 822 additions and 1 deletions
+84
@@ -0,0 +1,84 @@
# Configure Items
Each file in `configure.d/` is one item in the `configure` menu. To add an item, add a file.
## Naming
The file name sets both the order and the item's name. The number prefix is dropped: `configure.d/30-claude.sh` is shown third and can be run as `configure claude`.
Current items:
| File | Item |
|---|---|
| `10-core.sh` | Federated Computer Core: Superpanel, Tailscale, Nextcloud, email, files, Element |
| `30-claude.sh` | Claude Code |
| `31-opencode.sh` | opencode |
| `32-hermes.sh` | Hermes Agent |
## What a file defines
| Name | Required | Purpose |
|---|---|---|
| `TITLE` | yes | Shown in the menu |
| `DESCRIPTION` | yes | One line, shown after the title |
| `is_configured` | yes | Returns 0 if the item is done. Its output is hidden. |
| `run_configure` | yes | Sets the item up. Return non-zero if it wasn't set up. |
| `is_installed` | no | If defined and it fails, the item is left out of the menu |
The file is sourced in a subshell, so variables and `cd` don't leak out.
## What's available
| Name | What it is |
|---|---|
| `$COREDESKTOP` | `~/.local/share/coredesktop` |
| `confirm "Question?"` | Yes/no question, default **no** |
| `confirm_yes "Question?"` | Yes/no question, default **yes** |
| `unsandboxed <agent> ...` | Runs an AI agent's real command. Setting an agent up writes outside its sandbox, so use this rather than the sandboxed command. |
`~/.local/bin` is on `PATH`.
## Example
```bash
#!/bin/bash
TITLE="Claude Code"
DESCRIPTION="Sign in to Anthropic"
is_installed() {
command -v claude
}
is_configured() {
claude auth status
}
run_configure() {
echo " 1) Claude subscription (Pro, Max, Team or Enterprise)"
echo " 2) Anthropic Console (pay for API usage)"
local choice
read -rp "How do you pay for Claude? [1] " choice
case "$choice" in
""|1) claude auth login ;;
2) claude auth login --console ;;
*) echo "Choose 1 or 2."; return 1 ;;
esac
}
```
## Items with several steps
`10-core.sh` shows the pattern for an item made of several parts. Each part has a `<part>_done` check and a `setup_<part>` function, and a `step` helper does the rest:
- skips parts that are already done
- asks before each of the others
- carries on when one part fails, and reports the failure at the end
That way, running `configure` again only redoes what's missing.
Guidelines:
- **Remember answers.** Save what the user typed, like `~/.config/coredesktop/core.conf`, and offer it as the default next time.
- **Don't keep passwords** in files or variables longer than needed. Pass them on stdin, not as arguments, so they don't show in the process list. Prefer revocable tokens, as `10-core.sh` does with Nextcloud app passwords.
- **Check the real state** in `is_configured` and the `_done` checks, not just a marker file, so the menu stays accurate if the user changes something by hand.
+61
@@ -0,0 +1,61 @@
# Configure
Some of what CoreDesktop installs needs you to sign in or make a choice first. Run:
```
configure
```
It lists each item as **done** or **to do**. Choose a number to set one up, `a` to set up everything still to do, or `q` to quit. It's safe to run again: finished steps show as "already set up" and are skipped.
```
configure # menu
configure core # set up one item by name
configure --list # show what's set up, and the item names
```
## Federated Computer Core (`configure core`)
It asks for:
- your Superpanel's address, e.g. `https://superpanel.example.com`
- your Core email address
- your name, as shown on email you send
- your Core password, if Nextcloud, email or files still need setting up
Then it offers each of these in turn:
| Step | What it does |
|---|---|
| Superpanel | Adds Superpanel to your applications |
| Tailscale | Joins this computer to your Core's Tailscale network (`headscale.<domain>`). It prints a link to open and sign in. |
| Nextcloud | Adds Nextcloud to GNOME Online Accounts, for calendar, contacts and files |
| Email | Adds your Core email to GNOME Online Accounts |
| Files | Mounts your Nextcloud files at `/mnt/<domain>`, shown as `<domain>` in the Files sidebar |
| Element | Points Element at your Core's Matrix server, so you only need to choose "Sign in" |
What you entered is saved in `~/.config/coredesktop/core.conf`, so next time the answers are filled in for you.
### Passwords
Your Core password is only used while `configure` runs. For Nextcloud and the files mount, `configure` creates a Nextcloud **app password** instead of storing your Core password. It shows in Nextcloud under **Settings > Security** as "CoreDesktop on <computer name>", where you can revoke it. Email uses your Core password, which GNOME keeps in its keyring.
### Your Nextcloud files
The files are mounted with davfs2:
- They're mounted each time you log in to the desktop, not at boot, and not for SSH-only logins.
- If they aren't mounted, run `mount /mnt/<domain>`.
- Mounting without sudo requires you to be in the `network` group. `configure` adds you, but the group only takes effect at your next login.
See [Troubleshooting](Troubleshooting#nextcloud-files) if the folder is empty.
## AI agents
| Item | What it asks |
|---|---|
| `configure claude` | Whether you pay with a Claude subscription or the Anthropic Console, then signs you in |
| `configure opencode` | Which AI providers to sign in to. Pick models inside opencode with `/models`. |
| `configure hermes` | Nous Portal, or another provider such as OpenRouter, OpenAI or Anthropic |
See [Sandboxed AI Agents](Sandboxed-AI-Agents) for how the agents run.
+31
@@ -0,0 +1,31 @@
# CoreDesktop
CoreDesktop is a set of scripts that turns a fresh Arch Linux + GNOME install into a keyboard-driven tiling desktop with the software we use at Federated Computer. It isn't a distribution: Arch does the heavy lifting, and CoreDesktop installs, configures and keeps things up to date on top of it.
## Using CoreDesktop
- [Installing](Installing): install Arch, then CoreDesktop
- [Configure](Configure): connect to your Federated Computer Core and sign in to the AI agents
- [Updating](Updating): `update`, `refresh`, `version`, update notifications and snapshots
- [Software and Settings](Software-and-Settings): software, shell, firewall and system defaults
- [Sandboxed AI Agents](Sandboxed-AI-Agents): how opencode, Hermes Agent and Claude Code are contained
- [Troubleshooting](Troubleshooting): common problems and fixes
## Working on CoreDesktop
- [How CoreDesktop Works](How-CoreDesktop-Works): repository layout and what runs when
- [Update Hooks](Update-Hooks): adding a step to `update`
- [Migrations](Migrations): getting a change onto machines that are already installed
- [Configure Items](Configure-Items): adding something to `configure`
- [Sibling Projects](Sibling-Projects): building your own project on top of CoreDesktop
## Quick reference
| Command | What it does |
|---|---|
| `update` | Update CoreDesktop, Arch, AUR, Flatpaks and everything else |
| `refresh` | Re-run the installer to pick up newly added software |
| `configure` | Set up your Core, Tailscale, Nextcloud, email and AI agents |
| `version` | Show the CoreDesktop version, kernel and last update |
| `sibling` | Install, list or remove sibling projects |
| `unsandboxed <agent>` | Run an AI agent outside its sandbox |
+70
@@ -0,0 +1,70 @@
# How CoreDesktop Works
CoreDesktop is a git repository of bash scripts. Each machine has a clone in `~/.local/share/coredesktop`, and `update` keeps that clone current with `git pull`. Changes reach machines when they're pushed to `main`.
## Repository layout
| Path | What's there |
|---|---|
| `install.sh` | The installer. `refresh` re-runs it. |
| `update`, `refresh`, `configure`, `version`, `sibling` | User commands. They're copied to `~/.local/bin` by the installer and on every `update`. |
| `utils.sh` | Shared helpers: `install_packages`, `remove_packages`, `remove_flatpaks`, `enable_services`, `install_script`, `install_pipx_apps`, `ensure_gext`, `run_migrations` |
| `packages.conf` | Packages by category (`SYSTEM_UTILS`, `DEV_TOOLS`, `MAINTENANCE`, `DESKTOP`, `MEDIA`, `FONTS`), plus `PIPX` apps and `SERVICES` to enable |
| `remove-packages.conf` | Packages and Flatpaks that `update` uninstalls |
| `install-*.sh`, `claude.sh`, `bitwarden.sh` | Installer steps: Flatpaks, tmux plugins, mise/Ruby, Neovim, Claude Code, Bitwarden SSH agent |
| `updates.d/` | The steps `update` runs, in filename order. See [Update Hooks](Update-Hooks). |
| `migrations/` | One-time changes for machines that are already installed. See [Migrations](Migrations). |
| `configure.d/` | The items `configure` offers. See [Configure Items](Configure-Items). |
| `system/` | Firewall, Snapper, system defaults, GPU drivers, update notifications |
| `gnome/` | GNOME extensions and their dconf settings |
| `shell/` | bashrc, aliases, functions and the starship prompt |
| `sandbox/` | The AI agent sandbox: the `agent` wrapper, `bin/` links and default `config/` |
| `skills/` | Detailed guides for sibling project authors |
## Files on a machine
| Path | What it is |
|---|---|
| `~/.local/share/coredesktop` | The clone |
| `~/.local/bin/{update,refresh,configure,version,sibling}` | Installed copies of the commands |
| `~/.local/state/coredesktop/migrations/` | One empty file per migration that has run |
| `~/.local/state/coredesktop/pacman-update.log` | Output of the last `pacman -Su` |
| `~/.local/state/coredesktop/update-notified` | The newest version the user has been notified about |
| `~/.config/coredesktop/core.conf` | What the user entered in `configure core` |
| `~/.config/coredesktop/sandbox/` | The user's own sandbox rules |
| `~/.local/share/siblings/` | Sibling projects |
## Install vs update
Getting a change onto every machine has two halves:
- **New machines** run `install.sh`.
- **Existing machines** only run `update`.
| Kind of change | New machines | Existing machines |
|---|---|---|
| Add a package to `packages.conf` | `install.sh` | `10-pacman.sh` installs new packages automatically |
| Remove a package | Take it out of `packages.conf` | Add it to `remove-packages.conf` |
| Add a service to `SERVICES` | `install.sh` | `15-services.sh` enables it automatically |
| Add a pipx app to `PIPX` | `install.sh` | `60-pipx.sh` installs it automatically |
| Anything else in `install.sh` | `install.sh` | Add a [migration](Migrations) too |
| Something to redo on every update | `install.sh` | Add an [update hook](Update-Hooks) |
If you change `install.sh` and existing machines need the change too, you must also add a migration.
## Scripts that replace themselves
`update` pulls the repository, which can rewrite the scripts that are running. To make that safe:
- `update`, `refresh`, `configure`, `update-check` and `sandbox/agent` wrap their whole body in `{ ...; exit; }`, so bash reads the entire file before running any of it.
- `update` installs its fresh copy to `~/.local/bin/update`, then re-executes itself, so the rest of the run uses the new version.
Keep that wrapper in any new long-running script that lives in the repository.
## Conventions
- Bash, with a `# Purpose :` header on standalone scripts.
- Scripts must be safe to run again. Users re-run the installer to repair things, and migrations can run on fresh installs.
- Check before changing: `is_installed`, `grep -q` before appending, `cmp -s` before replacing a file.
- Write per-machine state outside the repository (`~/.config`, `~/.local/state`), never into tracked files, or `git pull` will fail.
- Messages tell the user what happened and what to do next.
+53
@@ -0,0 +1,53 @@
# Installing
You install CoreDesktop in two stages: first Arch Linux with GNOME, then CoreDesktop on top.
## 1. Install Arch Linux
1. Download the Arch ISO from <https://archlinux.org/download/> and write it to a USB stick.
2. Boot from the stick and run `archinstall`.
3. Mostly take the defaults, except:
- **Graphics:** if you have an NVIDIA GPU, choose the proprietary NVIDIA option. Choose the AMD option for AMD GPUs.
- **Profile:** choose "Desktop", then "GNOME".
- **Audio:** choose PipeWire.
- **Filesystem:** keep the default, btrfs. CoreDesktop uses it to take a snapshot before every update (see [Updating](Updating#snapshots-and-rolling-back)).
4. Reboot and log in to GNOME.
### No wired network?
Set up Wi-Fi from the installer with `iwctl`:
```
device list
station wlan0 scan
station wlan0 get-networks
station wlan0 connect <SSID>
```
Replace `wlan0` with your device from `device list`, then check with `ping archlinux.org`. The [Arch wiki](https://wiki.archlinux.org/title/Network_configuration/Wireless) has more detail.
It's normal to get the Arch install wrong the first time or two. Just start again.
## 2. Install CoreDesktop
Open a terminal and run:
```
wget -qO- https://gitea.federated.computer/david/coredesktop/raw/branch/main/install.sh | bash
```
The installer:
- asks for your password once, at the start
- updates Arch, then installs yay, the packages, Flatpaks, GNOME extensions, Ruby, Neovim, Claude Code and the sandboxed AI agents
- turns on the firewall, Snapper snapshots, update notifications and the system defaults
- installs the `update`, `refresh`, `configure`, `version` and `sibling` commands into `~/.local/bin`
CoreDesktop itself lives in `~/.local/share/coredesktop`.
If anything fails, run the same command again. It skips what's already done.
## 3. Finish setting up
1. Reboot when the installer finishes.
2. Run `configure` to connect to your Federated Computer Core and sign in to the AI agents. See [Configure](Configure).
+63
@@ -0,0 +1,63 @@
# Migrations
A migration is a script that `update` runs **once** on each machine. It's how machines installed before a change catch up with it.
## When you need one
If you change `install.sh`, or a script it calls, and machines that are already installed need the change too, add a migration. Package, service and pipx changes don't need one; see the table in [How CoreDesktop Works](How-CoreDesktop-Works#install-vs-update).
## Adding one
```bash
cd ~/Projects/coredesktop
$EDITOR migrations/$(date +%s).sh
```
Usually the migration calls the same script as the installer, so both paths share one copy of the logic:
```bash
#!/bin/bash
# Turn on the ufw firewall
bash "$HOME/.local/share/coredesktop/system/firewall.sh"
```
## How they run
- `updates.d/12-migrations.sh` runs them during `update`, and `install.sh` runs them at the end of every install.
- They run oldest first (by filename), with `bash`, as the user.
- A migration is recorded in `~/.local/state/coredesktop/migrations/<name>` **only when it exits 0**.
- The first failure stops the run, so a later migration never runs before an earlier one. The failed one is retried on the next `update`.
## Rules
- **Make it safe to repeat.** Fresh installs run every migration, right after `install.sh` has done the same work. A failed migration also runs again.
- **Exit non-zero on failure**, and only then. If you exit 0 when it didn't work, the migration is recorded and never runs again. If you exit non-zero when it did work, it blocks every later migration.
- **Don't assume tools work yet.** Migrations run right after the pacman upgrade, before `60-pipx.sh` and `80-gnome-extensions.sh` repair things. After a Python upgrade, for example, pipx apps such as `gext` are broken. Source `utils.sh` and use a helper such as `ensure_gext` to repair what you need.
- **Check the outcome, not the starting state.** For example, enable an extension even if it was already installed, in case an earlier run stopped half-way.
- **Never rename or delete a migration** that has been pushed. Machines track migrations by filename.
## Example
```bash
#!/bin/bash
# Install the Tailscale GNOME extension
source "$HOME/.local/share/coredesktop/utils.sh"
ext="tailscale-gnome@diskmth.fr"
ensure_gext || exit 1
if ! ~/.local/bin/gext list | grep -q "$ext"; then
~/.local/bin/gext --filesystem install "$ext" || exit 1
fi
# Enable even if it was already installed, in case an earlier run stopped short
~/.local/bin/gext --filesystem enable "$ext"
```
## Checking a machine
```bash
ls ~/.local/share/coredesktop/migrations/ # all migrations
ls ~/.local/state/coredesktop/migrations/ # the ones that have run
bash ~/.local/share/coredesktop/migrations/<name>.sh; echo "exit: $?" # run one by hand to see the error
```
+45
@@ -0,0 +1,45 @@
# Sandboxed AI Agents
CoreDesktop installs three AI coding agents: **opencode**, **Hermes Agent** and **Claude Code**. It runs all three in a sandbox, so an agent can't read your secrets or change files outside your project.
Before using an agent the first time, sign in with [`configure`](Configure#ai-agents).
## What the sandbox allows
A sandboxed agent:
- can change files only in the directory you start it from, plus its own settings, `/tmp` and a few package caches. Start it from a project directory: it refuses to start from your home folder or `/`.
- can't read your SSH and GPG keys, password stores, keyrings, browser profiles, cloud and Git credentials, or the other agents' logins
- can reach only AI providers and common developer sites: GitHub, Gitea, npm, PyPI, crates.io, RubyGems, the Go module proxy and archlinux.org
## How it works
| Agent | Sandbox |
|---|---|
| `opencode`, `hermes`, `hermes-agent` | Run inside Anthropic's [sandbox runtime](https://github.com/anthropics/sandbox-runtime) (`srt`), which uses bubblewrap and socat |
| Hermes' gateway | Its background service, which lets you talk to Hermes over Matrix, runs in the same sandbox. It can also reach the Matrix server in Hermes' settings. |
| Claude Code | Uses its own built-in sandbox for every command it runs. See `/sandbox` inside Claude. |
Typing an agent's name runs it sandboxed, because `sandbox/bin` comes first on your `PATH`. To run one without the sandbox:
```
unsandboxed opencode
```
## Allowing more
If an agent needs a site or folder the sandbox blocks, add it in:
- `~/.config/coredesktop/sandbox/base.json`, for every agent
- `~/.config/coredesktop/sandbox/<agent>.json`, for one agent: `opencode.json` or `hermes.json`
```json
{
"network": { "allowedDomains": ["docs.example.com"] },
"filesystem": { "allowWrite": ["~/notes"] }
}
```
Your files only add to CoreDesktop's defaults, which are in `sandbox/config/` in the repository. The change applies the next time you start the agent. For Hermes' gateway, run `systemctl --user restart hermes-gateway`.
For Claude Code, change the `sandbox` section of `~/.claude/settings.json`. CoreDesktop only fills in defaults there and never overwrites what you've set.
+43
@@ -0,0 +1,43 @@
# Sibling Projects
A sibling project is another git repository that builds on CoreDesktop: extra tools, team-specific setup, dotfiles and so on. CoreDesktop installs it and keeps it up to date.
## Using siblings
```
sibling # asks for the repository's git URL
sibling add <url> # same, without the prompt
sibling list # installed siblings, their URL and current commit
sibling remove <name> # delete it
```
`sibling add`:
1. Clones the repository into `~/.local/share/siblings/<name>`.
2. Runs its `install.sh`, if it has one.
From then on, every `update` pulls each sibling and runs its `update.sh`, if it has one.
`sibling remove` only deletes the clone. Anything its `install.sh` set up elsewhere stays.
## Making a repository a sibling
A repository needs no special files to be installable. To do more, add either or both of these at the top level:
| File | When it runs |
|---|---|
| `install.sh` | Once, from the repository folder, when the user runs `sibling add`, and again if they re-run it |
| `update.sh` | On every `update`, from the repository folder, after the pull |
Key rules:
- **The scripts inherit nothing from CoreDesktop.** Set `COREDESKTOP="$HOME/.local/share/coredesktop"` yourself, and source `$COREDESKTOP/utils.sh` for its helpers.
- **Exit non-zero on failure.** `install.sh` may prompt; `update.sh` shouldn't, and must be safe to run every time.
- **Keep `git pull --ff-only` working.** Never force-push the default branch, and never write into tracked files in the clone.
- **Private repositories** work only if the user can already clone them, with an SSH key or credential helper.
The full guide, with templates, testing steps and a checklist, is in the repository: [`skills/sibling-project-format.md`](https://gitea.federated.computer/Federated_Computer/coredesktop/src/branch/main/skills/sibling-project-format.md).
## Custom update hooks
For full control over how it updates, a sibling can instead copy its own hook, numbered 72–79, into CoreDesktop's `updates.d/` from its `install.sh`. Use one approach or the other, not both, or the project updates twice. See [`skills/sibling-project-updates.md`](https://gitea.federated.computer/Federated_Computer/coredesktop/src/branch/main/skills/sibling-project-updates.md) and [Update Hooks](Update-Hooks).
+87
@@ -0,0 +1,87 @@
# Software and Settings
## The desktop
GNOME with the **Tiling Shell** extension, set up as a three-column layout that works especially well on ultrawide monitors. The Tiling Shell icon in the top bar changes the layout. **Super + arrow keys** move windows between tiles.
The **Tailscale** GNOME extension shows your tailnet status in the top bar.
## Where software comes from
| Source | Defined in | Examples |
|---|---|---|
| pacman | `packages.conf` | GNOME, Neovim, tmux, Go, Rust, Node, Docker, Tailscale, Element, LibreOffice, Ghostty, kitty, opencode |
| AUR (via yay) | `packages.conf` | Helium browser, Winboat |
| Flathub | `install-flatpaks.sh` | Chrome, Firefox, Brave, Zen, VS Code, VSCodium, Signal, Telegram, Thunderbird, Obsidian, Logseq, OBS, Kdenlive, Inkscape, Zoom, Trayscale, LocalSend, Boxes and more |
| mise | `install-mise-ruby-rails.sh` | Ruby and Rails |
| pipx | `packages.conf` (`PIPX`) | terrahour |
| Their own installers | `claude.sh`, `sandbox/setup.sh` | Claude Code, Hermes Agent |
`update` installs anything newly added to these lists, and removes anything listed in `remove-packages.conf`.
To install more apps yourself, use `yay -S <package>` or browse <https://flathub.org>. To suggest something for everyone, open an issue or pull request.
## Shell
`~/.bashrc` sources `~/.local/share/coredesktop/shell/bashrc`, which sets up:
- the **starship** prompt (your own `~/.config/starship.toml` wins if you have one)
- **zoxide**: `z <part of a path>` jumps to a directory you've used before
- **fzf**: Ctrl-R searches history, Ctrl-T inserts a file path, Alt-C changes directory
- **eza** for `ls`, `ll`, `la` and `lt`
- aliases: `g` (git), `lg` (lazygit), `d` (docker), `ld` (lazydocker), `gcm`, `gcam`, `gcad`, `..`, `...`, `ff` (fuzzy-find with preview), `eff` (open the result in your editor)
- functions:
- `n`: open Neovim
- `open`: open a file or URL in its default app
- `compress` and `decompress`: make and unpack `.tar.gz` archives
- `fip`, `dip` and `lip`: start, stop and list SSH port forwards
- `ga` and `gd`: create and remove git worktrees
Anything you add to `~/.bashrc` after the CoreDesktop line overrides it.
## Networking
- **mDNS:** reach other machines on your LAN as `<name>.local`, and SSH to this one the same way. If systemd-resolved is enabled, the installer applies a fix so mDNS still works. For the details, see `fixmdns.sh` and the [Arch wiki](https://wiki.archlinux.org/title/Avahi#Hostname_resolution).
- **Tailscale:** installed and running. `configure core` joins it to your Core's network.
- **Printing:** CUPS is enabled.
## Firewall
ufw is on. Incoming connections are blocked, except:
- SSH (22)
- mDNS (5353/udp)
- LocalSend (53317)
- anything arriving over Tailscale
ufw-docker is installed too, because otherwise ports published by Docker containers get around ufw.
```
sudo ufw status # show the rules
sudo ufw allow <port>/tcp # open a port, e.g. for a dev server
sudo ufw-docker allow <container> <port> # let a container's port through
```
## Docker
Docker is installed and running, and you're in the `docker` group, which takes effect from your next login.
## GPU drivers
The installer adds the Vulkan driver for Intel and AMD GPUs. For NVIDIA GPUs, if archinstall's proprietary driver wasn't chosen, it installs NVIDIA's driver:
- the open kernel modules for GTX 16xx / RTX 20xx and newer
- the 580xx legacy driver for older cards, back to the GTX 900 series
## System defaults
- SSH connections notice a dropped link within a minute instead of hanging. Override this per host in `~/.ssh/config`.
- Logins allow 10 wrong passwords before locking the account for 2 minutes. Arch's default is 3 tries, then 10 minutes.
- Shutdown waits at most 10 seconds for a service that won't stop, instead of 90.
- If memory runs out, systemd-oomd closes the app causing it instead of freezing the whole desktop.
- Boot doesn't wait for the network.
- Commands run over SSH (`ssh <machine> <command>`) can find tools in `~/.local/bin` and mise.
## Backups
[Migrate](https://github.com/CyphrRiot/Migrate) is installed as `migrate`, and borg and Déjà Dup are available too.
-1
@@ -1 +0,0 @@
Welcome to the Wiki.
+96
@@ -0,0 +1,96 @@
# Troubleshooting
## Install and update
### The installer stopped part way
Run it again: each step skips what's already done. The usual causes are a sudo password timing out or a network hiccup.
### `update` says "Done, with errors in: …"
Scroll up to that step's output. Each step runs on its own, so the rest of the update still happened. Common ones:
| Step | Usual cause |
|---|---|
| `10-pacman.sh` | An Arch change that needs a manual step. Check the "Latest News" on <https://archlinux.org>. |
| `12-migrations.sh` | A one-time change failed. It's retried on every `update`, and later migrations wait for it. The output says which one failed. |
| `20-aur.sh` | An AUR package failed to build. It usually fixes itself when the package is updated. |
| `71-siblings.sh` | A sibling project couldn't pull, or its `update.sh` failed |
### "could not pull the latest CoreDesktop"
The CoreDesktop folder has local changes. `version` shows "modified" when this is the case. To see them:
```
git -C ~/.local/share/coredesktop status
```
Keep anything you need elsewhere, then discard the changes:
```
git -C ~/.local/share/coredesktop checkout -- .
```
### "Only N GiB free on /"
`update` needs 10 GiB free, because an upgrade that runs out of space can leave the system half-upgraded. Free up space, for example with `sudo paccache -rk1`. If you're sure, run `UPDATE_FORCE=1 update`.
### "Don't reboot yet: the initramfs failed to rebuild"
Rebooting now could leave the machine unbootable. Read the errors in `~/.local/state/coredesktop/pacman-update.log` and fix them first. If a reboot does fail, roll back to the snapshot taken before the update (see [Updating](Updating#snapshots-and-rolling-back)).
### A GNOME extension is missing or doesn't load
GNOME only loads new or updated extensions at login, so log out and back in first. If an extension still isn't there, check:
```
~/.local/bin/gext list
gnome-extensions info <extension-id>
```
If `gext` itself fails with `ModuleNotFoundError`, a Python upgrade broke it. `update` reinstalls it.
## Configure
### Tailscale: "failed to connect to local tailscaled"
The Tailscale service isn't running. `configure` starts it now. On older versions, start it yourself and run `configure core` again:
```
sudo systemctl enable --now tailscaled
```
### Nextcloud files
If `/mnt/<domain>` is empty, first check whether it's mounted:
```
mountpoint /mnt/<domain>
mount /mnt/<domain>
```
| Message | Fix |
|---|---|
| `user <you> must be member of group network` | `configure` added you to the `network` group, but it only takes effect at your next login. Log out and back in, or for now run `sudo -u $USER mount /mnt/<domain>`. |
| Asks for a password, or 401 errors | The app password was revoked or is wrong. Run `configure core` again. |
| Mounted, but empty | Check the WebDAV address in `/etc/fstab`. It should end with `/remote.php/dav/files/<your user>`. |
The folder is mounted when you log in to the desktop. If the network wasn't up yet, it won't be mounted; run `mount /mnt/<domain>`.
### Nextcloud says the password is wrong
`configure` checks your Core password against Nextcloud and gives you three tries. If you use two-factor sign-in, Nextcloud may refuse the password itself. In that case, create an app password in Nextcloud under **Settings > Security** and enter that instead.
## AI agents
### "Start <agent> from a project directory"
A sandboxed agent can change anything in the directory it starts in, so it refuses to start in your home folder or `/`. `cd` into a project first.
### An agent can't reach a site or write a folder
The sandbox blocked it. Add the domain or folder as shown in [Sandboxed AI Agents](Sandboxed-AI-Agents#allowing-more).
### "The sandbox (srt) isn't installed"
Run `update`. To use the agent without the sandbox in the meantime, run `unsandboxed <agent>`.
+90
@@ -0,0 +1,90 @@
# Update Hooks
`update` runs each `*.sh` file in `updates.d/` in filename order. To add a step to `update`, add a file there.
## How hooks run
For each hook, `update`:
1. Starts a subshell, so a `cd`, `set -e`, `exit` or stray variable can't affect other hooks.
2. Sources the file.
3. If the file defines `run_update`, calls it, but only if `is_installed` isn't defined or returns true.
4. If the subshell exits non-zero, adds the hook to "Done, with errors in: …" at the end. The remaining hooks still run.
`$COREDESKTOP` is set to `~/.local/share/coredesktop`. Nothing from earlier hooks is available, so source `utils.sh` if you need its helpers. Source it **before** defining your own `is_installed`, because `utils.sh` has a pacman-specific function with that name.
Sudo is already authenticated and kept alive for the whole run. Hooks shouldn't prompt for anything else.
## Two styles
Top-level code, for steps that always apply:
```bash
#!/bin/bash
COREDESKTOP="$HOME/.local/share/coredesktop"
source "$COREDESKTOP/utils.sh"
echo "Checking for new services..."
enable_services "${SERVICES[@]}"
```
Functions, for steps that only apply on some machines:
```bash
#!/bin/bash
is_installed() {
command -v fwupdmgr &>/dev/null
}
run_update() {
echo "Checking for firmware updates..."
...
return 0
}
```
Return non-zero from `run_update` to report a failure.
## Order
| Hook | Purpose |
|---|---|
| `09-snapshot.sh` | Snapper snapshot of `/` (btrfs only) |
| `10-pacman.sh` | Remove dropped packages, update keyring and Arch, install new packages |
| `12-migrations.sh` | One-time [migrations](Migrations) |
| `15-services.sh` | Enable new services |
| `16-update-notify.sh` | Update-notification timer |
| `20-aur.sh` | AUR packages; rebuilds yay if a pacman upgrade broke it |
| `30-flatpak.sh` | Flatpaks |
| `40-ruby.sh` | mise, Ruby and gems |
| `45-tmux.sh` | tmux plugins |
| `46-neovim.sh` | Neovim plugins |
| `50-bitwarden.sh` | Bitwarden SSH agent |
| `60-pipx.sh` | pipx apps; rebuilds them after a Python upgrade |
| `65-migrate.sh` | Migrate backup tool |
| `70-claude.sh` | Claude Code |
| `71-siblings.sh` | Sibling projects |
| `72-sandbox.sh` | Sandbox runtime and Hermes Agent |
| `80-gnome-extensions.sh` | GNOME extensions |
| `85-gnome-settings.sh` | GNOME extension settings |
| `90-firmware.sh` | Report firmware updates |
| `95-cleanup.sh` | Flatpak and pacman cache cleanup, orphaned packages |
| `99-reboot-check.sh` | Say whether a reboot is needed |
Pick a number that puts your hook after whatever it depends on. Anything that runs before `60-pipx.sh` can't rely on pipx apps working, and anything before `20-aur.sh` can't rely on yay.
## Testing
Run one hook the way `update` does:
```bash
( COREDESKTOP=~/.local/share/coredesktop
source updates.d/NN-name.sh
if declare -f run_update >/dev/null; then
! declare -f is_installed >/dev/null || is_installed && run_update
fi ); echo "exit: $?"
```
Run it twice to make sure the second run does nothing harmful.
+82
@@ -0,0 +1,82 @@
# Updating
## `update`
Run this at least once a month:
```
update
```
It pulls the latest CoreDesktop, then runs each step in `~/.local/share/coredesktop/updates.d/` in order. Among other things, it:
- stops if there's less than 10 GiB free on `/` (`UPDATE_FORCE=1 update` skips this check)
- takes a Snapper snapshot of `/` first, on btrfs
- updates the Arch keyring, then Arch and GNOME, AUR packages, Flatpaks, Ruby, pipx apps, GNOME extensions, sibling projects and the AI agents
- installs packages newly added to CoreDesktop, and removes ones it has dropped
- runs any one-time [migrations](Migrations) this machine hasn't had yet
- reports available firmware updates; it doesn't install them (run `fwupdmgr update` yourself)
- removes packages nothing needs any more, and lists them
- tells you if you should reboot, or warns you **not** to if the initramfs failed to rebuild
You type your password once; `update` keeps sudo alive until it's done. If a step fails, the rest still run, and the failures are listed at the end:
```
Done, with errors in: 20-aur.sh
```
To keep a package that `update` removes as unneeded, mark it as one you installed yourself:
```
sudo pacman -D --asexplicit <package>
```
If Arch itself won't update, check the "Latest News" on <https://archlinux.org>. It often explains a needed manual step.
## Update notifications
A few minutes after you log in, and every 4 hours after that, CoreDesktop checks for a new version. If there is one, a notification appears. Click it to run `update` in a terminal. If you dismiss it, you won't be notified again until there's an even newer version.
## `version`
```
$ version
CoreDesktop 2026.10.22 (c5f0651)
Kernel 7.2.11-arch1-1
Last update 2026-10-22 13:43
```
CoreDesktop is a rolling release, so its version is the date and id of the latest change on your machine. `version` also tells you:
- if a newer CoreDesktop is available
- if a newer kernel is waiting for a reboot
- if the CoreDesktop folder has local changes ("modified"), which can stop `update` from pulling
## `refresh`
`refresh` re-runs the full installer. `update` already installs new packages and runs migrations, so you rarely need `refresh`. Use it to repair an install, or to pick up something that only the installer sets up. Run `update` afterwards.
## Snapshots and rolling back
If `/` is btrfs (the archinstall default), Snapper snapshots `/` before every `update` and keeps the last 5. `/home` is a separate subvolume and isn't included, so rolling back never touches your files.
```
sudo snapper -c root list
```
These snapshots can't be chosen from the boot menu. To roll back after a bad update, boot the Arch ISO and replace the `@` subvolume with a snapshot:
```
mount -o subvolid=5 /dev/<root partition> /mnt
mv /mnt/@ /mnt/@.broken
btrfs subvolume snapshot /mnt/@.snapshots/<number>/snapshot /mnt/@
reboot
```
If the disk is encrypted, open it with `cryptsetup open` first and mount the opened device. Once the system works again, free the space the broken root used:
```
sudo mount -o subvolid=5 /dev/<root partition> /mnt
sudo btrfs subvolume delete /mnt/@.broken
sudo umount /mnt
```
+17
@@ -0,0 +1,17 @@
**Using CoreDesktop**
- [Home](Home)
- [Installing](Installing)
- [Configure](Configure)
- [Updating](Updating)
- [Software and Settings](Software-and-Settings)
- [Sandboxed AI Agents](Sandboxed-AI-Agents)
- [Troubleshooting](Troubleshooting)
**Working on CoreDesktop**
- [How CoreDesktop Works](How-CoreDesktop-Works)
- [Update Hooks](Update-Hooks)
- [Migrations](Migrations)
- [Configure Items](Configure-Items)
- [Sibling Projects](Sibling-Projects)