Compare commits

...

75 commits

Author SHA1 Message Date
f0d9d8891a
Install the Kingfisher service image for secret detection 2026-01-15 22:25:16 +02:00
c5d3f37222
Disable the testcontainers reaper service by default 2026-01-15 22:25:15 +02:00
5c701586d8
Create a script to clean up dangling buildah containers 2026-01-15 22:25:11 +02:00
bf92740823
Install agentbox from the live git repo 2026-01-12 22:15:52 +02:00
7e6272540d
Create a script for listing and selecting urgent windows in sway 2026-01-10 00:02:25 +02:00
39250fd4f2
Give visual cues on terminal bell 2026-01-08 00:31:44 +02:00
d1e206805b
Replace pipx with uv for installing Python-based tools 2026-01-07 23:22:14 +02:00
a37fa4f0d7
Integrate Emacs package updates into the system upgrade script 2026-01-03 23:09:38 +02:00
ce00fe50dc
Install docker type stubs in the lsp venv 2026-01-03 22:39:57 +02:00
ff41e71ed5
Ensure mounted directories exist 2026-01-03 22:39:56 +02:00
3d40752a91
Clean images after updates 2026-01-03 22:39:56 +02:00
c0740c39f8
Enable auto updates for the service containers 2025-12-30 00:26:35 +02:00
93f0d00179
Configure testcontainers to use the podman socket 2025-12-29 09:38:20 +02:00
96568a1103
Install testcontainers in the lsp venv for type inference 2025-12-29 09:38:16 +02:00
331954c204
Install a GPU monitor utility 2025-12-26 17:47:56 +02:00
69cbc19188
All things deserve to be known by their true name 2025-12-26 17:47:21 +02:00
f6544f6dd7
Rely on podman's built-in update mechanism 2025-12-26 00:23:44 +02:00
db05998d6c
Parse flag-like inputs as literal names 2025-12-26 00:08:20 +02:00
6a06a995b0
Add input validation - the script only supports editing a single file 2025-12-26 00:08:20 +02:00
888434d5d9
Write a run0-based alternative to sudoedit 2025-12-26 00:03:57 +02:00
5716e68abd
Increase the cache TTL for loaded models 2025-12-24 22:28:32 +02:00
6f3c6f0409
Transparently use run0 instead of sudo 2025-12-24 22:18:18 +02:00
5ca5d6386d
Replace sudo with run0 in all scripts 2025-12-24 22:17:47 +02:00
30aa59ddfd
Replace sudo with run0 in scripts that only use it once 2025-12-24 20:34:34 +02:00
e7ff3d3f16
Run service containers in the transient store for speed and safety 2025-12-24 20:30:24 +02:00
9ee480b959
Use a read-only root filesystem in service containers 2025-12-24 20:30:24 +02:00
0df3008fa0
Collect all the flyspell configuration options together 2025-12-24 20:30:24 +02:00
9d2e483917
Detach the Transmission server from the host network 2025-12-24 20:30:23 +02:00
4cba3af502
Integrate PlantUML mode with org-mode code blocks 2025-12-24 20:30:23 +02:00
deb1bfd574
Setup PlantUML mode in Emacs to use the local server 2025-12-24 20:30:19 +02:00
5f99d941ab
Start a local PlantUML server on boot 2025-12-20 17:09:49 +02:00
82cd587d18
Use long option names 2025-12-20 17:09:49 +02:00
38bf705a3c
Update binary name 2025-12-19 15:58:26 +02:00
de56d1a1f2
Enable tree-sitter-integrated modes 2025-12-19 15:58:25 +02:00
7e4ddb6dab
Use conventional commenting style for Emacs Lisp 2025-12-13 23:58:49 +02:00
b16f6c410e
Pre-download container images 2025-12-07 00:26:11 +02:00
8c3126e47e
Keep container images up-to-date 2025-12-07 00:26:11 +02:00
fdb2da7942
Alias docker->podman to support container-based pre-commit hooks 2025-12-07 00:26:10 +02:00
ec5ed3cf1f
Update pre-commit hooks 2025-12-04 22:54:14 +02:00
924e8c6551
Include Go binaries in $PATH 2025-12-04 22:54:14 +02:00
9432164bc3
Add capabilities to the model library 2025-12-03 19:17:35 +02:00
1b7fb23a2e
Automatically update the list of Ollama models 2025-12-03 18:35:35 +02:00
7900661c82
Add Gemma 3 to the model library 2025-12-03 18:35:34 +02:00
883968b8aa
Fix model listing logic 2025-12-03 18:35:34 +02:00
e7bd7fc24f
Enable spellchecking in Emacs buffers 2025-12-02 22:59:14 +02:00
3a951298a6
Correctly set company mode 2025-12-02 22:59:13 +02:00
43ccc833f6
Run the transmission client inside a contained service 2025-12-02 22:59:13 +02:00
e957ac480b
Make the default system prompt more fun 2025-12-02 22:59:12 +02:00
1756c8c802
Switch to a more powerful default model 2025-11-19 01:04:48 +02:00
3de4e0ab1e
Add Devstral to the model library 2025-11-19 01:04:48 +02:00
91c52deda9
Share the local model library into the vault container 2025-11-19 01:04:47 +02:00
55d5c907e2
Give the vault container access to the Ollama server 2025-11-18 08:11:26 +02:00
fd645f60a2
Retrieve model list directly from the Ollama server 2025-11-18 08:11:26 +02:00
90c310302d
Allow sharing configurations between host and containers 2025-11-18 08:11:25 +02:00
06d2a14d09
Add an Emacs Lisp lexical binding cookie 2025-11-18 08:11:21 +02:00
ed44c92fc9
Rely on pipx's built-in upgrade capabilities 2025-11-17 22:35:49 +02:00
d187c19d71
Enrich the Ollama models list with metadata for known models 2025-11-17 22:35:49 +02:00
7998f20d52
Compose a library of known local models 2025-11-17 22:35:49 +02:00
a019feb7cd
Reenable autoformatting for Emacs Lisp 2025-11-17 00:18:08 +02:00
131511a2f7
Use conventional commenting style for Emacs Lisp 2025-11-17 00:18:07 +02:00
59583b296a
Support restricted one-off uses of language models 2025-11-17 00:18:06 +02:00
f533b3ef76
Start the Ollama server automatically on boot 2025-11-17 00:18:05 +02:00
89d667542a
Display advanced options in the gptel menu 2025-11-17 00:18:04 +02:00
7699aa4084
Query the Ollama server for the list of installed models 2025-11-17 00:18:03 +02:00
75ed82967c
Isolate the LLM server from the internet 2025-11-15 17:00:24 +02:00
e5087f5023
Revoke unnecessary filesystem permissions for the server 2025-11-15 17:00:23 +02:00
f8b80bd60d
Create a dedicated script to manage local models 2025-11-15 17:00:23 +02:00
8a29d8da40
Keep the models in a persistent volume 2025-11-12 17:45:04 +02:00
bd5cfa7a65
Personalize chat UX more 2025-11-12 17:30:15 +02:00
304710810c
Make Ollama the default backend for gptel 2025-11-12 17:29:28 +02:00
be95a1eb42
Allow gptel to use the Ollama server 2025-11-12 11:48:32 +02:00
98dc827a10
Wrap long lines in chat interaction for convenience 2025-11-12 11:48:08 +02:00
9147292b66
Synchronize branch and tag metadata by default 2025-11-12 11:47:40 +02:00
40f24304ea
Declare a quadlet file for an Ollama server 2025-11-12 11:42:22 +02:00
b010d90306
Use MPV as the default player for OGG/Opus audio files 2025-11-12 11:39:46 +02:00
38 changed files with 615 additions and 138 deletions

