diff --git a/debian/README.md b/debian/README.md index 4b186ad..c716324 100644 --- a/debian/README.md +++ b/debian/README.md @@ -13,8 +13,8 @@ git clone https://tangled.sh/seth.computer/dotfiles ~/dotfiles ## Later runs -`setup.yml` is idempotent; re-run it anytime (also picks up new ghostty -releases, which have no apt repo). +`setup.yml` is idempotent; re-run it anytime (also picks up new ghostty and +obsidian releases, which have no apt repo). ```bash ansible-playbook ~/dotfiles/debian/ansible/setup.yml --ask-become-pass @@ -29,20 +29,36 @@ ansible-playbook ~/dotfiles/debian/ansible/update.yml --ask-become-pass ## What setup.yml does - apt packages: thunderbird, zsh, git, curl, gnupg, jq, xdg-utils -- apt repos + install: Helium (`helium-bin`), 1Password +- apt repos + install: Helium (`helium-bin`), 1Password (+ `op` CLI) - ghostty and obsidian: latest GitHub-release .debs ([mkasberg/ghostty-ubuntu](https://github.com/mkasberg/ghostty-ubuntu), [obsidianmd/obsidian-releases](https://github.com/obsidianmd/obsidian-releases)) -- Helium extensions (1Password, Vimium, Margin) via Chromium force-install policy +- Helium extensions (1Password, Vimium, Margin, Dark Reader) via Chromium + force-install policy - clones dotfiles to `~/dotfiles` and runs `sync.sh` -- login shell → zsh; default browser → Helium +- homebrew at `/home/linuxbrew/.linuxbrew`, then brewsync + (`brew bundle --zap --force-cleanup`; casks are skipped on Linux) +- `mise install` for all tools in the mise config +- caps lock mapped to ctrl; login shell → zsh; default browser → Helium - unattended security upgrades (daily, Debian's stock config) +- pi Umans login: API key pulled from 1Password (`umans-api-key` item, + `password` field) into `~/.pi/agent/auth.json` + +## Manual steps + +- Sign in to the 1Password desktop app, then enable **Integrate with + 1Password CLI** in its developer settings. Until then, setup skips the pi + Umans login (it prints a note); re-run setup afterwards. +- The `op` key fetch triggers a 1Password approval prompt — approve it while + setup runs. +- If the Helium extensions don't auto-install, that's the upstream + force-install bug — install them manually from the Chrome Web Store. ## Caveats - Helium extension force-installs are currently broken upstream ([helium#1737](https://github.com/imputnet/helium/issues/1737)). The policy - file is in place and correct; until the bug is fixed, install the three + file is in place and correct; until the bug is fixed, install the extensions manually from the Chrome Web Store. - Ghostty and Obsidian ship no apt repo on Debian, so `apt upgrade` will not update them. Re-run `setup.yml` to pick up new releases. diff --git a/debian/ansible/setup.yml b/debian/ansible/setup.yml index 5725bae..36cd1f2 100644 --- a/debian/ansible/setup.yml +++ b/debian/ansible/setup.yml @@ -88,8 +88,9 @@ - name: Install Helium and 1Password ansible.builtin.apt: name: - - helium-bin - 1password + - 1password-cli + - helium-bin state: present update_cache: true become: true @@ -109,6 +110,7 @@ content: | APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; + become: true - name: Look up latest ghostty and obsidian releases ansible.builtin.uri: @@ -155,7 +157,8 @@ "ExtensionInstallForcelist": [ "aeblfdkhhhdcdjpifhhbdiojplfdncoa;https://clients2.google.com/service/update2/crx", "dbepggeogbaibhgnhhndojpepiihcmeb;https://clients2.google.com/service/update2/crx", - "cgpmbiiagnehkikhcbnhiagfomajncpa;https://clients2.google.com/service/update2/crx" + "cgpmbiiagnehkikhcbnhiagfomajncpa;https://clients2.google.com/service/update2/crx", + "eimadpbcbfnmbopodijcomhbndbaanm;https://clients2.google.com/service/update2/crx" ] } become: true @@ -177,13 +180,83 @@ 'backing it up' in sync.stdout or 'Cloning into' in sync.stderr + # Homebrew: prefix created as root, clone and bundle as the user (the + # official installer refuses to run as root, and this avoids its + # interactive sudo prompts). + - name: Create homebrew directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ ansible_user_id }}" + mode: "0755" + loop: + - /home/linuxbrew + - /home/linuxbrew/.linuxbrew + become: true + + - name: Check for homebrew + ansible.builtin.stat: + path: /home/linuxbrew/.linuxbrew/bin/brew + register: brew_bin + + - name: Clone homebrew + ansible.builtin.git: + repo: https://github.com/Homebrew/brew + dest: /home/linuxbrew/.linuxbrew + when: not brew_bin.stat.exists + + # brewsync: cask entries are skipped automatically on Linux + - name: Sync homebrew packages + ansible.builtin.command: brew bundle --zap --force-cleanup --file Brewfile + args: + chdir: "{{ dotfiles_dir }}" + environment: + PATH: "/home/linuxbrew/.linuxbrew/bin:{{ ansible_env.PATH }}" + register: brewsync + changed_when: >- + 'Installing ' in (brewsync.stdout ~ brewsync.stderr) or + 'Uninstalling ' in (brewsync.stdout ~ brewsync.stderr) + + - name: Install mise tools + ansible.builtin.command: mise install -y + environment: + PATH: "/home/linuxbrew/.linuxbrew/bin:{{ ansible_env.PATH }}" + register: mise_install + changed_when: >- + 'installed 0 tools' not in + (mise_install.stdout ~ mise_install.stderr) + - name: Set login shell to zsh ansible.builtin.user: name: "{{ ansible_user_id }}" shell: /usr/bin/zsh become: true - - name: Check current default browser + - name: Read keyboard xkb options + ansible.builtin.command: + argv: + - gsettings + - get + - org.gnome.desktop.input-sources + - xkb-options + register: xkb_options + changed_when: false + become: false + + - name: Map caps lock to ctrl + ansible.builtin.shell: | + schema=org.gnome.desktop.input-sources + current=$(gsettings get $schema xkb-options) + case "$current" in *ctrl:nocaps*) exit 0 ;; esac + if [ "$current" = "@as []" ] || [ "$current" = "[]" ]; then + gsettings set $schema xkb-options "['ctrl:nocaps']" + else + gsettings set $schema xkb-options "${current%]}, 'ctrl:nocaps']" + fi + when: "'ctrl:nocaps' not in xkb_options.stdout" + become: false + + - name: Read current default browser ansible.builtin.command: gio mime x-scheme-handler/https register: default_browser changed_when: false @@ -194,4 +267,68 @@ loop: - x-scheme-handler/http - x-scheme-handler/https - when: "'helium.desktop' not in (default_browser.stdout | default(''))" + changed_when: >- + 'helium.desktop' not in + (default_browser.stdout | default('')) + + - name: Set default web browser (xdg) + ansible.builtin.command: + argv: + - xdg-settings + - set + - default-web-browser + - helium.desktop + changed_when: >- + 'helium.desktop' not in + (default_browser.stdout | default('')) + failed_when: false + + - name: Verify Helium is the default browser + ansible.builtin.command: gio mime x-scheme-handler/https + register: default_browser_check + changed_when: false + failed_when: "'helium.desktop' not in default_browser_check.stdout" + + # pi Umans login: pull the API key from 1Password and store it where pi + # reads it (~/.pi/agent/auth.json, same shape /login writes). + - name: Fetch Umans API key from 1Password + ansible.builtin.command: op item get umans-api-key --fields password + register: op_umans + changed_when: false + failed_when: false + no_log: true + + - name: Configure pi Umans login + ansible.builtin.shell: | + set -euo pipefail + key=$(op item get umans-api-key --fields password) + auth="$HOME/.pi/agent/auth.json" + mkdir -p "$(dirname "$auth")" + current=$(jq -r '.umans.access // empty' "$auth" 2>/dev/null || true) + if [ "$current" = "$key" ]; then + echo "already configured" + exit 0 + fi + never=8640000000000000 + cred='{type: "oauth", refresh: $key, access: $key, expires: $exp}' + tmp=$(mktemp) + if [ -f "$auth" ]; then + jq --arg key "$key" --argjson exp $never \ + ".umans = $cred" "$auth" > "$tmp" + else + jq -n --arg key "$key" --argjson exp $never \ + "{umans: $cred}" > "$tmp" + fi + chmod 600 "$tmp" + mv "$tmp" "$auth" + register: umans_login + changed_when: "'already configured' not in umans_login.stdout" + when: op_umans.rc == 0 + no_log: true + + - name: Note skipped pi Umans login + ansible.builtin.debug: + msg: >- + op CLI not available (sign in to 1Password and enable "Integrate + with 1Password CLI", then re-run setup) — pi Umans login skipped. + when: op_umans.rc != 0