diff --git a/boot-setup.org b/boot-setup.org new file mode 100644 index 0000000..14ed1d1 --- /dev/null +++ b/boot-setup.org @@ -0,0 +1,478 @@ +#+title: Boot Setup & Low-Level Configuration on Slackware +#+author: Ian Keane +#+date: 2026-08-08 +#+options: toc:t num:nil + +* Overview + +This is a writeup of how I have my Slackware laptop configured at the low level — +boot services, input devices, audio, remote filesystems, and window manager. The +goal throughout has been to keep everything in =~/.dotfiles= as static files, +managed by a pair of symlink scripts (=link.sh= for user configs, =link-root.sh= +for system configs), and to avoid =systemd= or any init system that requires +service descriptors to express dependencies. + +I run Slackware at runlevel 3 (multi-user, no X). I log in on a TTY and start +=stumpwm= or =kde= manually via =startx=, which means my X session is entirely +optional and can be restarted or switched without rebooting. Several services — +keyboard remapping, VPN, a network filesystem, and a music daemon — start at boot +via =rc.local=, before any user session begins. + +* The Boot Sequence + +Slackware uses BSD-style =rc.d= shell scripts. There's no dependency tracking, no +parallelism, no =After== directives. Everything in =/etc/rc.d/rc.local= runs +sequentially, in order, as root. + +My =rc.local= looks like this: + +#+begin_src bash +#!/bin/bash + +# Log all boot output to file while still showing on console +exec > >(tee /var/log/rc.local.log) 2>&1 + +if [ -x /etc/rc.d/rc.kanata ]; then + /etc/rc.d/rc.kanata start +fi + +if [ -x /etc/rc.d/rc.tailscale ]; then + /etc/rc.d/rc.tailscale start +fi + +if [ -x /etc/rc.d/rc.autofs ]; then + /etc/rc.d/rc.autofs start +fi + +if [ -x /etc/rc.d/rc.mpd ]; then + /etc/rc.d/rc.mpd start +fi +#+end_src + +The =exec > >(tee ...)= line redirects all subsequent stdout and stderr to both +the console and =/var/log/rc.local.log=, which is invaluable for debugging boot +failures without having to attach a serial console or dig through kernel logs. + +The services start in dependency order: kanata first (it only needs the kernel +input device), then tailscale (needs the network), then autofs (needs tailscale +to reach the NFS server), then MPD (needs autofs to have mounted the music +library). + +* Keyboard Remapping with Kanata + +I use [[https://github.com/jtroo/kanata][kanata]] for keyboard remapping. It operates at the kernel input layer via +=evdev=, entirely independent of X — which means my remaps work in TTYs, in +=tmux= over SSH, everywhere. + +The rc script at =~/.dotfiles/config/kanata/rc.kanata= is symlinked to +=/etc/rc.d/rc.kanata= by =link-root.sh=: + +#+begin_src bash +#!/bin/bash + +KANATA_BIN="/home/green/.cargo/bin/kanata" +KANATA_CFG="/home/green/.config/kanata/kanata.kbd" +PIDFILE="/var/run/kanata.pid" + +case "$1" in + start) + echo "Starting kanata..." + # Clear caps lock state before grabbing device + DISPLAY=:0 XAUTHORITY=/home/green/.Xauthority xset led off 2>/dev/null || true + sleep 0.2 + $KANATA_BIN --cfg $KANATA_CFG & + echo $! > $PIDFILE + ;; + stop) + kill $(cat $PIDFILE) && rm -f $PIDFILE + ;; + restart) + $0 stop; sleep 1; $0 start + ;; +esac +#+end_src + +Kanata is installed from source via cargo. Because it's a Rust binary in +=~/.cargo/bin=, the full path is hardcoded in the rc script — no =PATH= games at +boot. + +* Tailscale VPN + +[[https://tailscale.com][Tailscale]] provides a WireGuard-based mesh VPN. I use it primarily to reach my +home server (=tower=) from anywhere without port forwarding or a fixed IP. +=tailscaled= is the userspace daemon; I installed the binary directly from a +release tarball into =/usr/sbin/= and =/usr/bin/=. + +The rc script at =~/.dotfiles/config/tailscale/rc.tailscale=: + +#+begin_src bash +#!/bin/bash + +TAILSCALED=/usr/sbin/tailscaled +STATEDIR=/var/lib/tailscale +PIDFILE=/var/run/tailscaled.pid + +start() { + if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then + echo "tailscaled is already running"; return + fi + echo "Starting tailscaled..." + mkdir -p /var/run/tailscale + $TAILSCALED --state=$STATEDIR/tailscaled.state \ + --socket=/var/run/tailscale/tailscaled.sock \ + --port=41641 \ + &>/var/log/tailscaled.log & + echo $! > $PIDFILE + echo "tailscaled started (pid $(cat $PIDFILE))" +} + +stop() { + [ -f "$PIDFILE" ] && kill "$(cat $PIDFILE)" 2>/dev/null && rm -f "$PIDFILE" +} + +case "$1" in + start) start ;; + stop) stop ;; + restart) stop; sleep 1; start ;; + status) + if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then + echo "tailscaled is running (pid $(cat $PIDFILE))" + else + echo "tailscaled is not running" + fi ;; +esac +#+end_src + +** DNS Caveat + +Tailscale's MagicDNS (which would let you =ssh tower= instead of =ssh +100.73.64.64=) relies on the system DNS resolver being configured to use +Tailscale's nameserver. On Slackware without =systemd-resolved=, this doesn't +happen automatically, and =tailscaled= exits with status 2 when it tries to set +it up. + +Compounding this, my router's DHCP server advertises =dump.town= as a domain +search suffix. So when I type =tower=, the resolver tries =tower.dump.town= first +— which exists and resolves to the public IP of my server, not its Tailscale IP. + +The short-term fix is to use the Tailscale IP directly (=100.73.64.64=) anywhere +I need to reach =tower=. I also added this to =/etc/dhcpcd.conf= to suppress the +rogue domain search from DHCP: + +#+begin_src conf +nooption domain_name, domain_search +#+end_src + +Getting MagicDNS working properly on Slackware is a longer project involving +either patching =/etc/resolv.conf= from a hook or running a local resolver like +=dnsmasq=. + +* Network Filesystem via autofs + +My music library lives on =tower= and is exported over NFS. Rather than mount it +in =/etc/fstab= (which would block boot if the VPN wasn't up yet), I use +[[https://www.kernel.org/doc/Documentation/filesystems/autofs.txt][autofs]] to mount it on demand. + +autofs works by presenting a directory that triggers an NFS mount the first time +it's accessed. If the mount fails, the directory just looks empty. This is much +cleaner than retry loops or =nofail= in fstab. + +=~/.dotfiles/config/autofs/auto.master=: + +#+begin_src conf +/mnt /etc/auto.mnt --timeout=300 + +/misc /etc/auto.misc +/net -hosts +#+end_src + +=~/.dotfiles/config/autofs/auto.mnt=: + +#+begin_src conf +media -fstype=nfs,nolock,soft,timeo=30 100.73.64.64:/mnt/user/media +#+end_src + +This makes =/mnt/media= appear as a directory at all times. The NFS share from +=tower= is mounted into it the first time something accesses the path, and +unmounted after 300 seconds of inactivity. + +Key NFS options: +- =nolock= — required because =rpc.statd= is not running on this Slackware install +- =soft= — NFS operations time out instead of hanging forever if the server is unreachable +- =timeo=30= — 3-second timeout (value is in tenths of a second) + +* Music Player Daemon + +MPD runs as my user, started at boot by root, before any X session. It serves +music from the NFS share over a local socket that =ncmpcpp= connects to. + +The tricky part: MPD exits immediately if its =music_directory= is inaccessible. +It does not retry. So the rc script needs to ensure the NFS share is actually +mounted before starting MPD. + +=~/.dotfiles/config/mpd/rc.mpd=: + +#+begin_src bash +#!/bin/bash + +MPD=/usr/bin/mpd +CONF=/home/green/.config/mpd/mpd.conf +PIDFILE=/var/run/mpd/mpd.pid + +start() { + [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null && return + + echo "Starting mpd..." + mkdir -p /var/run/mpd + chown green:users /var/run/mpd + + # XDG_RUNTIME_DIR is normally created by pam_systemd on login. + # Since we start before login, we create it manually. + mkdir -p /run/user/1000 + chown green:users /run/user/1000 + chmod 700 /run/user/1000 + + # Wait for Tailscale to have connectivity to tower + echo "Waiting for Tailscale connectivity to tower..." + for i in $(seq 1 30); do + tailscale ping --c 1 100.73.64.64 &>/dev/null && break + sleep 1 + done + + # Trigger autofs mount and wait for it to succeed + echo "Waiting for /mnt/media/Music..." + for i in $(seq 1 15); do + ls /mnt/media/Music &>/dev/null && break + sleep 1 + done + + if ! ls /mnt/media/Music &>/dev/null; then + echo "WARNING: /mnt/media/Music not accessible, starting MPD anyway" + fi + + sudo -u green XDG_RUNTIME_DIR=/run/user/1000 $MPD $CONF + echo "mpd started" +} +#+end_src + +The two-phase wait is important: first we ping tower via Tailscale (to ensure the +VPN peer is reachable), then we =ls= the music directory (to trigger the autofs +mount and confirm it succeeded). In practice both loops complete in 1-2 seconds +once the network is up. + +MPD's config at =~/.dotfiles/config/mpd/mpd.conf= uses PulseAudio for output. +Since I'm also running PipeWire with =pipewire-pulse=, this just works — PulseAudio +clients connect to PipeWire's compatibility layer. + +#+begin_src conf +music_directory "/mnt/media/Music" +playlist_directory "/mnt/media/Music/playlists" +db_file "~/.config/mpd/database" +pid_file "/var/run/mpd/mpd.pid" +user "green" +bind_to_address "localhost" +port "6600" + +audio_output { + type "pulse" + name "PulseAudio Output" +} +#+end_src + +* X Session: Runlevel 3 + startx + +I boot to runlevel 3 and log in on a TTY. X is started manually with =startx=, +which reads =~/.dotfiles/config/xinitrc= (symlinked to =~/.xinitrc=): + +#+begin_src bash +#!/bin/sh + +[ -f ~/.profile ] && . ~/.profile + +# XDG_RUNTIME_DIR not set by systemd at runlevel 3 +mkdir -p /run/user/$(id -u) +chmod 700 /run/user/$(id -u) + +# Start PipeWire audio stack +pipewire & +pipewire-pulse & +wireplumber & + +exec stumpwm +#+end_src + +PipeWire starts here rather than at boot because it's a user service that needs +=XDG_RUNTIME_DIR= set correctly. It runs within the X session lifetime. + +I also keep KDE Plasma installed and can switch to it by changing =exec stumpwm= +to =exec startplasma-x11=. Since I'm at runlevel 3, there's no display manager +involved — it's just which process I exec at the end of =xinitrc=. + +* Input: libinput Touchpad + +My touchpad is driven by =libinput= rather than the legacy Synaptics driver. +=libinput= supports =DisableWhileTyping= natively via the X server, without +needing a separate =syndaemon= process: + +=~/.dotfiles/config/xorg.conf.d/70-touchpad.conf=: + +#+begin_src conf +Section "InputClass" + Identifier "touchpad" + MatchIsTouchpad "on" + Driver "libinput" + Option "DisableWhileTyping" "true" + Option "Tapping" "on" + Option "NaturalScrolling" "false" +EndSection +#+end_src + +This is symlinked to =/etc/X11/xorg.conf.d/70-touchpad.conf= by =link-root.sh=. +The Synaptics driver is still installed but doesn't match the touchpad because the +=libinput= rule takes precedence (the =70-= prefix puts it after the default =10-= +synaptics rules). + +* WiFi Power Management + +NetworkManager's default behavior is to enable power saving on WiFi, which causes +packet loss and latency spikes. One drop config file fixes it: + +=~/.dotfiles/config/NetworkManager/wifi-powersave-off.conf=: + +#+begin_src conf +[connection] +wifi.powersave = 2 +#+end_src + +Value =2= means "disable power saving". This is symlinked to +=/etc/NetworkManager/conf.d/wifi-powersave-off.conf=. + +* StumpWM: Window Manager Configuration + +StumpWM is a tiling window manager written and configured in Common Lisp. It runs +on SBCL and exposes the full language for configuration — no DSL, no limitations. + +** Floating Terminal Scratchpads + +The most interesting thing I've set up in StumpWM is a floating scratchpad system. +The goal: press a keybinding, get a floating terminal running a specific program, +positioned at a fixed location on screen. Press the binding again (or focus +another window), and it goes away. + +The key insight was =define-frame-preference= with =:float= as the frame number: + +#+begin_src lisp +(define-frame-preference "Default" + (:float t t :class "alsamixer-scratch") + (:float t t :class "ncmpcpp-scratch")) +#+end_src + +When StumpWM adds a new window to a group, it consults =frame-preference= rules +to decide where to place it. =:float= as the frame argument causes the window to +be floated /during/ =group-add-window=, before any tiling logic runs. This is the +only reliable way to float a window from config — by the time =*new-window-hook*= +fires, the window has already been tiled. + +We use the WM_CLASS property to identify windows. =wezterm --class my-class= sets +both fields of WM_CLASS to =my-class=, so the rule matches reliably. + +The full scratchpad system in =~/.dotfiles/config/stumpwm/config.lisp=: + +#+begin_src lisp +(defvar *float-term-rules* '()) + +(defun register-float-term (class command x y width height &key persistent) + "Register a floating terminal. Add a matching define-frame-preference rule too. +If PERSISTENT is t, smart-kill will hide the window instead of killing it." + (setf *float-term-rules* + (cons (list class command x y width height :persistent persistent) + (remove class *float-term-rules* :key #'car :test #'string=)))) + +;; Position floats after they appear (define-frame-preference handles the float, +;; the hook handles the geometry) +(add-hook *new-window-hook* + (lambda (win) + (let ((rule (assoc (window-class win) *float-term-rules* :test #'string=))) + (when rule + (stumpwm::float-window-move-resize win + :x (nth 2 rule) :y (nth 3 rule) + :width (nth 4 rule) :height (nth 5 rule)))))) + +(defun spawn-or-focus (class command) + "Focus existing window with CLASS, or spawn COMMAND if none exists." + (let ((win (find-if (lambda (w) (string= (window-class w) class)) + (screen-windows (current-screen))))) + (cond ((null win) (run-shell-command command)) + ((eq win (current-window)) nil) + (t (focus-window win))))) + +(defcommand smart-kill () () + "Kill window, or hide it if it's a persistent float." + (let* ((win (current-window)) + (class (when win (window-class win))) + (rule (when class (assoc class *float-term-rules* :test #'string=)))) + (if (and rule (getf (nthcdr 6 rule) :persistent)) + (hide-window win) + (kill-window-or-frame)))) + +;; Registrations +(register-float-term "alsamixer-scratch" + "wezterm start --class alsamixer-scratch -- alsamixer" + 628 450 800 400) + +(register-float-term "ncmpcpp-scratch" + "wezterm start --class ncmpcpp-scratch -- ncmpcpp" + 628 300 900 500 :persistent t) +#+end_src + +=ncmpcpp= is registered as =:persistent t=, so =s-q= (=smart-kill=) hides it +rather than closing it — preserving the MPD connection. =alsamixer= is not +persistent, so =s-q= just quits it. =s-F= (=select-floating-window=) lets you +recover a hidden float. + +** Keybindings + +StumpWM supports both a prefix-key map (=C-t= by default) and direct top-level +bindings via =*top-map*=. I prefer Super-key bindings for common actions: + +| Binding | Action | +|-----------+---------------------------------| +| =s-d= | rofi run launcher | +| =s-w= | rofi window picker | +| =s-hjkl= | focus frame (vim directions) | +| =s-q= | smart-kill | +| =s-f= | toggle fullscreen | +| =s-F= | select floating window | +| =s-1..4= | switch group | +| =s--= | vertical split | +| =s-\= | horizontal split | +| =s-c a= | alsamixer float | +| =s-m p= | ncmpcpp float | +| =s-m v= | pick video with mpv | +| =s-m d= | download URL from clipboard | + +Brightness is via a sub-map: =s-c b 1= through =s-c b 0= set 10%-100%. + +** Gaps + +=swm-gaps= module provides inner and outer gap support. I have keybindings to +switch between gap presets (=s-W g 0/1/2/3= for none/small/medium/large). + +* Dotfiles Management + +All of the above is stored in =~/.dotfiles= and managed by two scripts: + +- =link.sh= — user-level symlinks (run as self) +- =link-root.sh= — system-level symlinks (run as root) + +Both scripts use a =backup_and_link= function that moves any existing file to +=.bak= before creating the symlink, so they're safe to run on a system that +already has config files in place. + +=link-root.sh= also idempotently appends service invocations to =/etc/rc.local= +if they're not already present, and =chmod +x='s the rc scripts it symlinks. + +The advantage of this approach over Nix or stow: it's plain shell, readable, +debuggable, and doesn't require any tooling to be installed first. The +disadvantage: no atomicity, no rollback. For a single personal machine that's a +fine tradeoff. diff --git a/config/autofs/auto.master b/config/autofs/auto.master new file mode 100644 index 0000000..9ef740e --- /dev/null +++ b/config/autofs/auto.master @@ -0,0 +1,14 @@ +# +# auto.master - autofs mount map +# +# Generated from dotfiles. See /etc/auto.master(5). +# +# /mnt is managed by autofs - media share mounted on demand via Tailscale +/mnt /etc/auto.mnt --timeout=300 + +# Default entries +/misc /etc/auto.misc +/net -hosts + ++dir:/etc/auto.master.d ++auto.master diff --git a/config/autofs/auto.mnt b/config/autofs/auto.mnt new file mode 100644 index 0000000..6b425cc --- /dev/null +++ b/config/autofs/auto.mnt @@ -0,0 +1,3 @@ +# autofs map for /mnt +# Mounts NFS share from tower via Tailscale IP on demand +media -fstype=nfs,nolock,soft,timeo=30 100.73.64.64:/mnt/user/media diff --git a/config/mpd/mpd.conf b/config/mpd/mpd.conf new file mode 100644 index 0000000..d3fdf8a --- /dev/null +++ b/config/mpd/mpd.conf @@ -0,0 +1,22 @@ +# MPD configuration +# Music is on NFS share at /mnt/media — MPD only starts if that's mounted + +music_directory "/mnt/media/Music" +playlist_directory "/mnt/media/Music/playlists" +db_file "~/.config/mpd/database" +log_file "~/.config/mpd/log" +pid_file "/var/run/mpd/mpd.pid" +state_file "~/.config/mpd/state" + +user "green" + +bind_to_address "localhost" +port "6600" + +audio_output { + type "pulse" + name "PulseAudio Output" +} + +# Also try pipewire via pulse compatibility layer +# If pure pipewire is needed, switch type to "pipewire" diff --git a/config/mpd/rc.mpd b/config/mpd/rc.mpd new file mode 100755 index 0000000..e255821 --- /dev/null +++ b/config/mpd/rc.mpd @@ -0,0 +1,64 @@ +#!/bin/bash +# rc.mpd - Music Player Daemon for Slackware +# Only starts if /mnt/media is mounted (NFS share must be available) + +MPD=/usr/bin/mpd +CONF=/home/green/.config/mpd/mpd.conf +PIDFILE=/var/run/mpd/mpd.pid + +start() { + if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then + echo "mpd is already running" + return + fi + echo "Starting mpd..." + mkdir -p /var/run/mpd + chown green:users /var/run/mpd + # Ensure user runtime dir exists (normally created by pam_systemd on login, + # but we start before login so we create it manually) + mkdir -p /run/user/1000 + chown green:users /run/user/1000 + chmod 700 /run/user/1000 + # Wait for Tailscale to have connectivity to tower specifically + echo "Waiting for Tailscale connectivity to tower..." + for i in $(seq 1 30); do + tailscale ping --c 1 100.73.64.64 &>/dev/null && break + sleep 1 + done + # Now trigger autofs mount + echo "Waiting for /mnt/media/Music..." + for i in $(seq 1 15); do + ls /mnt/media/Music &>/dev/null && break + sleep 1 + done + if ! ls /mnt/media/Music &>/dev/null; then + echo "WARNING: /mnt/media/Music not accessible after wait, starting MPD anyway" + fi + sudo -u green XDG_RUNTIME_DIR=/run/user/1000 $MPD $CONF + echo "mpd started" +} + +stop() { + if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then + echo "Stopping mpd..." + sudo -u green $MPD --kill $CONF + else + echo "mpd is not running" + fi +} + +status() { + if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then + echo "mpd is running (pid $(cat $PIDFILE))" + else + echo "mpd is not running" + fi +} + +case "$1" in + start) start ;; + stop) stop ;; + restart) stop; sleep 1; start ;; + status) status ;; + *) echo "Usage: $0 {start|stop|restart|status}" ;; +esac diff --git a/config/stumpwm/config.lisp b/config/stumpwm/config.lisp index a9ece51..bdcc55f 100644 --- a/config/stumpwm/config.lisp +++ b/config/stumpwm/config.lisp @@ -54,6 +54,12 @@ (:float t t :class "alsamixer-scratch") (:float t t :class "ncmpcpp-scratch")) +(define-frame-preference "scratch-emacs-1" + (0 t t :title "scratch-emacs-1")) + +(define-frame-preference "scratch-emacs-2" + (0 t t :title "scratch-emacs-2")) + (define-frame-preference "Emacs" (1 t t :restore "emacs-editing-dump" :title "...xdvi") (0 t t :create "emacs-dump" :class "Emacs")) @@ -326,3 +332,60 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." (define-key *top-map* (kbd "s-2") "gselect 2") (define-key *top-map* (kbd "s-3") "gselect 3") (define-key *top-map* (kbd "s-4") "gselect 4") + +;;; Fullscreen scratchpad groups +;; +;; Each scratchpad lives in its own hidden group. Pressing the binding +;; switches to it (spawning the window on first visit). Pressing again +;; returns to wherever you came from. We track the previous group ourselves +;; rather than relying on gother, which is unreliable when windows spawn async. + +(defun toggle-scratchpad-group (group-name command &key window-title) + (let* ((screen (current-screen)) + (current (current-group)) + (target (find-group screen group-name)) + (existing (when window-title + (find-if (lambda (w) (string= (window-name w) window-title)) + (screen-windows screen))))) + (if (eq current target) + (when *scratchpad-previous-group* + (switch-to-group *scratchpad-previous-group*)) + (progn + (setf *scratchpad-previous-group* current) + (when (and existing (not (eq (window-group existing) target))) + (move-window-to-group existing target)) + (switch-to-group target) + (unless existing + (run-shell-command command)))))) + +(run-commands "gnewbg scratch-term-1" + "gnewbg scratch-term-2" + "gnewbg scratch-emacs-1" + "gnewbg scratch-emacs-2") + +(defcommand scratch-term-1 () () + "Toggle fullscreen persistent terminal (wezterm)." + (toggle-scratchpad-group "scratch-term-1" + "wezterm start --class scratch-term-1")) + +(defcommand scratch-term-2 () () + "Toggle second fullscreen persistent terminal (wezterm)." + (toggle-scratchpad-group "scratch-term-2" + "wezterm start --class scratch-term-2")) + +(defcommand scratch-emacs-1 () () + "Toggle fullscreen persistent emacsclient frame." + (toggle-scratchpad-group "scratch-emacs-1" + "emacsclient -c --frame-parameters '((name . \"scratch-emacs-1\"))'" + :window-title "scratch-emacs-1")) + +(defcommand scratch-emacs-2 () () + "Toggle second fullscreen persistent emacsclient frame (leetcode)." + (toggle-scratchpad-group "scratch-emacs-2" + "emacsclient -c --frame-parameters '((name . \"scratch-emacs-2\"))'" + :window-title "scratch-emacs-2")) + +(define-key *top-map* (kbd "C-Return") "scratch-term-1") +(define-key *top-map* (kbd "C-\\") "scratch-term-2") +(define-key *top-map* (kbd "s-e") "scratch-emacs-1") +(define-key *top-map* (kbd "s-L") "scratch-emacs-2") diff --git a/config/tailscale/rc.tailscale b/config/tailscale/rc.tailscale new file mode 100755 index 0000000..2567ba9 --- /dev/null +++ b/config/tailscale/rc.tailscale @@ -0,0 +1,46 @@ +#!/bin/bash +# rc.tailscale - Tailscale VPN daemon for Slackware + +TAILSCALED=/usr/sbin/tailscaled +STATEDIR=/var/lib/tailscale +PIDFILE=/var/run/tailscaled.pid + +start() { + if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then + echo "tailscaled is already running" + return + fi + echo "Starting tailscaled..." + $TAILSCALED --state=$STATEDIR/tailscaled.state \ + --socket=/var/run/tailscale/tailscaled.sock \ + --port=41641 \ + &>/var/log/tailscaled.log & + echo $! > $PIDFILE + echo "tailscaled started (pid $(cat $PIDFILE))" +} + +stop() { + if [ -f "$PIDFILE" ]; then + echo "Stopping tailscaled..." + kill "$(cat $PIDFILE)" 2>/dev/null + rm -f "$PIDFILE" + else + echo "tailscaled is not running" + fi +} + +status() { + if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then + echo "tailscaled is running (pid $(cat $PIDFILE))" + else + echo "tailscaled is not running" + fi +} + +case "$1" in + start) start ;; + stop) stop ;; + restart) stop; sleep 1; start ;; + status) status ;; + *) echo "Usage: $0 {start|stop|restart|status}" ;; +esac diff --git a/link-root.sh b/link-root.sh index 37b32a9..4474e97 100755 --- a/link-root.sh +++ b/link-root.sh @@ -39,6 +39,22 @@ backup_and_link "$DOTFILES/config/xorg.conf.d/70-touchpad.conf" "/etc/X11/xorg.c backup_and_link "$DOTFILES/config/kanata/rc.kanata" "/etc/rc.d/rc.kanata" chmod +x /etc/rc.d/rc.kanata +# Tailscale daemon +backup_and_link "$DOTFILES/config/tailscale/rc.tailscale" "/etc/rc.d/rc.tailscale" +chmod +x /etc/rc.d/rc.tailscale +mkdir -p /var/lib/tailscale +mkdir -p /var/run/tailscale + +# autofs config +backup_and_link "$DOTFILES/config/autofs/auto.master" "/etc/auto.master" +backup_and_link "$DOTFILES/config/autofs/auto.mnt" "/etc/auto.mnt" + +# MPD rc script +chmod +x /etc/rc.d/rc.autofs +backup_and_link "$DOTFILES/config/mpd/rc.mpd" "/etc/rc.d/rc.mpd" +chmod +x /etc/rc.d/rc.mpd +mkdir -p /mnt + # NetworkManager wifi power saving fix backup_and_link "$DOTFILES/config/NetworkManager/wifi-powersave-off.conf" "/etc/NetworkManager/conf.d/wifi-powersave-off.conf" @@ -53,5 +69,38 @@ else echo "rc.local already has kanata entry, skipping" fi +# Add boot output logging if not present +if ! grep -q "tee /var/log/rc.local.log" /etc/rc.d/rc.local; then + # Insert logging line after the shebang/header + sed -i '/^#.*Local system/a\\n# Log all boot output to file while still showing on console\nexec > >(tee /var/log/rc.local.log) 2>&1' /etc/rc.d/rc.local + echo "added boot logging to rc.local" +else + echo "rc.local already has boot logging, skipping" +fi + +# Add tailscale, autofs, mpd to rc.local if not present +if ! grep -q "rc.tailscale" /etc/rc.d/rc.local; then + cat >> /etc/rc.d/rc.local << 'RCEOF' + +# Start tailscale daemon +if [ -x /etc/rc.d/rc.tailscale ]; then + /etc/rc.d/rc.tailscale start +fi + +# Start autofs (handles NFS mounts on demand) +if [ -x /etc/rc.d/rc.autofs ]; then + /etc/rc.d/rc.autofs start +fi + +# Start MPD (autofs will mount /mnt/media on first access) +if [ -x /etc/rc.d/rc.mpd ]; then + /etc/rc.d/rc.mpd start +fi +RCEOF + echo "added tailscale/autofs/mpd to rc.local" +else + echo "rc.local already has tailscale entry, skipping" +fi + echo "" echo "done. reboot or restart X for xorg changes to take effect." diff --git a/link.sh b/link.sh index 94cbe83..3c71963 100755 --- a/link.sh +++ b/link.sh @@ -33,6 +33,7 @@ backup_and_link "$DOTFILES/config/senpai" "$HOME/.config/senpai" backup_and_link "$DOTFILES/config/kanata" "$HOME/.config/kanata" backup_and_link "$DOTFILES/config/nyxt" "$HOME/.config/nyxt" backup_and_link "$DOTFILES/config/yazi" "$HOME/.config/yazi" +backup_and_link "$DOTFILES/config/mpd/mpd.conf" "$HOME/.config/mpd/mpd.conf" backup_and_link "$DOTFILES/config/rofi" "$HOME/.config/rofi" backup_and_link "$DOTFILES/config/zathura" "$HOME/.config/zathura"