View file

@ -1,7 +1,8 @@
#! /usr/bin/bash
alias em="emacs --no-init-file --no-splash --no-window-system"
alias ll="exa --binary --group --long"
alias ll="eza --binary --group --long"
alias la="ll --all"
alias tree="ll --tree"
alias llblk="lsblk -o NAME,TYPE,FSTYPE,SIZE,MOUNTPOINT,LABEL,PARTLABEL,UUID,PARTUUID"
alias llblk="lsblk --output NAME,TYPE,FSTYPE,SIZE,MOUNTPOINT,LABEL,PARTLABEL,UUID,PARTUUID"
alias sudo="run0"

View file

@ -0,0 +1,24 @@
[Unit]
Description=A local LLM server
[Container]
# keep-sorted start
AutoUpdate=registry
ContainerName=ollama
Environment=OLLAMA_KEEP_ALIVE=10m
Image=docker.io/ollama/ollama:latest
Network=ollama.network
PodmanArgs=--transient-store
PublishPort=11434:11434
ReadOnly=true
Volume=%h/.local/share/ollama:/root/.ollama:ro,z
# keep-sorted end
[Install]
WantedBy=default.target
[Service]
# keep-sorted start
ExecStartPre=mkdir --parents %h/.local/share/ollama
Restart=always
# keep-sorted end

View file

@ -0,0 +1,5 @@
[Unit]
Description=Isolated network for my local LLM server
[Network]
Internal=true

View file

@ -0,0 +1,19 @@
[Unit]
Description=A local PlantUML server
[Container]
# keep-sorted start
AutoUpdate=registry
ContainerName=plantuml
Image=docker.io/plantuml/plantuml-server:jetty
Network=private
PodmanArgs=--transient-store
PublishPort=8080:8080
ReadOnly=true
# keep-sorted end
[Install]
WantedBy=default.target
[Service]
Restart=always

View file

@ -0,0 +1,34 @@
[Unit]
Description=Transmission client service with web interface on localhost:9091
[Container]
# keep-sorted start
AutoUpdate=registry
ContainerName=transmission
Environment=PGID=1000
Environment=PUID=1000
Image=lscr.io/linuxserver/transmission:latest
Network=private
PodmanArgs=--transient-store
PublishPort=51413:51413
PublishPort=51413:51413/udp
PublishPort=9091:9091
ReadOnly=true
UserNS=keep-id
Volume=%h/.config/transmission:/config:Z
Volume=%h/Downloads/transmission/watch:/watch:ro,Z
Volume=%h/Downloads/transmission:/downloads:Z
# keep-sorted end
[Install]
WantedBy=default.target
[Service]
# keep-sorted start
ExecStartPre=mkdir --parents %h/.config/transmission
ExecStartPre=mkdir --parents %h/Downloads/transmission
ExecStartPre=mkdir --parents %h/Downloads/transmission/complete
ExecStartPre=mkdir --parents %h/Downloads/transmission/incomplete
ExecStartPre=mkdir --parents %h/Downloads/transmission/watch
Restart=always
# keep-sorted end

View file

