Boot fixes, scratch windows

This commit is contained in:
Ian Keane 2026-08-09 14:31:49 -04:00
parent 8aa1f8a8a6
commit d3de780583
9 changed files with 740 additions and 0 deletions

478
boot-setup.org Normal file
View file

@ -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.

14
config/autofs/auto.master Normal file
View file

@ -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

3
config/autofs/auto.mnt Normal file
View file

@ -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

22
config/mpd/mpd.conf Normal file
View file

@ -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"

64
config/mpd/rc.mpd Executable file
View file

@ -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

View file

@ -54,6 +54,12 @@
(:float t t :class "alsamixer-scratch") (:float t t :class "alsamixer-scratch")
(:float t t :class "ncmpcpp-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" (define-frame-preference "Emacs"
(1 t t :restore "emacs-editing-dump" :title "...xdvi") (1 t t :restore "emacs-editing-dump" :title "...xdvi")
(0 t t :create "emacs-dump" :class "Emacs")) (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-2") "gselect 2")
(define-key *top-map* (kbd "s-3") "gselect 3") (define-key *top-map* (kbd "s-3") "gselect 3")
(define-key *top-map* (kbd "s-4") "gselect 4") (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")

46
config/tailscale/rc.tailscale Executable file
View file

@ -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

View file

@ -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" backup_and_link "$DOTFILES/config/kanata/rc.kanata" "/etc/rc.d/rc.kanata"
chmod +x /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 # NetworkManager wifi power saving fix
backup_and_link "$DOTFILES/config/NetworkManager/wifi-powersave-off.conf" "/etc/NetworkManager/conf.d/wifi-powersave-off.conf" 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" echo "rc.local already has kanata entry, skipping"
fi 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 ""
echo "done. reboot or restart X for xorg changes to take effect." echo "done. reboot or restart X for xorg changes to take effect."

View file

@ -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/kanata" "$HOME/.config/kanata"
backup_and_link "$DOTFILES/config/nyxt" "$HOME/.config/nyxt" backup_and_link "$DOTFILES/config/nyxt" "$HOME/.config/nyxt"
backup_and_link "$DOTFILES/config/yazi" "$HOME/.config/yazi" 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/rofi" "$HOME/.config/rofi"
backup_and_link "$DOTFILES/config/zathura" "$HOME/.config/zathura" backup_and_link "$DOTFILES/config/zathura" "$HOME/.config/zathura"