blog.iankulin.com

Linux

Getting Ghostty to Work on Synology

Trying the Ghostty terminal, I hit a problem over SSH to a Synology NAS: arrow-key history recall and clear don’t work, because Ghostty sets TERM to xterm-ghostty and Synology lacks the matching terminfo. The practical fix is overriding TERM to xterm-256color via an ssh config SetEnv entry, which can be wildcarded across multiple hosts. The terminfo installation method Ghostty officially recommends fails on Synology’s minimal DSM...

Command chaining with NTFY for long running commands

Using ntfy.sh, shell commands can trigger a push notification to a phone or watch via curl when they finish. I demonstrate chaining long-running commands like rsync with && and || to send success or failure messages, running everything in the background with nohup, and redirecting the curl output to a log file...

rsync between Synology NAS

After abandoning an overly complicated LXD-based file transfer setup, I walk through using command-line rsync to sync files directly between two Synology NASes, including over Tailscale. I cover setting up passwordless SSH without ssh-copy-id, working around DSM 7’s restriction on Tailscale outbound connections and its lack of Magic DNS, enabling rsync in the Synology interface, excluding metadata and recycle bin directories, fixing permissions, throttling bandwidth, deleting remote files in one-way syncs, and running the job in the background with nohup...

User environment variables are not available in cron

Docker environment variables set for a container aren’t available to scripts run by cron inside it, even when cron runs as the same user. After confirming the cause and finding no clean fix, I settle on a workaround: saving the needed variable to a file in the entry point script and reading it back from the cron job...

Beginning Node App Security

I cover simple steps to secure a Node.js web app exposed to the public internet on a VPS, including putting it behind an Nginx reverse proxy with basic auth, enforcing HTTPS, and using Fail2ban to automatically ban brute-force attempts. I also cover restricting ports via a cloud firewall, disabling root SSH login, using SSH keys, keeping the system updated, and basic monitoring with Uptime Kuma...

Docker volume backup is more complicated than it should be

I explain how to back up and migrate a Docker named volume between hosts using the official method: stopping the container, then running a temporary container that mounts the volume and tars its data to a file. I work through an example with Uptime Kuma that covers moving the data to a new VM and restoring it into a fresh install, including a gotcha where restoring to the wrong path leaves the data nested and ignored. I end by questioning why simply copying the volume’s files from the local filesystem isn’t recommended...

apt update - BADSIG 871920D1991BC93C

My weekend Ansible apt upgrade run failed on one Ubuntu host with a BADSIG GPG signature error on the Ubuntu jammy-updates repository. I trace the problem to my Apt Cacher NG cache and fix it by deleting the cached Ubuntu repository data, forcing fresh index files to be downloaded. I leave the underlying cause unresolved, though it appears to affect only that one host...

Caching APT updates

I set up APT Cacher NG in an unprivileged LXC container to cache Debian package downloads for machines on my homelab LAN, avoiding repeated fetching of the same updates. My walkthrough covers installing and configuring the server (including enabling HTTPS passthrough), pointing client machines at the cache via a proxy config file pushed out with Ansible, and checking the built-in report page for cache statistics...

Installing service with Ansible

I deploy a small Go monitoring endpoint to my homelab servers as a systemd service, using Ansible. I cover the basics of a systemd .service file, including target dependencies like After=network.target, and an Ansible playbook that copies the executable and service file into place, restarting the service via handlers only when files change. I pull my sudo credentials from an encrypted Ansible vault...

Disable SSH root logins

My freshly spun-up Ubuntu VPS starts receiving SSH brute-force login attempts against root within minutes, at roughly one attempt every 10 seconds. I cover disabling root SSH login by creating a sudo user, setting PermitRootLogin no in sshd_config, restarting sshd, and watching out for included config files in sshd_config.d that can override settings. I end with the idea of logging what passwords the bots actually try...

Error wiping old drive in Proxmox