@ -23,8 +23,12 @@
:bind (("C-z" . nil)
("C-z i" . find-init-file)
("C-z f" . ffap))
:hook ((after-save . executable-make-buffer-file-executable-if-script-p)
(xref-after-update . outline-minor-mode))
:hook (
;; keep-sorted start
(after-save . executable-make-buffer-file-executable-if-script-p)
(xref-after-update . outline-minor-mode)
;; keep-sorted end
)
:init
(setq load-path (append (list
(expand-file-name "~/.config/emacs/site-lisp/")
@ -32,7 +36,7 @@
)
load-path))
:config
; keep-sorted start
;; keep-sorted start
(defalias 'yes-or-no-p 'y-or-n-p)
(display-battery-mode)
(display-time-mode)
@ -42,9 +46,9 @@
(put 'dired-find-alternate-file 'disabled nil)
(set-default-file-modes #o750)
(windmove-default-keybindings 'super)
; keep-sorted end
;; keep-sorted end
:custom
; keep-sorted start
;; keep-sorted start
(auto-save-interval 20)
(auto-save-visited-mode t)
(auto-save-visited-predicate #'should-auto-save-current-buffer)
@ -56,8 +60,21 @@
(global-auto-revert-non-file-buffers t)
(inhibit-startup-screen t)
(show-paren-context-when-offscreen 'overlay)
(treesit-enabled-modes t)
(xref-search-program 'ripgrep)
; keep-sorted end
;; keep-sorted end
)
(use-package flyspell
:ensure nil
:bind (:map flyspell-mode-map
("C-;" . nil))
:hook (
;; keep-sorted start
(prog-mode . flyspell-prog-mode)
(text-mode . flyspell-mode)
;; keep-sorted end
)
)
(use-package diminish)
@ -75,10 +92,10 @@
("<C-tab>" . dired-subtree-cycle)
("<S-iso-lefttab>" . dired-subtree-remove))
:config
; keep-sorted start
;; keep-sorted start
(defadvice dired-subtree-cycle (after add-icons activate) (revert-buffer))
(defadvice dired-subtree-toggle (after add-icons activate) (revert-buffer))
; keep-sorted end
;; keep-sorted end
:custom
(dired-subtree-use-backgrounds nil))
@ -157,7 +174,7 @@
("M-." . org-open-at-point)
("M-," . org-mark-ring-goto))
:custom
; keep-sorted start block=yes
;; keep-sorted start block=yes
(org-agenda-start-on-weekday 0)
(org-agenda-weekend-days '(5 6))
(org-default-notes-file "~/Documents/notes.org")
@ -172,13 +189,29 @@
"** TODO %?\n %U")
))
(org-clock-sound "~/Music/single-ding.wav")
; keep-sorted end
;; keep-sorted end
)
(use-package org-contrib)
(use-package org-contacts
:after org-contrib)
(use-package deflate
:ensure (:repo "https://github.com/skuro/deflate"))
(use-package plantuml-mode
:ensure (:repo "https://github.com/skuro/plantuml-mode")
:custom
;; keep-sorted start
(plantuml-default-exec-mode 'server)
(plantuml-output-type "svg")
(plantuml-server-url "http://localhost:8080")
;; keep-sorted end
:init
(add-to-list 'auto-mode-alist '("\\.puml\\'" . plantuml-mode))
(add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
(org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t)))
)
(use-package age
:custom
(age-default-identity "~/.age/key")
@ -196,7 +229,7 @@
)
(use-package company
:init (global-company-mode)
:config (global-company-mode)
:diminish company-mode)
(use-package apt-mode
@ -217,7 +250,6 @@
(use-package apheleia
:config
(apheleia-global-mode)
(setf (alist-get 'emacs-lisp-mode apheleia-mode-alist nil 'remove) nil)
(setf (alist-get 'ruff-isort apheleia-formatters)
'("ruff" "check"
"-n"
@ -236,18 +268,18 @@
(use-package flymake
:ensure nil
:bind (:map flymake-mode-map
("C-c C-l" . flymake-show-buffer-diagnostics)
("C-x C-l" . flymake-show-project-diagnostics)
("C-c C-n" . flymake-goto-next-error)
("C-c C-p" . flymake-goto-prev-error))
("C-c C-l" . flymake-show-buffer-diagnostics)
("C-x C-l" . flymake-show-project-diagnostics)
("C-c C-n" . flymake-goto-next-error)
("C-c C-p" . flymake-goto-prev-error))
:custom
(flymake-show-diagnostics-at-end-of-line t)
)
; Note: debugging Python in a virtualenv requires debugpy to be installed inside the venv
;; Note: debugging Python in a virtualenv requires debugpy to be installed inside the venv
(use-package dape)
; Requires poetry to be installed
;; Requires poetry to be installed
(use-package poetry)
(defun load-python-env ()
@ -259,7 +291,7 @@
(eglot-ensure))))
(use-package python
:bind (:map python-mode-map
:bind (:map python-ts-mode-map
("C-c C-p" . nil)
("C-c C-l" . nil)
("C-c t" . elpy-test))
@ -278,10 +310,10 @@
:config
(require 'ein-notebook)
:custom
; keep-sorted start
;; keep-sorted start
(ein:jupyter-default-notebook-directory "~/Projects/notebooks")
(ein:output-area-inlined-images t)
; keep-sorted end
;; keep-sorted end
)
(use-package direnv
@ -306,18 +338,52 @@
auto-mode-alist))
)
(defun list-ollama-models ()
"Query the local Ollama server for the list of installed models."
(let* ((tags-buffer (url-retrieve-synchronously "http://ollama:11434/api/tags"))
(raw-response (with-current-buffer tags-buffer (buffer-string)))
(tags-payload (nth 1 (split-string raw-response "\n\n")))
(models (gethash "models" (json-parse-string tags-payload)))
(model-names (mapcar (lambda (model) (gethash "name" model)) models)))
(mapcar #'intern model-names)))
(defun enrich-ollama-models (available library)
"Enrich the available models with metadata from the library of known models."
(mapcar
(lambda (model)
(seq-find (lambda (x) (eq (car x) model)) library model))
available))
(defun generate-ollama-declaration ()
(gptel-make-ollama "Ollama"
:host "ollama:11434"
:stream t
:models (enrich-ollama-models (list-ollama-models) gptel--local-models)))
(use-package gptel
:hook
;; keep-sorted start
(gptel-mode . gptel-highlight-mode)
(gptel-mode . visual-line-mode)
(gptel-post-response . gptel-end-of-response)
(gptel-post-stream . gptel-auto-scroll)
;; keep-sorted end
:custom
; keep-sorted start
(gptel-backend (gptel-get-backend "Claude"))
;; keep-sorted start
(gptel--system-message "You are a sassy, sharp-tongued personal assistant. I need you to assist me in crafting responses to questions, dripping with acerbic wit and sarcasm.")
(gptel-backend (gptel-get-backend "Ollama"))
(gptel-default-mode 'org-mode)
(gptel-model 'claude-3-5-haiku-20241022)
; keep-sorted end
(gptel-expert-commands t)
(gptel-highlight-methods '(face margin))
(gptel-model 'hf.co/unsloth/Devstral-Small-2507-GGUF:latest)
;; keep-sorted end
:preface
(load "local-models.el")
(gptel-make-anthropic "Claude"
:stream t
:key 'gptel-api-key-from-auth-source
)
(generate-ollama-declaration)
)
(use-package power-mode)
@ -327,10 +393,10 @@
(require 'emms-setup)
(emms-all)
:custom
; keep-sorted start
;; keep-sorted start
(emms-info-functions '(emms-info-native))
(emms-player-list '(emms-player-mpv))
; keep-sorted end
;; keep-sorted end
)
(defun my-qr-selection ()

View file

@ -0,0 +1,55 @@
;; -*- lexical-binding: t; -*-
(defconst gptel--local-models
'(
;; keep-sorted start
(
gemma3:latest
:description "A model from Google built on Gemini technology"
:capabilities (media tool-use cache)
:mime-types ("image/bmp" "image/gif" "image/jpeg" "image/png" "image/tiff" "image/webp")
:context-window 128
:cutoff-date "2024-08"
)
(
hf.co/Orenguteng/Llama-3.1-8B-Lexi-Uncensored-V2-GGUF:latest
:description "Uncensored model based on Llama-3.1-8b-Instruct"
:capabilities (tool-use cache)
:context-window 128
:cutoff-date "2023-12"
)
(
hf.co/TheBloke/MythoMax-L2-13B-GGUF:latest
:description "Proficient at both roleplaying and storywriting"
:context-window 32
)
(
hf.co/bartowski/cognitivecomputations_Dolphin-Mistral-24B-Venice-Edition-GGUF:latest
:description "Uncensored version of Mistral 24B"
:context-window 32
:cutoff-date "2023-10"
)
(
hf.co/unsloth/Devstral-Small-2507-GGUF:latest
:capabilities (json tool-use cache)
:description "Agentic LLM for software engineering tasks"
:context-window 128
)
(
llama3.2:latest
:description "Instruction-tuned model optimized for multilingual dialogue"
:capabilities (tool-use cache)
:context-window 128
:cutoff-date "2023-12"
)
(
mollysama/rwkv-7-g0a3:13.3b
:description "Pure RNN reasoning model, suitable for post-training and fine-tuning"
:context-window 1000
:cutoff-date "2023-10"
)
;; keep-sorted end
)
"List of known local models and associated properties.
Refer to https://gptel.org/manual.html#models for a description of supported properties"
)

View file

@ -1,9 +1,10 @@
(
; keep-sorted start
;; keep-sorted start
(magit-commit "--gpg-sign=")
(magit-diff:magit-revision-mode "--no-ext-diff" "--stat" "--show-signature")
(magit-fetch "--force" "--prune" "--tags")
(magit-log:magit-log-mode "-n256" "--graph" "--decorate" "--show-signature")
(magit-merge "--gpg-sign=")
(magit-tag "--sign")
; keep-sorted end
;; keep-sorted end
)

View file

@ -0,0 +1,3 @@
# keep-sorted start
HOSTALIASES=~/.config/hosts
# keep-sorted end

View file

@ -0,0 +1,4 @@
# keep-sorted start
DOCKER_HOST=unix:///run/user/1000/podman/podman.sock
TESTCONTAINERS_RYUK_DISABLED=true
# keep-sorted end

View file

@ -40,9 +40,9 @@
force_color_prompt=true
[bell]
# urgent=no
urgent=yes
# notify=no
# visual=no
visual=yes
# command=
# command-focused=no

1
.config/hosts Normal file
View file

@ -0,0 +1 @@
ollama localhost

View file

@ -10,6 +10,8 @@ application/xhtml+xml=userapp-Firefox-RJEWT1.desktop
application/x-extension-xhtml=userapp-Firefox-RJEWT1.desktop
application/x-extension-xht=userapp-Firefox-RJEWT1.desktop
x-scheme-handler/magnet=userapp-transmission-gtk-BAUQU2.desktop
audio/ogg=mpv.desktop;
audio/x-opus+ogg=mpv.desktop;
[Added Associations]
x-scheme-handler/http=userapp-Firefox-RJEWT1.desktop;

View file

@ -3,8 +3,8 @@
set -euo pipefail
IFS=$'\n\t'
sudo cp "$(systemd-path user-configuration)"/setup/sources.list.d/debian.sources /etc/apt/sources.list.d/
sudo rm /etc/apt/sources.list
run0 cp "$(systemd-path user-configuration)"/setup/sources.list.d/debian.sources /etc/apt/sources.list.d/
run0 rm /etc/apt/sources.list
sudo apt update
sudo apt --yes full-upgrade
run0 apt update
run0 apt --yes full-upgrade

View file

@ -16,6 +16,7 @@ DEB_PKGS=(
build-essential
cargo
cargo-doc
catatonit
curl
direnv
emacs-mozc
@ -58,8 +59,8 @@ DEB_PKGS=(
network-manager-openvpn
nmap
ntfs-3g
nvtop
pipewire-audio
pipx
postfix
pre-commit
pulseaudio-utils
@ -83,7 +84,6 @@ DEB_PKGS=(
syncthing
thunderbolt-tools
timidity
transmission
udisks2
unar
unrar-free
@ -106,5 +106,6 @@ META_PKGS=(
# keep-sorted end
)
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes "${DEB_PKGS[@]}"
sudo DEBIAN_FRONTEND=noninteractive apt-get install --install-recommends --yes "${META_PKGS[@]}"
run0 --setenv=DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes "${DEB_PKGS[@]}"
run0 --setenv=DEBIAN_FRONTEND=noninteractive apt-get install --install-recommends --yes "${META_PKGS[@]}"
run0 --setenv=DEBIAN_FRONTEND=noninteractive apt-get remove --yes sudo

View file

@ -1,30 +0,0 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
PIPX_PKGS=(
# keep-sorted start
python-lsp-server
rshell
ruff
# keep-sorted end
)
if (( "${#PIPX_PKGS[@]}" != 0 )); then
pipx install "${PIPX_PKGS[@]}"
fi
PYLSP_PLUGINS=(
# keep-sorted start
fs
podman
pydantic
pylsp-mypy
pylsp-rope
pytest
types-pyxdg
# keep-sorted end
)
pipx inject python-lsp-server "${PYLSP_PLUGINS[@]}"

View file

@ -19,6 +19,6 @@ mkdir --parents ~/Pictures/screenshots
pre-commit install
sudo usermod --append --groups dialout "${USER}"
run0 usermod --append --groups dialout "${USER}"
echo 'kernel.perf_event_paranoid=1' | sudo tee '/etc/sysctl.d/51-enable-perf-events.conf'
echo 'kernel.perf_event_paranoid=1' | run0 tee '/etc/sysctl.d/51-enable-perf-events.conf'

View file

@ -3,12 +3,12 @@
set -euo pipefail
IFS=$'\n\t'
sudo cp "$(systemd-path user-configuration)"/setup/sources.list.d/tor.sources /etc/apt/sources.list.d/
sudo chmod 644 /etc/apt/sources.list.d/tor.sources
run0 cp "$(systemd-path user-configuration)"/setup/sources.list.d/tor.sources /etc/apt/sources.list.d/
run0 chmod 644 /etc/apt/sources.list.d/tor.sources
outfile=/usr/share/keyrings/deb.torproject.org-keyring.pgp
curl --silent https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | sudo tee "${outfile}" >/dev/null
sudo chmod 644 "${outfile}"
curl --silent https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | run0 tee "${outfile}" >/dev/null
run0 chmod 644 "${outfile}"
TOR_PKGS=(
# keep-sorted start
@ -18,5 +18,5 @@ TOR_PKGS=(
# keep-sorted end
)
sudo apt update
sudo apt install --no-install-recommends --yes "${TOR_PKGS[@]}"
run0 apt update
run0 apt install --no-install-recommends --yes "${TOR_PKGS[@]}"

View file

@ -0,0 +1,6 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
resync-live-git-repo-packages

View file

@ -4,4 +4,4 @@ set -euo pipefail
IFS=$'\n\t'
curl --location --output-dir /tmp --remote-name https://zoom.us/client/latest/zoom_amd64.deb
sudo apt-get install --no-install-recommends --yes /tmp/zoom_amd64.deb
run0 apt-get install --no-install-recommends --yes /tmp/zoom_amd64.deb

View file

@ -3,8 +3,8 @@
set -euo pipefail
IFS=$'\n\t'
sudo mkdir --parents "${BORG_REPO}"
sudo chown "${USER}":"${USER}" "${BORG_REPO}"
sudo chmod 700 "${BORG_REPO}"
run0 mkdir --parents "${BORG_REPO}"
run0 chown "${USER}":"${USER}" "${BORG_REPO}"
run0 chmod 700 "${BORG_REPO}"
borg init --encryption=repokey

View file

@ -0,0 +1,16 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
IMAGES=(
# keep-sorted start
docker.io/ollama/ollama:latest
docker.io/plantuml/plantuml-server:jetty
ghcr.io/hadolint/hadolint:latest
ghcr.io/mongodb/kingfisher:latest
lscr.io/linuxserver/transmission:latest
# keep-sorted end
)
podman pull "${IMAGES[@]}"

View file

@ -0,0 +1,30 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
UV_PKGS=(
# keep-sorted start
rshell
ruff
# keep-sorted end
)
for package in "${UV_PKGS[@]}"; do
uv tool install "${package}"
done
PYLSP_PLUGINS=(
# keep-sorted start
--with fs
--with podman
--with pydantic
--with pylsp-mypy
--with pylsp-rope
--with pytest
--with testcontainers
--with types-docker
--with types-pyxdg
# keep-sorted end
)
uv tool install "${PYLSP_PLUGINS[@]}" python-lsp-server

View file

@ -32,15 +32,15 @@ include /etc/sway/config-vars.d/*
#
# output HDMI-A-1 resolution 1920x1080 position 1920,0
#
# You can get the names of your outputs by running: swaymsg -t get_outputs
# You can get the names of your outputs by running: swaymsg --type get_outputs
### Idle configuration
#
exec swayidle -w \
timeout 300 'swaylock -f -c 000000' \
timeout 300 'swaylock --daemonize --color 000000' \
timeout 600 'swaymsg "output * dpms off"' resume 'swaymsg "output * dpms on"' \
before-sleep 'swaylock -f -c 000000'
before-sleep 'swaylock --daemonize --color 000000'
# This will lock your screen after 300 seconds of inactivity, then turn off
# your displays after another 300 seconds, and turn your screens back on when
@ -58,7 +58,7 @@ input type:touchpad {
tap enabled
}
# You can get the names of your inputs by running: swaymsg -t get_inputs
# You can get the names of your inputs by running: swaymsg --type get_inputs
# Read `man 5 sway-input` for more information about this section.
### Key bindings
@ -99,9 +99,9 @@ input type:touchpad {
# Lock screen
# Black
bindsym $wm_mod+l exec swaylock -f -c 000000
bindsym $wm_mod+l exec swaylock --daemonize --color 000000
# Random color
bindsym $wm_mod+semicolon exec swaylock -f -c $(dd if=/dev/urandom bs=1 count=3 2>/dev/null | hexdump -e '"%02x"')
bindsym $wm_mod+semicolon exec swaylock --daemonize --color $(dd if=/dev/urandom bs=1 count=3 2>/dev/null | hexdump --format '"%02x"')
# Exit sway (logs you out of your Wayland session)
bindsym $wm_mod+Delete exec leave
@ -188,6 +188,9 @@ input type:touchpad {
# Move focus to the parent container
bindsym $wm_mod+bracketleft focus parent
bindsym $wm_mod+bracketright focus child
# Select urgent window to move focus to
bindsym $wm_mod+backslash exec select-urgent
#
# Scratchpad:
#

11
.local/bin/clear-buildah Executable file
View file

@ -0,0 +1,11 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
buildah_containers="$(buildah list | tail --lines +2 | cut --delimiter ' ' --fields 1)"
if [ -z "${buildah_containers}" ]; then
echo "No containers to remove"
else
buildah rm "${buildah_containers}"
fi

View file

@ -3,4 +3,4 @@
set -euo pipefail
IFS=$'\n\t'
resync-git-sync
podman "${@:1}"

View file

@ -3,6 +3,6 @@
set -euo pipefail
IFS=$'\n\t'
sudo sysctl net.ipv6.conf.all.disable_ipv6=1
run0 sysctl net.ipv6.conf.all.disable_ipv6=1
"$@"
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
run0 sysctl net.ipv6.conf.all.disable_ipv6=0

57
.local/bin/podllama Executable file
View file

@ -0,0 +1,57 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
if ! PARSED_OPTIONS=$(getopt --options "" --long "offline,volatile" --name "$0" -- "$@"); then
echo "Error parsing options." >&2
exit 1
fi
eval set -- "$PARSED_OPTIONS"
offline=false
volatile=false
while true; do
case "$1" in
--offline)
offline=true
shift
;;
--volatile)
volatile=true
shift
;;
--)
shift
break
;;
*)
echo "Internal error!" >&2
exit 1
;;
esac
done
mkdir --parents ~/.local/share/ollama/
if [[ "true" == "${volatile}" ]]; then
PODMAN=(podman --transient-store)
MOUNTS=()
else
PODMAN=(podman)
MOUNTS=(--volume ~/.local/share/ollama/:/root/.ollama)
fi
if [[ "true" == "${offline}" ]]; then
NETWORK=(--network none)
else
NETWORK=()
fi
pod_id=$("${PODMAN[@]}" run --detach --rm "${MOUNTS[@]}" "${NETWORK[@]}" ollama:latest)
"${PODMAN[@]}" exec --interactive --tty "${pod_id}" ollama "$@"
"${PODMAN[@]}" kill "${pod_id}" > /dev/null
emacsclient --eval "(generate-ollama-declaration)"

View file

@ -1,6 +1,6 @@
#! /usr/bin/sh
recordings=$(pgrep wf-recorder | wc -l)
recordings=$(pgrep wf-recorder | wc --lines)
if [ "${recordings}" = "0" ]; then
recs=""
else
@ -19,7 +19,7 @@ else
numlock="⮔"
fi
touchpad=$(swaymsg -t get_inputs | jq -r '[.[] | select(.type == "touchpad")][0] | .libinput.send_events')
touchpad=$(swaymsg --type get_inputs | jq --raw-output '[.[] | select(.type == "touchpad")][0] | .libinput.send_events')
if [ "${touchpad}" = "enabled" ]; then
touchpad_active="✅"
else
@ -29,8 +29,8 @@ fi
today=$(date +'%Y-%m-%d')
now=$(date +'%H:%M:%S')
hour=$(echo "${now}" | cut -d: -f1 -)
minute=$(echo "${now}" | cut -d: -f2 -)
hour=$(echo "${now}" | cut --delimiter ":" --fields 1 -)
minute=$(echo "${now}" | cut --delimiter ":" --fields 2 -)
if [ "${minute}" -lt 30 ]; then
case "${hour}" in
"00"|"12") clock_face="🕛";;
@ -64,7 +64,7 @@ else
fi
audio_mute=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}')
audio_volume=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]\+%' | head -n 1 | tr -d %)
audio_volume=$(pactl get-sink-volume @DEFAULT_SINK@ | grep --only-matching '[0-9]\+%' | head --lines 1 | tr --delete %)
audio_symbol="❔"
if [ "${audio_mute}" = "yes" ]; then
audio_symbol="🔇"
@ -81,7 +81,7 @@ else
fi
mike_mute=$(pactl get-source-mute @DEFAULT_SOURCE@ | awk '{print $2}')
mike_volume=$(pactl get-source-volume @DEFAULT_SOURCE@ | grep -o '[0-9]\+%' | head -n 1 | tr -d %)
mike_volume=$(pactl get-source-volume @DEFAULT_SOURCE@ | grep --only-matching '[0-9]\+%' | head --lines 1 | tr --delete %)
mike_symbol="❔"
if [ "${mike_mute}" = "yes" ]; then
mike_symbol="🍌"

View file

@ -1,13 +0,0 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
REPO_DIR="$(systemd-path user-state-private)"/git-sync
if [ -d "${REPO_DIR}" ]
then
git -C "${REPO_DIR}" pull
else
cd "$(systemd-path user-state-private)" || exit
git clone https://github.com/simonthum/git-sync
fi

View file

@ -0,0 +1,24 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
REPO_DIR="$(systemd-path user-state-private)"/agentbox
if [ -d "${REPO_DIR}" ]
then
git -C "${REPO_DIR}" pull
else
cd "$(systemd-path user-state-private)" || exit
git clone https://github.com/libohad-dev/agentbox
git -C "${REPO_DIR}" checkout podman
ln --symbolic "${REPO_DIR}"/agentbox ~/.local/bin/
fi
REPO_DIR="$(systemd-path user-state-private)"/git-sync
if [ -d "${REPO_DIR}" ]
then
git -C "${REPO_DIR}" pull
else
cd "$(systemd-path user-state-private)" || exit
git clone https://github.com/simonthum/git-sync
fi

61
.local/bin/select-urgent Executable file
View file

@ -0,0 +1,61 @@
#! /usr/bin/python3
import json
import re
import subprocess
from collections.abc import Iterable, Iterator
from typing import Any
def urgent_descendents(node: dict[str, Any]) -> Iterator[dict[str, Any]]:
for subnode in node["nodes"]:
yield from find_urgent(subnode)
def find_urgent(node: dict[str, Any]) -> Iterator[dict[str, Any]]:
if node["type"] in ("root", "output"):
yield from urgent_descendents(node)
elif node["type"] == "workspace":
for unode in urgent_descendents(node):
yield unode | {"workspace": node["name"]}
elif node["type"] == "con":
if node["focus"]:
yield from urgent_descendents(node)
elif node["urgent"]:
yield {"id": node["id"], "name": node["name"]}
def display_option(unode: dict[str, Any]) -> str:
return f"[#{unode['id']} @ WS {unode['workspace']}] {unode['name']}"
def select_option(unodes: Iterable[dict[str, Any]]) -> str:
joined = "".join(display_option(unode) + "\n" for unode in unodes)
proc = subprocess.run(
["fuzzel", "--dmenu"],
input=joined,
capture_output=True,
text=True,
)
return proc.stdout
def parse_id_from_option(opt: str) -> int:
m = re.match("^\\[#(?P<id>[0-9]+) ", opt)
if m is None:
raise ValueError()
return int(m["id"])
def main() -> None:
proc = subprocess.run(["swaymsg", "-t", "get_tree"], capture_output=True, text=True)
sway_tree = json.loads(proc.stdout)
opt = select_option(find_urgent(sway_tree))
con_id = parse_id_from_option(opt)
subprocess.run(["swaymsg", "--raw", f"[con_id={con_id}]", "focus"])
if __name__ == "__main__":
main()

View file

@ -37,15 +37,15 @@ else
fi
get_focused_monitor() {
swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name'
swaymsg --type get_outputs | jq --raw-output '.[] | select(.focused) | .name'
}
get_focused_window() {
swaymsg -t get_tree | jq -j '.. | select(.type?) | select(.focused).rect | "\(.x),\(.y) \(.width)x\(.height)"'
swaymsg --type get_tree | jq --join-output '.. | select(.type?) | select(.focused).rect | "\(.x),\(.y) \(.width)x\(.height)"'
}
select_window() {
swaymsg -t get_tree | jq -r '.. | select(.pid? and .visible?) | .rect | "\(.x),\(.y) \(.width)x\(.height)"' | slurp
swaymsg --type get_tree | jq --raw-output '.. | select(.pid? and .visible?) | .rect | "\(.x),\(.y) \(.width)x\(.height)"' | slurp
}
case "${option}" in
@ -68,11 +68,11 @@ case "${option}" in
"📷 Select window → 📋")
grim -g "$(select_window)" - | wl-copy;;
"📽 Record focused monitor")
wf-recorder -o "$(get_focused_monitor)" -f "$(recording_filename)";;
wf-recorder --output "$(get_focused_monitor)" -f "$(recording_filename)";;
"📽 Record select region")
wf-recorder -g "$(slurp)" -f "$(recording_filename)";;
wf-recorder --geometry "$(slurp)" -f "$(recording_filename)";;
"📽 Record focused window")
wf-recorder -g "$(get_focused_window)" -f "$(recording_filename)";;
wf-recorder --geometry "$(get_focused_window)" -f "$(recording_filename)";;
"📽 Record select window")
wf-recorder -g "$(select_window)" -f "$(recording_filename)";;
wf-recorder --geometry "$(select_window)" -f "$(recording_filename)";;
esac

81
.local/bin/temper Executable file
View file

@ -0,0 +1,81 @@
#! /usr/bin/bash
set -euo pipefail
IFS=$'\n\t'
default_editor() {
if [ -n "${SUDO_EDITOR+set}" ]; then
echo "${SUDO_EDITOR}"
elif [ -n "${VISUAL+set}" ]; then
echo "${VISUAL}"
elif [ -n "${EDITOR+set}" ]; then
echo "${EDITOR}"
else
echo "No editor configured" 1>&2
exit 1
fi
}
try_command() {
if "$@" 2> /dev/null; then
return
elif run0 "$@" > /dev/null; then
return
else
exit 2
fi
}
make_editable_copy() {
orig_file="$1"
tempfile="$2"
try_command cp -- "${orig_file}" "${tempfile}"
}
elevate_permissions() {
tempfile="$1"
orig_file="$2"
try_command chown --reference "${orig_file}" "${tempfile}"
try_command chmod --reference "${orig_file}" "${tempfile}"
}
update_file() {
tempfile="$1"
orig_file="$2"
try_command mv --force -- "${tempfile}" "${orig_file}"
}
clean_tempfiles() {
tempfile="$1"
rm --force -- "${tempfile}" "${tempfile}.orig"
}
if [ "$#" -ne "1" ]; then
echo "Expected exactly one input file. Usage:" 1>&2
echo " $0 FILE" 1>&2
exit 1
fi
editor_cmd="$(default_editor)"
echo "Editing using the command \"${editor_cmd}\""
IFS=' ' read -r -a editor <<< "${editor_cmd}"
orig_file="$1"
tempfile="$(mktemp)"
echo "Using temporary file ${tempfile}"
make_editable_copy "${orig_file}" "${tempfile}"
cp "${tempfile}" "${tempfile}.orig"
"${editor[@]}" "${tempfile}"
if cmp --quiet "${tempfile}" "${tempfile}.orig"; then
echo "No change to file"
else
elevate_permissions "${tempfile}" "${orig_file}"
update_file "${tempfile}" "${orig_file}"
fi
clean_tempfiles "${tempfile}"

View file

@ -4,11 +4,14 @@ set -euo pipefail
IFS=$'\n\t'
apt_update() {
sudo --reset-timestamp
sudo apt update --audit && apt list --upgradable
sudo apt full-upgrade
sudo apt autoremove
sudo --reset-timestamp
run0 apt update --audit && apt list --upgradable
run0 apt full-upgrade
run0 apt autoremove
}
emacs_update() {
echo Queuing updates for Emacs packages...
emacsclient --alternate-editor "" --reuse-frame --eval "(elpaca-pull-all t)" --no-wait > /dev/null
}
cargo_update() {
@ -16,20 +19,27 @@ cargo_update() {
cargo install $(cargo install --list | grep '^[a-z0-9_-]\+ v[0-9.]\+:$' | cut --delimiter=' ' --fields=1)
}
pipx_update() {
for venv in $(pipx list --json | jq --raw-output ".venvs | keys[]")
do
pipx upgrade "${venv}"
done
uv_update() {
uv tool upgrade --all
}
git_sync_update() {
echo Updating git-sync...
resync-git-sync
live_git_repo_update() {
echo Updating packages installed directly via git repos...
resync-live-git-repo-packages
}
podman_update() {
echo Updating container service images...
podman auto-update
podman --transient-store auto-update
echo Removing dangling images...
podman image prune --force
}
apt_update
pipx_update
emacs_update
uv_update
cargo_update
ghup
git_sync_update
live_git_repo_update
podman_update

View file

@ -3,13 +3,14 @@
set -euo pipefail
IFS=$'\n\t'
podman --transient-store run --rm -ti \
podman --transient-store run --interactive --rm --tty \
--env WAYLAND_DISPLAY \
--env XDG_RUNTIME_DIR=/tmp \
--hostname localhost \
--mount type=bind,source="${XDG_RUNTIME_DIR}"/"${WAYLAND_DISPLAY}",target=/tmp/"${WAYLAND_DISPLAY}" \
--mount type=bind,source="$(systemd-path user-configuration)"/emacs/site-lisp/local-models.el,target=/root/.config/emacs/site-lisp/local-models.el \
--mount type=bind,readonly=true,source="$(systemd-path user)"/.keys/vaults,target=/root/.age/key \
--mount type=bind,readonly=true,source="$(systemd-path user)"/.keys/vaults.pub,target=/root/.age/key.pub \
--mount type=bind,source="$(systemd-path user)"/Vaults,target=/root/Vaults \
--network none \
--network systemd-ollama \
vaulter:latest

View file

@ -10,13 +10,13 @@ repos:
exclude: ^\.config/fcitx5/
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.1
rev: v0.14.8
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.18.2
rev: v1.19.0
hooks:
- id: mypy
- repo: https://github.com/syntaqx/git-hooks
@ -29,6 +29,6 @@ repos:
hooks:
- id: detect-secrets
- repo: https://github.com/google/keep-sorted
rev: v0.6.0
rev: v0.7.1
hooks:
- id: keep-sorted

View file

@ -30,6 +30,10 @@ if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi
if [ -d "$(go env GOBIN)" ] ; then
PATH="$(go env GOBIN):$PATH"
fi
# Install the git-sync script
if [ -d "$(systemd-path user-state-private)"/git-sync ] ; then
PATH="$(systemd-path user-state-private)/git-sync:$PATH"