I value simplicity and minimalism. Even as a computer scientist, I use as little software as possible. I want my operating system to be lightweight, performant, and free from telemetry and spyware. Full configurability and complete control over my system are essential to me. Therefore, I exclusively use Arch Linux.
The base install is managed by install.sh, and I have a just command for each of my device setups. Any and all manual steps are documented below.
-
Flash the Arch Linux ISO to a USB drive.
-
Insert the USB drive into the computer and temporarily boot into it via the BIOS boot menu.
-
Enter the Arch Linux live environment.
-
If using a wireless network, connect to it over WiFi. The install script falls back to
iwctlif it cannot reach a network, so connect beforehand:List available devices:
iwctl device list
Scan for available networks:
iwctl station <device> scan
List available networks:
iwctl station <device> get-networks
Connect to the network:
iwctl --passphrase <password> station <device> connect <network>
Test the connection:
ping archlinux.org
-
Fetch the install script:
curl -O https://raw.githubusercontent.com/dan-smith-tech/rig/main/install.sh
-
Make the script executable:
chmod +x install.sh
-
Run the install and follow the prompts:
./install.sh
-
Reboot the device.
-
Log in and, if using a wireless network, connect to it over WiFi:
nmcli device wifi connect <network> --ask
-
Clone this repo:
git clone https://github.com/dan-smith-tech/rig.git
-
Run the setup for the relevant device:
just <device>
-
Reboot the device.
-
Open System Settings and configure:
- Global Theme Colours, Icons, Login Screen
- Keyboard -> Virtual Keyboard -> Plasma Keyboard
- Display & Monitor -> HDR
- Wallpaper (plus Layout set to
Desktop) - Screen Locking
- Power Management
- Users
- Session
-
Right click on the Panel and select
Panel Configurationto set up and manage the applets on the bar. -
Right click on the Panel and select
Add or Manage Widgets..., then select and remove the KDE Logo, Task Manager, Pager, and replace the Peek with a Margins Separator. -
Press
Meta + Tto configure the window layout settings. -
Create shortcuts:
- Plasma Workspace:
Meta
- Plasma Workspace:
-
Install the Proton Pass extension.
-
Sync settings at brave://settings/braveSync/setup.
-
Open brave://settings and configure:
- Get started:
- On startup
- Customise new tab page
- Appearance
- Content:
- Containers
- Shields
- Forget me
- Content filtering
- Social media blocking
- Privacy and security
- Data collection
- Leo
- Autofill and passwords
- Downloads
- System:
- Show full-screen reminder to press Esc on exit
- Memory saver
- Get started:
-
Customise the toolbar icons.
- Copy public key:
wl-copy < ~/.ssh/id_ed25519.pub
- Open GitHub Settings -> SSH and GPG keys
- Select
New SSH key, paste the content, and save. - Switch this repository to the SSH remote:
git remote set-url origin git@github.com:dan-smith-tech/rig.git
-
Open Settings -> AI -> MCP Servers and select the sign-in link for
context7. Zed opens a browser. -
Sign in with GitHub and authorise Context7.
-
Back in Zed,
context7should show as active and its tools available to the agent.
When the token expires, the server shows as offline and its tools stop working. Repeat the steps above to re-authenticate.
-
After a few days of usage check the token savings:
rtk gain
-
Add the following Gamescope settings to launch settings:
gamescope -W 3440 -H 1440 -r 144 --hdr-enabled --fullscreen --force-grab-cursor -- env DXVK_HDR=1 %command% --launcher-skip
-
Add a host entry to
~/.ssh/config(using the server's LAN IP - find it on the server withip a):Host homelab HostName 192.168.x.x User dan IdentityFile ~/.ssh/id_ed25519
-
Copy the public key to the clipboard:
wl-copy < ~/.ssh/id_ed25519.pub
-
Create the SSH directory and allow the client's public key:
mkdir -p ~/.ssh chmod 700 ~/.ssh nvim ~/.ssh/authorized_keys
Test from the client:
ssh homelab
- Disable CGN: A dedicated public IP is required. Disable "Carrier-Grade NAT" or "Large Scale NAT" in the router settings.
- Static IP or DynDNS:
- If a static public IP is available, use it directly as the client endpoint.
- Otherwise, set up DynDNS (No-IP) to give the changing public IP a stable hostname:
- Create a free account at No-IP.
- Add a hostname record (e.g.,
myhomelab.ddns.net) and enable Dynamic DNS. - In the router's DynDNS tab, set service to No-IP and enter the hostname, username, and password.
- Port Forwarding: Assign a static LAN IP to the homelab server, then add a NAT/PAT rule to the router:
- Internal/External Port:
51820 - Protocol:
UDP - Target: Server static LAN IP
- Internal/External Port:
-
Generate a keypair on both the server and the client:
umask 077 wg genkey > ~/.wg-private.key wg pubkey < ~/.wg-private.key > ~/.wg-public.key
-
Create
/etc/wireguard/wg0.conf:[Interface] Address = 192.168.2.1/24 ListenPort = 51820 PrivateKey = <contents of server ~/.wg-private.key> [Peer] PublicKey = <contents of client ~/.wg-public.key> AllowedIPs = 192.168.2.2/32
Add a separate
[Peer]block for each client, withAllowedIPsset to that client's tunnel address. -
Start the interface, and enable IP forwarding so LAN devices can reply to tunnel traffic:
sudo wg-quick up wg0 sudo systemctl enable wg-quick@wg0 echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-wireguard.conf sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
-
Persist the NAT rules for the
br0bridge across reboots:cat <<'EOF' | sudo tee /etc/nftables.conf > /dev/null table ip nat { chain postrouting { type nat hook postrouting priority srcnat; policy accept; oifname "br0" masquerade } } EOF sudo systemctl enable --now nftables
-
Create
/etc/wireguard/wg0.conf:[Interface] Address = 192.168.2.2/24 PrivateKey = <contents of client ~/.wg-private.key> [Peer] PublicKey = <contents of server ~/.wg-public.key> Endpoint = <no-ip-hostname-or-static-public-ip>:51820 AllowedIPs = 192.168.2.1/32, 192.168.1.0/24
The last octet of the client
Address(.2) is its index within the192.168.2.0/24subnet —.2for the first client,.3for the second, and so on. -
Connect to the tunnel
sudo wg-quick up wg0 sudo systemctl enable wg-quick@wg0
-
From a remote client, verify tunnel and LAN access:
ping 192.168.2.1 curl http://192.168.1.21:8123
-
Enable in BIOS (Advanced -> APM Configuration -> Power On by PCI-E).
-
Find the MAC address to use:
ip link show
The homelab doubles as the HTPC. Plasma is not started at boot. Home Assistant starts it over SSH when an app is chosen, and the app opens fullscreen. Choosing off stops the session.
-
In the Home Assistant VS Code add-on, open a terminal and generate a key into the config folder so it survives updates:
mkdir -p /config/.ssh ssh-keygen -t ed25519 -N "" -C homeassistant -f /config/.ssh/id_ed25519 cat /config/.ssh/id_ed25519.pub -
Copy the public key output.
-
On the homelab, add it to
~/.ssh/authorized_keys, prefixed so the key can only runhtpc-launch:restrict,command="/usr/local/bin/htpc-launch" ssh-ed25519 AAAA... homeassistant -
Reserve the homelab's IP address in the router, as the Home Assistant config below uses it.
-
Test from the Home Assistant terminal, using the homelab's LAN address (find it with
ip -4 addr show br0):ssh -i /config/.ssh/id_ed25519 dan@192.168.1.20 https://www.youtube.com/tv ssh -i /config/.ssh/id_ed25519 dan@192.168.1.20 off
Accept the host key on the first connection. The first command should start Plasma and Brave on the TV, and the second should stop them.
-
Add the shell command to
configuration.yaml, then check the configuration and restart. This step has no UI equivalent:shell_command: htpc_launch: "ssh -i /config/.ssh/id_ed25519 -o UserKnownHostsFile=/config/.ssh/known_hosts -o StrictHostKeyChecking=accept-new dan@192.168.1.20 '{{ app }}'"
-
Create the dropdown: Settings -> Devices & services -> Helpers -> Create helper -> Dropdown.
- Name:
HTPC app - Options:
Off,YouTube,Netflix,Prime - Icon:
mdi:television
This creates
input_select.htpc_app. - Name:
-
Create the
HTPC app changedautomation (Settings -> Automations & scenes). It runs wheninput_select.htpc_appchanges and does three things:- Toggles the TV power (
script.toggle_tv_power) when the choice andinput_boolean.is_tv_ondisagree: an app picked while the TV is off, orOffpicked while the TV is on. - Sends the URL to the HTPC via
shell_command.htpc_launch, looked up in aurl_mapautomation variable keyed by the dropdown option:YouTube: https://www.youtube.com/tv,Netflix: https://www.netflix.com,Prime: https://www.primevideo.com.Offis not in the map, so the lookup falls back tooffand the HTPC stops. The keys must match the option values exactly, case included. - Notifies the phone to launch the matching app: YouTube ReVanced for YouTube, KDE Connect for Netflix and Prime.
- Toggles the TV power (
-
Add the
HTPC appdropdown to a dashboard. In other automations, set it with theinput_select.select_optionaction.
-
Choose
YouTubein theHTPC appdropdown, plug in a keyboard, and open System Settings to configure: -
Screen Locking (disable)
-
Power Management (never sleep, never turn off the screen)
-
Display and Monitor (check the TV resolution and scale)
Use the phone as a mouse and keyboard to drive the HTPC. kdeconnect is installed and its daemon auto-starts with the Plasma session, so it only needs the session running.
- Install the KDE Connect app on the phone, enable it, and make sure the phone and HTPC share a network.
- Start the HTPC session (pick any app in the
HTPC appdropdown) and, for this one-time pairing, plug in a keyboard. - From the phone, request pairing with the HTPC and then mouse and keyboard, and accept the prompts on the HTPC itself.
- Choose
YouTubein theHTPC appdropdown. - With a keyboard (or KDE Connect), open Settings in the YouTube TV interface and choose
Link with TV code. - In the YouTube phone app, go to Settings -> Watch on TV -> Enter TV code.
- The pairing lives in
~/.config/brave-htpc, so it only needs doing once.
- Add the name to the
input_selectoptions in Home Assistant. - Add
Name: https://...to theurl_mapin the HTPC automation. - Optionally, add a
choosebranch in the automation so the phone launches the matching app. - Only if the app needs special browser flags: add a
caseinhtpc-sessioninside the justfile.