When reusing my old Proxmox boot SSD as a ZFS pool, a “has a holder (500)” error blocks wiping the drive in the web GUI. The workaround is to delete the old partitions with fdisk, reboot to release the kernel’s hold, and then wipe the drive normally. I also note the installer’s existing-volume-group prompt and recommend the graphical installer when adding a boot drive to an existing node...

Bloody VIM

A short survival guide to vi/vim for sysadmins who unexpectedly find themselves in it, such as when editing an Ansible vault or adding a message to a git commit. I cover the essentials of Vim’s modes: navigating with the cursor keys, pressing i to enter insert mode, using escape and :wq to save and quit, with a brief note on why Vim’s unusual movement-focused design exists...

Finding the host IP from inside a Docker container

Migrating my node.js API from a homelab VM to a VPS hits a snag: nginx, running in a Docker container, serves static files fine but returns Bad Gateway when proxying API requests. I walk through working out that localhost inside the container doesn’t reach the host, trying host.docker.internal without success, and finding the fix by checking the docker0 bridge interface, which gave 172.17.0.1 as the address to put in the nginx config...

nginx in Front of a node.js app

I configure NGINX on a VPS to serve static files while forwarding /api routes to a Node.js app running on localhost. I cover a server block example with proxy_pass and the Host header, the conf.d include convention, and restarting or validating the config. This post is part of a series on my weather API, but it is self-contained...

ZFS Basics on Proxmox

Persuaded by the 2.5 Admins podcast’s enthusiasm for ZFS, I set up a mirrored ZFS pool in Proxmox using two 256GB NVME drives in a USB dual-bay enclosure. My walkthrough covers creating the pool via the Proxmox web GUI, a primer on ZFS terminology (pools, vdevs, datasets), and basic tasks like moving VM disks onto the pool and running a scrub. I also note enclosure temperatures under load and possible sustained write slowdowns on the Samsung PM981 SSDs...

Outside Temperature From an API in a Shell Script

I describe adding outdoor temperature readings to my homelab server temperature logs to account for ambient temperature changes, using OpenWeather’s free API. A bash script on a VPS polls the weather endpoint every five minutes, extracts the temperature and timestamp with awk and cut, and serves the result as a text file that each server fetches during its logging cycle; the server-side script also loads the drivetemp kernel module to read SSD temperatures. Graphs of 24 hours of data show how ambient temperature, workload, and a CPU stress test affected the three servers...

Git/GutHub - macOS - marking file as executable

While setting up a small cron-driven script to cache a weather API’s JSON output for serving over Nginx, I run into trouble getting git to preserve the script’s executable bit between macOS and an Ubuntu VPS. After git update-index –chmod=+x fails and toggling the filemode config setting only seems to work by accident, the root cause turns out to be a forgotten fact: macOS does support Unix file permissions, so a plain chmod before committing solves it...

Linux Shell Script for Temperature Logging

Worried about internal temperatures in a stack of HP Elitedesk nodes running Proxmox, and wanting a before-and-after comparison for a planned NVMe upgrade, I set up logging of PCH, CPU, and SSD temperatures. The readings come from the /sys/class/hwmon/ tree, with a drivetemp kernel module needed for the SSD; a shell script appends them to a CSV every five minutes via cron. The collected data is graphed, including during a CPU stress test...

Why use './' in front of filenames?

An exploration of why filenames in Linux are sometimes prefixed with ./. I ask ChatGPT for an explanation, verify its claims with hands-on tests, and find the prefix is useful for executing scripts in the current directory, preventing filenames from being mistaken for command arguments, and clarifying relative paths in HTML—though in many cases it’s optional...

Mounting NFS shares into LXC containers

I run Syncthing in a Proxmox LXC container as a possible Dropbox replacement and hit a snag when an NFS share from the NAS fails to mount with an access denied error. The cause is the container’s security restrictions, and the fix is to run it as privileged, either by editing its config file or changing the option in the Proxmox web GUI...