[{"content":"Debian 13 \u0026ldquo;trixie\u0026rdquo; has been out for about a year, so I finally upgraded my small server from Debian 12 \u0026ldquo;bookworm\u0026rdquo;. Short version: boring, which is exactly what you want from Debian.\nThese are my notes, not a replacement for the official release notes. Read those first.\nBefore # fully up to date on bookworm first sudo apt update \u0026amp;\u0026amp; sudo apt full-upgrade # anything held or not from Debian? apt-mark showhold apt list \u0026#39;?narrow(?installed, ?not(?origin(Debian)))\u0026#39; I also took a backup (restic, see this post) and a disk snapshot, and made sure I could reach the machine through a console in case SSH didn\u0026rsquo;t come back.\nThe upgrade Switch the sources from bookworm to trixie:\nsudo sed -i \u0026#39;s/bookworm/trixie/g\u0026#39; /etc/apt/sources.list /etc/apt/sources.list.d/*.list Then upgrade in two steps, as the release notes recommend:\nsudo apt update sudo apt upgrade --without-new-pkgs sudo apt full-upgrade For config files I\u0026rsquo;d changed (sshd_config, mostly), I looked at the diff and kept my version where the change was only mine.\nAfter sudo reboot cat /etc/debian_version sudo apt autoremove trixie ships APT 3.0, which can convert old one-line .list files to the newer deb822 .sources format:\nsudo apt modernize-sources Then I checked that every service I care about was running:\nsystemctl --failed journalctl -p err -b systemctl --failed was empty, and the only errors in the journal were from a service I\u0026rsquo;d forgotten existed, which I removed.\nTotal time About 40 minutes, most of it reading and preparing. The actual upgrade took less than ten.\n","permalink":"https://blog.arongp.com/posts/debian-13-upgrade/","summary":"Moving a small server from bookworm to trixie. The upgrade itself was uneventful. Preparing for it and checking afterwards took most of the time.","title":"Upgrading a small server from Debian 12 to 13"},{"content":"A year ago I moved my notes into Obsidian. Like most people I started by watching setup videos and building an elaborate system. Most of it is gone now. Here\u0026rsquo;s what\u0026rsquo;s left.\nFew folders, many links I started with a deep folder tree. Now there are five top-level folders:\nInbox/ Notes/ Projects/ Daily/ Attachments/ Everything that isn\u0026rsquo;t a daily note or tied to an active project goes in Notes/. Structure comes from links and a handful of index notes instead of folders. When I can\u0026rsquo;t decide where something goes, it goes in Notes/ and gets a link from wherever I\u0026rsquo;d look for it.\nDaily notes as the default entry point The daily note is where I write anything without thinking about where it belongs: meeting notes, commands I ran, a half-formed idea. Once a week I skim the week and promote anything useful into a proper note.\nPlugins I kept Daily notes and Templates (core). Enough for my needs. Dataview, but only for two queries: open tasks across projects, and notes created this week. Git for syncing and history. My vault is a private repository and commits every 10 minutes. I removed everything else. Each plugin was another thing to update and another way for the vault to look different on my phone.\nWriting notes I can find again The biggest change was in how I write, not the tooling:\nTitles are statements or questions, like \u0026ldquo;How restic retention works\u0026rdquo;, not \u0026ldquo;restic\u0026rdquo;. One idea per note when it\u0026rsquo;s something I\u0026rsquo;ll link to. Long logs are fine in daily notes. A short summary at the top in my own words. When I come back months later, that line is what tells me whether I\u0026rsquo;m in the right place. What didn\u0026rsquo;t stick Elaborate tagging. Tags drifted, links didn\u0026rsquo;t. Graph view. Pretty, never once useful. Reading highlights auto-imported from everywhere. I never read them again. The system I have now is boring, which is the point. I spend my time writing notes instead of maintaining the system.\n","permalink":"https://blog.arongp.com/posts/obsidian-one-year/","summary":"I started with a complicated folder hierarchy and a dozen plugins. A year later the vault is simpler, and I find things faster. Here\u0026rsquo;s what survived.","title":"One year of Obsidian: what actually stuck"},{"content":"I\u0026rsquo;ve wanted to design a PCB for years. This autumn I finally did: a small board with a USB-C connector, a 3.3 V regulator, a microcontroller module, and a temperature/humidity sensor. Designed in KiCad 9, ordered from a cheap fab, assembled by hand.\nIt powered up and talked over USB on the first try. A few things still went wrong.\nThings that went right Using the fab\u0026rsquo;s design rules from the start. Before routing anything I entered the manufacturer\u0026rsquo;s minimum track width, clearance and via sizes in Board Setup → Design Rules. DRC then catches problems while routing instead of at the end.\nUSB-C CC resistors. For a board that only sinks power, each CC pin needs its own 5.1 kΩ pull-down to ground. One shared resistor or none at all and a proper USB-C charger won\u0026rsquo;t give you 5 V. I double-checked this three times and it paid off.\nA ground pour on both layers, stitched with vias. Routing got a lot easier once I stopped drawing ground traces.\nThings I\u0026rsquo;d do differently Check footprints against the actual part. The footprint I picked for the USB-C receptacle was for a slightly different part number. The mounting pads lined up, the signal pads were close enough to solder, but only just. Now I open the datasheet\u0026rsquo;s land pattern next to the footprint before placing it.\nSilkscreen is not decoration. I didn\u0026rsquo;t label the pin header or mark pin 1 on the sensor. Future me, holding the board with a multimeter, was not impressed.\nTest points. I had nowhere convenient to clip a probe for 3.3 V and ground. Two small pads would have saved a lot of awkward probing.\nWorkflow that worked for me Schematic first, with every footprint assigned and checked. Run ERC and actually read every warning. Place connectors and mounting holes first, since they\u0026rsquo;re fixed by the enclosure. Power and ground, then the rest. DRC, 3D viewer, print at 1:1 and put real parts on the paper. Step 5 sounds silly. It caught the USB-C footprint problem, just too late because I skipped it the first time.\nNext board: same thing, smaller, with the fixes above, and maybe a battery.\n","permalink":"https://blog.arongp.com/posts/first-kicad-board/","summary":"I designed and ordered my first PCB, a small USB-C powered sensor board. It worked on the first spin, mostly. These are the mistakes I\u0026rsquo;d like to not repeat.","title":"Notes from my first KiCad board"},{"content":"My small home server had \u0026ldquo;backups\u0026rdquo; in the sense that I copied things to an external disk when I remembered. I finally set up something automatic with restic.\nWhy restic: encrypted by default, deduplicated, a single static binary, and it supports local disks, SFTP, and S3-compatible storage with the same commands.\nRepository sudo apt install restic sudo mkdir -p /etc/restic sudo sh -c \u0026#39;openssl rand -base64 32 \u0026gt; /etc/restic/password\u0026#39; sudo chmod 600 /etc/restic/password /etc/restic/env:\nRESTIC_REPOSITORY=/mnt/backup/restic RESTIC_PASSWORD_FILE=/etc/restic/password sudo env $(cat /etc/restic/env) restic init Copy the password somewhere that isn\u0026rsquo;t this machine. Without it the repository is just random bytes.\nThe backup job /etc/systemd/system/restic-backup.service:\n[Unit] Description=restic backup [Service] Type=oneshot EnvironmentFile=/etc/restic/env ExecStart=/usr/bin/restic backup --exclude-caches /etc /home /var/lib/docker/volumes ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune /etc/systemd/system/restic-backup.timer:\n[Unit] Description=nightly restic backup [Timer] OnCalendar=*-*-* 03:30 RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target sudo systemctl daemon-reload sudo systemctl enable --now restic-backup.timer Persistent=true means a missed run (machine was off) happens at next boot.\nChecking it Once a week I let restic verify a random slice of the actual data, not just the metadata:\nrestic check --read-data-subset=5% Test the restore A backup you\u0026rsquo;ve never restored is a hope, not a backup. I restored /etc to a temp directory and diffed it:\nrestic restore latest --target /tmp/restore-test --include /etc diff -r /etc /tmp/restore-test/etc | head Next step is a second copy off-site. Since restic speaks S3, that\u0026rsquo;s mostly a second repository and a restic copy in the same timer.\n","permalink":"https://blog.arongp.com/posts/restic-backups/","summary":"Encrypted, deduplicated nightly backups with restic, a systemd timer, and a retention policy. About 20 lines of config, and I actually tested the restore.","title":"A small restic backup setup for a Debian box"},{"content":"At some point I noticed I had nvm, pyenv, a Go install in /usr/local, a hand-downloaded terraform binary, and direnv, each with its own shell hook. Every new machine meant an hour of setup.\nmise handles all of that with one tool.\nInstall and activate curl https://mise.run | sh Then hook it into the shell. For fish:\n# ~/.config/fish/config.fish ~/.local/bin/mise activate fish | source For bash it\u0026rsquo;s eval \u0026quot;$(~/.local/bin/mise activate bash)\u0026quot;.\nGlobal defaults mise use -g node@22 python@3.12 go@latest This writes to ~/.config/mise/config.toml. Tools are downloaded on first use and live under ~/.local/share/mise, so nothing touches system packages.\nPer-project versions In a project directory:\nmise use node@20 terraform@1.9 That creates a mise.toml you can commit:\n[tools] node = \u0026#34;20\u0026#34; terraform = \u0026#34;1.9\u0026#34; [env] AWS_PROFILE = \u0026#34;dev\u0026#34; cd into the directory and the right versions are on PATH, plus the environment variables, which is what I used direnv for. Teammates run mise install once.\nIt also reads existing .nvmrc and .python-version files, which made migrating old repos easy.\nTasks The part I didn\u0026rsquo;t expect to use is tasks. Small projects that had a Makefile only for convenience now have this instead:\n[tasks.build] run = \u0026#34;go build -o bin/app ./cmd/app\u0026#34; [tasks.test] run = \u0026#34;go test ./...\u0026#34; mise run test works the same on every machine.\nVerdict One binary, one config format, and a new machine is ready after mise install. I removed nvm, pyenv and direnv from my dotfiles and haven\u0026rsquo;t missed them.\n","permalink":"https://blog.arongp.com/posts/mise-dev-tools/","summary":"nvm, pyenv, goenv, a manually downloaded terraform, and a shell script for direnv. I replaced all of it with mise and one small config file per project.","title":"Replacing five version managers with mise"},{"content":"I\u0026rsquo;ve used bash for a long time and zsh with a plugin manager for a few years. My .zshrc had slowly turned into something I was afraid to touch, so I gave fish a proper month instead of the usual afternoon.\nShort version: I\u0026rsquo;m keeping it for interactive use. Scripts stay in bash.\nWhat I like Good defaults. Autosuggestions from history, syntax highlighting, and completions generated from man pages all work out of the box. That replaced four zsh plugins.\nAbbreviations instead of aliases. An abbreviation expands in place when you hit space, so your history contains the real command:\nabbr -a gs git status abbr -a gco git checkout abbr -a k kubectl Functions are just files. Anything in ~/.config/fish/functions/name.fish is autoloaded the first time you call it. funced and funcsave make quick edits painless:\nfunction mkcd mkdir -p $argv[1]; and cd $argv[1] end funcsave mkcd PATH handling. fish_add_path deduplicates and persists, so no more PATH growing on every nested shell:\nfish_add_path ~/.local/bin What takes getting used to fish is not POSIX. Variables use set (there\u0026rsquo;s an export wrapper, but set -gx is the native way), and $(...) only arrived in fish 3.4, so I just use (...):\nset -gx EDITOR nvim set files (ls *.md) Sourcing a bash script that sets environment variables doesn\u0026rsquo;t work directly. For the two tools that need it, I either use the fish integration they ship (most do now) or run them through bash.\nMy setup My whole config.fish is now about 30 lines. Most of it is abbreviations and a couple of status is-interactive guards. Compared to the old zsh setup, startup is faster and I actually know what every line does.\nIf you\u0026rsquo;ve bounced off fish before because a copy-pasted command from Stack Overflow didn\u0026rsquo;t work, the trick is simple: keep #!/usr/bin/env bash for scripts, and type bash for the rare one-off.\n","permalink":"https://blog.arongp.com/posts/fish-shell/","summary":"After years of bash and a growing pile of zsh plugins, I switched my interactive shell to fish. Here\u0026rsquo;s what I kept, what I changed, and the few things that still trip me up.","title":"Giving fish shell a real try"},{"content":"I\u0026rsquo;m arongp, a software engineer who mostly works on Windows and Linux client software.\nOutside of work I like small, boring, reliable setups: a Debian box that just runs, a shell config I understand, and notes I can actually find again. Lately I\u0026rsquo;ve also been learning hardware design and slowly getting less bad at soldering.\nThis blog is my notebook. Posts are written for future me first, so they tend to be practical and a bit terse. If something here helped you, or is wrong, feel free to reach out.\nThings I use day to day\nDebian on servers, Windows on the desktop fish shell, mise, git Obsidian for notes KiCad for the occasional PCB The site is built with Hugo and the PaperMod theme.\n","permalink":"https://blog.arongp.com/about/","summary":"About me","title":"About"}]