chore: link vault wiki to Gitea

This commit is contained in:
2026-03-11 11:20:12 -04:00
parent fe6ec20351
commit 9c22a661ea
54 changed files with 3069 additions and 3 deletions

View File

View File

@@ -0,0 +1,90 @@
---
title: "Linux Distro Guide for Beginners"
domain: linux
category: distro-specific
tags: [linux, distros, beginners, ubuntu, fedora, mint]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Linux Distro Guide for Beginners
If you're new to Linux and trying to figure out where to start, Ubuntu is the answer I give most often. I've been out of the beginner game for a while so there may be better options now, but Ubuntu has the widest community, the most documentation, and the best chance of finding a guide for whatever breaks on your hardware.
## The Short Answer
Start with **Ubuntu LTS** (the Long Term Support release). It's stable, well-documented, and has the largest community for getting help. Once you're comfortable, explore from there.
## Why Ubuntu for Beginners
Ubuntu hits the right marks for someone starting out:
- **Hardware support is broad.** Most laptops and desktops work out of the box or close to it, including NVIDIA drivers via the additional drivers tool.
- **Documentation everywhere.** Years of Ask Ubuntu, Ubuntu Forums, and community guides means almost any problem you hit has already been solved somewhere.
- **LTS releases are supported for 5 years.** You're not chasing upgrades every six months while you're still learning the basics.
- **Software availability.** Most Linux software either provides Ubuntu packages first or builds for it. Snap and Flatpak are both available.
```bash
# Check your Ubuntu version
lsb_release -a
# Update the system
sudo apt update && sudo apt upgrade
# Install software
sudo apt install packagename
```
## Other Distros Worth Knowing About
Once you've got your footing, the Linux ecosystem is wide. Here's how I'd categorize the common options:
**If Ubuntu feels like too much hand-holding:**
- **Fedora** — cutting edge packages, great for developers, ships very recent software. More DIY than Ubuntu but well-documented. My go-to for anything development-focused.
- **Linux Mint** — Ubuntu base with a more Windows-like desktop. Good if the Ubuntu GNOME interface feels unfamiliar.
**If you want something rolling (always up to date):**
- **Arch Linux** — you build it from scratch. Not for beginners, but you'll learn a lot. The Arch Wiki is the best Linux documentation that exists and is useful even if you're not running Arch.
- **Manjaro** — Arch base with an installer and some guardrails. Middle ground between Arch and something like Fedora.
**If you're building a server:**
- **Ubuntu Server** or **Debian** — rock solid, wide support, what most tutorials assume.
- **RHEL/AlmaLinux/Rocky Linux** — if you want to learn the Red Hat ecosystem for professional reasons.
## The Desktop Environment Question
Most beginners don't realize that Linux separates the OS from the desktop environment. Ubuntu ships with GNOME by default, but you can install others or pick a distro that comes with a different one.
| Desktop | Feel | Distro that ships it |
|---|---|---|
| GNOME | Modern, minimal, touch-friendly | Ubuntu, Fedora |
| KDE Plasma | Feature-rich, highly customizable | Kubuntu, KDE Neon |
| XFCE | Lightweight, traditional | Xubuntu, MX Linux |
| MATE | Classic, stable | Ubuntu MATE |
| Cinnamon | Windows-like | Linux Mint |
If you're not sure, start with whatever comes default on your chosen distro. You can always install another desktop later or try a different distro flavor.
## Getting Help
The community is the best part of Linux. When you get stuck:
- **Ask Ubuntu** (askubuntu.com) — for Ubuntu-specific questions
- **The Arch Wiki** — for general Linux concepts even if you're not on Arch
- **r/linux4noobs** — beginner-friendly community
- **Your distro's forums** — most major distros have their own
Be specific when asking for help. Include your distro and version, what you tried, and the exact error message. People can't help you with "it doesn't work."
## Gotchas & Notes
- **Don't dual-boot as your first step.** It adds complexity. Use a VM (VirtualBox, VMware) or a spare machine first until you're confident.
- **NVIDIA on Linux** can be annoying. Ubuntu's additional drivers GUI makes it manageable, but know that going in. AMD graphics tend to work better out of the box.
- **The terminal is your friend, not something to fear.** You'll use it. The earlier you get comfortable with basic commands, the easier everything gets.
- **Backups before you start.** If you're installing on real hardware, back up your data first. Not because Linux will eat it, but because installation steps can go sideways on any OS.
## See Also
- [[wsl2-instance-migration-fedora43]]
- [[managing-linux-services-systemd-ansible]]

View File

@@ -0,0 +1,101 @@
---
title: WSL2 Instance Migration (Fedora 43)
domain: linux
category: distro-specific
tags:
- wsl2
- fedora
- windows
- migration
- majorrig
status: published
created: '2026-03-06'
updated: '2026-03-08'
---
# WSL2 Instance Migration (Fedora 43)
To move a WSL2 distro from C: to another drive, export it to a tar file with `wsl --export`, unregister the original, then re-import it at the new location with `wsl --import`. After import you'll need to fix the default user — WSL always resets it to root on import, which you patch via `/etc/wsl.conf`.
## The Short Answer
```powershell
wsl --terminate Fedora-43
wsl --export Fedora-43 D:\fedora_backup.tar
wsl --unregister Fedora-43
mkdir D:\WSL\Fedora43
wsl --import Fedora-43 D:\WSL\Fedora43 D:\fedora_backup.tar --version 2
```
Then fix the default user — see Steps below.
## Background
WSL2 stores each distro as a VHDX on whatever drive it was installed to, which is C: by default. If you're running Unsloth fine-tuning runs or doing anything that generates large files in WSL2, C: fills up fast. The migration is straightforward but the import resets your default user to root, which you have to fix manually.
## Steps
1. Shut down the instance cleanly
```powershell
wsl --terminate Fedora-43
```
2. Export to the destination drive
```powershell
wsl --export Fedora-43 D:\fedora_backup.tar
```
3. Remove the C: instance
```powershell
wsl --unregister Fedora-43
```
4. Create the new directory and import
```powershell
mkdir D:\WSL\Fedora43
wsl --import Fedora-43 D:\WSL\Fedora43 D:\fedora_backup.tar --version 2
```
5. Fix the default user and enable systemd — edit `/etc/wsl.conf` inside the distro
```ini
[boot]
systemd=true
[user]
default=majorlinux
```
6. Restart WSL to apply
```powershell
wsl --shutdown
wsl -d Fedora-43
```
## Gotchas & Notes
- **Default user always resets to root on import** — this is expected WSL behavior. The `/etc/wsl.conf` fix is mandatory, not optional.
- **Windows Terminal profiles:** If the GUID changed after re-registration, update the profile. The command line stays the same: `wsl.exe -d Fedora-43`.
- **Verify the VHDX landed correctly:** Check `D:\WSL\Fedora43\ext4.vhdx` exists before deleting the backup tar.
- **Keep the tar until verified:** Don't delete `D:\fedora_backup.tar` until you've confirmed the migrated instance works correctly.
- **systemd=true is required for Fedora 43** — without it, services (including Docker and Ollama in WSL) won't start properly.
## Maintenance Aliases (DNF5)
Fedora 43 ships with DNF5. Add these to `~/.bashrc`:
```bash
alias update='sudo dnf upgrade --refresh'
alias install='sudo dnf install'
alias clean='sudo dnf clean all'
```
## See Also
- [[Managing disk space on MajorRig]]
- [[Unsloth QLoRA fine-tuning setup]]

View File

View File

@@ -0,0 +1,157 @@
---
title: "Linux File Permissions and Ownership"
domain: linux
category: files-permissions
tags: [permissions, chmod, chown, linux, acl, security]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Linux File Permissions and Ownership
File permissions are how Linux controls who can read, write, and execute files. Misunderstanding them is responsible for a lot of broken setups — both "why can't I access this" and "why is this insecure." This is the reference I'd want when something permission-related goes wrong.
## The Short Answer
```bash
# Change permissions
chmod 755 /path/to/file
chmod +x script.sh # add execute for all
chmod u+w file # add write for owner
chmod o-r file # remove read for others
# Change ownership
chown user file
chown user:group file
chown -R user:group /directory/ # recursive
```
## Reading Permission Bits
When you run `ls -la`, you see something like:
```
-rwxr-xr-- 1 major wheel 4096 Mar 8 10:00 myscript.sh
drwxr-xr-x 2 root root 4096 Mar 8 10:00 mydir/
```
The first column breaks down as:
```
- rwx r-x r--
│ │ │ └── Others: read only
│ │ └────── Group: read + execute
│ └────────── Owner: read + write + execute
└──────────── Type: - = file, d = directory, l = symlink
```
**Permission bits:**
| Symbol | Octal | Meaning |
|---|---|---|
| `r` | 4 | Read |
| `w` | 2 | Write |
| `x` | 1 | Execute (or traverse for directories) |
| `-` | 0 | No permission |
Common permission patterns:
| Octal | Symbolic | Common use |
|---|---|---|
| `755` | `rwxr-xr-x` | Executables, directories |
| `644` | `rw-r--r--` | Regular files |
| `600` | `rw-------` | Private files (SSH keys, config with credentials) |
| `700` | `rwx------` | Private directories (e.g., `~/.ssh`) |
| `777` | `rwxrwxrwx` | Everyone can do everything — almost never use this |
## chmod
```bash
# Symbolic mode
chmod u+x file # add execute for user (owner)
chmod g-w file # remove write from group
chmod o=r file # set others to read-only exactly
chmod a+r file # add read for all (user, group, other)
chmod ug=rw file # set user and group to read+write
# Octal mode
chmod 755 file # rwxr-xr-x
chmod 644 file # rw-r--r--
chmod 600 file # rw-------
# Recursive
chmod -R 755 /var/www/html/
```
## chown
```bash
# Change owner
chown major file
# Change owner and group
chown major:wheel file
# Change group only
chown :wheel file
# or
chgrp wheel file
# Recursive
chown -R major:major /home/major/
```
## Special Permissions
**Setuid (SUID):** Execute as the file owner, not the caller. Used by system tools like `sudo`.
```bash
chmod u+s /path/to/executable
# Shows as 's' in owner execute position: rwsr-xr-x
```
**Setgid (SGID):** Files created in a directory inherit the directory's group. Useful for shared directories.
```bash
chmod g+s /shared/directory
# Shows as 's' in group execute position
```
**Sticky bit:** Only the file owner can delete files in the directory. Used on `/tmp`.
```bash
chmod +t /shared/directory
# Shows as 't' in others execute position: drwxrwxrwt
```
## Finding and Fixing Permission Problems
```bash
# Find files writable by everyone
find /path -perm -o+w -type f
# Find SUID files (security audit)
find / -perm -4000 -type f 2>/dev/null
# Find files owned by a user
find /path -user major
# Fix common web server permissions (files 644, dirs 755)
find /var/www/html -type f -exec chmod 644 {} \;
find /var/www/html -type d -exec chmod 755 {} \;
```
## Gotchas & Notes
- **Directories need execute to traverse.** You can't `cd` into a directory without execute permission, even if you have read. This catches people off guard — `chmod 644` on a directory locks you out of it.
- **SSH is strict about permissions.** `~/.ssh` must be `700`, `~/.ssh/authorized_keys` must be `600`, and private keys must be `600`. SSH silently ignores keys with wrong permissions.
- **`chmod -R 777` is almost never the right answer.** If something isn't working because of permissions, find the actual issue. Blanket 777 creates security holes and usually breaks setuid/setgid behavior.
- **umask controls default permissions.** New files are created with `0666 & ~umask`, new directories with `0777 & ~umask`. The default umask is usually `022`, giving files `644` and directories `755`.
- **ACLs for more complex needs.** When standard user/group/other isn't enough (e.g., multiple users need different access to the same file), look at `setfacl` and `getfacl`.
## See Also
- [[linux-server-hardening-checklist]]
- [[ssh-config-key-management]]
- [[bash-scripting-patterns]]

View File

View File

@@ -0,0 +1,135 @@
---
title: "SSH Config and Key Management"
domain: linux
category: networking
tags: [ssh, keys, security, linux, remote-access]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# SSH Config and Key Management
SSH is how you get into remote servers. Key-based authentication is safer than passwords and, once set up, faster too. The `~/.ssh/config` file is what turns SSH from something you type long commands for into something that actually works the way you want.
## The Short Answer
```bash
# Generate a key (use ed25519 — it's faster and more secure than RSA now)
ssh-keygen -t ed25519 -C "yourname@hostname"
# Copy the public key to a server
ssh-copy-id user@server-ip
# SSH using a specific key
ssh -i ~/.ssh/id_ed25519 user@server-ip
```
## Key Generation
```bash
# ed25519 — preferred
ssh-keygen -t ed25519 -C "home-laptop"
# RSA 4096 — use this if the server is old and doesn't support ed25519
ssh-keygen -t rsa -b 4096 -C "home-laptop"
```
The `-C` comment is just a label — use something that tells you which machine the key came from. Comes in handy when you look at `authorized_keys` on a server and need to know what's what.
Keys land in `~/.ssh/`:
- `id_ed25519` — private key. **Never share this.**
- `id_ed25519.pub` — public key. This is what you put on servers.
## Copying Your Key to a Server
```bash
# Easiest way
ssh-copy-id user@server-ip
# If the server is on a non-standard port
ssh-copy-id -p 2222 user@server-ip
# Manual way (if ssh-copy-id isn't available)
cat ~/.ssh/id_ed25519.pub | ssh user@server-ip "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
```
After copying, test that key auth works before doing anything else, especially before disabling password auth.
## SSH Config File
`~/.ssh/config` lets you define aliases for servers so you can type `ssh myserver` instead of `ssh -i ~/.ssh/id_ed25519 -p 2222 admin@192.168.1.50`.
```
# ~/.ssh/config
# Home server
Host homelab
HostName 192.168.1.50
User admin
IdentityFile ~/.ssh/id_ed25519
Port 22
# Remote VPS
Host vps
HostName vps.yourdomain.com
User ubuntu
IdentityFile ~/.ssh/vps_key
Port 2222
# Jump host pattern — SSH through a bastion to reach internal servers
Host internal-server
HostName 10.0.0.50
User admin
ProxyJump bastion.yourdomain.com
# Default settings for all hosts
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
IdentityFile ~/.ssh/id_ed25519
```
After saving, `ssh homelab` connects with all those settings automatically.
## Managing Multiple Keys
One key per machine you connect from is reasonable. One key per server you connect to is overkill for personal use but correct for anything sensitive.
```bash
# List keys loaded in the SSH agent
ssh-add -l
# Add a key to the agent (so you don't type the passphrase every time)
ssh-add ~/.ssh/id_ed25519
# On macOS, persist the key in Keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
```
The SSH agent stores decrypted keys in memory for the session. You enter the passphrase once and the agent handles authentication for the rest of the session.
## Server-Side: Authorized Keys
Public keys live in `~/.ssh/authorized_keys` on the server. One key per line.
```bash
# Check permissions — wrong permissions break SSH key auth silently
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
```
If key auth isn't working and the config looks right, permissions are the first thing to check.
## Gotchas & Notes
- **Permissions must be right.** SSH ignores `authorized_keys` if the file or directory is world-writable. `chmod 700 ~/.ssh` and `chmod 600 ~/.ssh/authorized_keys` are required.
- **ed25519 over RSA.** ed25519 keys are shorter, faster, and currently considered more secure. Use them unless you have a compatibility reason not to.
- **Add a passphrase to your private key.** If your machine is compromised, an unprotected private key gives the attacker access to everything it's authorized on. A passphrase mitigates that.
- **`ServerAliveInterval` in your config** keeps connections from timing out on idle sessions. Saves you from the annoyance of reconnecting after stepping away.
- **Never put private keys in cloud storage, Git repos, or Docker images.** It happens more than you'd think.
## See Also
- [[linux-server-hardening-checklist]]
- [[managing-linux-services-systemd-ansible]]

0
01-linux/packages/.keep Normal file
View File

View File

@@ -0,0 +1,172 @@
---
title: "Linux Package Management Reference: apt, dnf, pacman"
domain: linux
category: packages
tags: [packages, apt, dnf, pacman, linux, distros]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Linux Package Management Reference: apt, dnf, pacman
Every major Linux distro has a package manager. The commands are different but the concepts are the same: install, remove, update, search. Here's the equivalent commands across the three you're most likely to encounter.
## Common Tasks by Package Manager
| Task | apt (Debian/Ubuntu) | dnf (Fedora/RHEL) | pacman (Arch) |
|---|---|---|---|
| Update package index | `apt update` | `dnf check-update` | `pacman -Sy` |
| Upgrade all packages | `apt upgrade` | `dnf upgrade` | `pacman -Su` |
| Update index + upgrade | `apt update && apt upgrade` | `dnf upgrade` | `pacman -Syu` |
| Install a package | `apt install pkg` | `dnf install pkg` | `pacman -S pkg` |
| Remove a package | `apt remove pkg` | `dnf remove pkg` | `pacman -R pkg` |
| Remove + config files | `apt purge pkg` | `dnf remove pkg` | `pacman -Rn pkg` |
| Search for a package | `apt search term` | `dnf search term` | `pacman -Ss term` |
| Show package info | `apt show pkg` | `dnf info pkg` | `pacman -Si pkg` |
| List installed | `apt list --installed` | `dnf list installed` | `pacman -Q` |
| Which pkg owns file | `dpkg -S /path/to/file` | `rpm -qf /path/to/file` | `pacman -Qo /path/to/file` |
| List files in pkg | `dpkg -L pkg` | `rpm -ql pkg` | `pacman -Ql pkg` |
| Clean cache | `apt clean` | `dnf clean all` | `pacman -Sc` |
## apt (Debian, Ubuntu, and derivatives)
```bash
# Always update before installing
sudo apt update
# Install
sudo apt install nginx
# Install multiple
sudo apt install nginx curl git
# Remove
sudo apt remove nginx
# Remove with config files
sudo apt purge nginx
# Autoremove orphaned dependencies
sudo apt autoremove
# Search
apt search nginx
# Show package details
apt show nginx
# Upgrade a specific package
sudo apt install --only-upgrade nginx
```
**APT sources** live in `/etc/apt/sources.list` and `/etc/apt/sources.list.d/`. Third-party PPAs go here. After adding a source, run `apt update` before installing from it.
## dnf (Fedora, RHEL, AlmaLinux, Rocky)
```bash
# Update everything
sudo dnf upgrade
# Install
sudo dnf install nginx
# Remove
sudo dnf remove nginx
# Search
dnf search nginx
# Info
dnf info nginx
# List groups (collections of packages)
dnf group list
# Install a group
sudo dnf group install "Development Tools"
# History — see what was installed and when
dnf history
dnf history info <id>
# Undo a transaction
sudo dnf history undo <id>
```
dnf's history and undo features are underused and genuinely useful when you've installed something that broke things.
## pacman (Arch, Manjaro)
```bash
# Full system update (do this before anything else, Arch is rolling)
sudo pacman -Syu
# Install
sudo pacman -S nginx
# Remove
sudo pacman -R nginx
# Remove with dependencies not needed by anything else
sudo pacman -Rs nginx
# Search
pacman -Ss nginx
# Info
pacman -Si nginx
# Query installed packages
pacman -Q # all installed
pacman -Qs nginx # search installed
pacman -Qi nginx # info on installed package
# Find orphaned packages
pacman -Qdt
# Clean package cache
sudo pacman -Sc # keep installed versions
sudo pacman -Scc # remove all cached packages
```
**AUR (Arch User Repository)** — packages not in the official repos. Use an AUR helper like `yay` or `paru`:
```bash
# Install yay
git clone https://aur.archlinux.org/yay.git
cd yay && makepkg -si
# Then use like pacman
yay -S package-name
```
## Flatpak and Snap (Distro-Agnostic)
For software that isn't in your distro's repos or when you want a sandboxed installation:
```bash
# Flatpak
flatpak install flathub com.spotify.Client
flatpak run com.spotify.Client
flatpak update
# Snap
sudo snap install spotify
sudo snap refresh
```
Flatpak is what I prefer — better sandboxing story, Flathub has most things you'd want. Snap works fine but the infrastructure is more centralized.
## Gotchas & Notes
- **Always `apt update` before `apt install`.** Installing from a stale index can grab outdated versions or fail entirely.
- **`apt upgrade` vs `apt full-upgrade`:** `full-upgrade` (or `dist-upgrade`) allows package removal to resolve conflicts. Use it for major upgrades. `upgrade` won't remove anything.
- **Arch is rolling — update frequently.** Partial upgrades on Arch cause breakage. Always do `pacman -Syu` (full update) before installing anything.
- **dnf is noticeably slower than apt on first run** due to metadata downloads. Gets faster after the cache is warm.
- **Don't mix package sources carelessly.** Adding random PPAs (apt) or COPR repos (dnf) can conflict with each other. Keep third-party sources to a minimum.
## See Also
- [[linux-distro-guide-beginners]]
- [[linux-server-hardening-checklist]]

View File

View File

@@ -0,0 +1,150 @@
---
title: "Managing Linux Services: systemd and Ansible"
domain: linux
category: process-management
tags: [systemd, ansible, services, linux, automation]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Managing Linux Services: systemd and Ansible
If you're running services on a Linux server, systemd is what you're working with day-to-day — starting, stopping, restarting, and checking on things. For managing services across multiple machines, Ansible is where I've landed. It handles the repetitive stuff so you can focus on what actually matters.
## The Short Answer
```bash
# Check a service status
systemctl status servicename
# Start / stop / restart
sudo systemctl start servicename
sudo systemctl stop servicename
sudo systemctl restart servicename
# Enable at boot
sudo systemctl enable servicename
# Disable at boot
sudo systemctl disable servicename
# Reload config without full restart (if supported)
sudo systemctl reload servicename
```
## systemd Basics
systemd is the init system on basically every major Linux distro now. Love it or not, it's what you're using. The `systemctl` command is your interface to it.
**Checking what's running:**
```bash
# List all active services
systemctl list-units --type=service --state=active
# List failed services (run this when something breaks)
systemctl list-units --type=service --state=failed
```
**Reading logs for a service:**
```bash
# Last 50 lines
journalctl -u servicename -n 50
# Follow live (like tail -f)
journalctl -u servicename -f
# Since last boot
journalctl -u servicename -b
```
`journalctl` is your friend. When a service fails, go here before you do anything else.
**Writing a simple service file:**
Drop a `.service` file in `/etc/systemd/system/` and systemd will pick it up.
```ini
[Unit]
Description=My Custom App
After=network.target
[Service]
Type=simple
User=myuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/start.sh
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
```
After creating or editing a service file, reload the daemon before doing anything else:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
```
## Ansible for Service Management Across Machines
For a single box, `systemctl` is fine. Once you've got two or more servers doing similar things, Ansible starts paying for itself fast. I've been using it heavily at work and it's changed how I think about managing infrastructure.
The `ansible.builtin.service` module handles start/stop/enable/disable:
```yaml
- name: Ensure nginx is started and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
```
A more complete playbook pattern:
```yaml
---
- name: Manage web services
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Start and enable nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Reload nginx after config change
ansible.builtin.service:
name: nginx
state: reloaded
```
Run it with:
```bash
ansible-playbook -i inventory.ini manage-services.yml
```
## Gotchas & Notes
- **daemon-reload is mandatory after editing service files.** Forgetting this is the most common reason changes don't take effect.
- **`restart` vs `reload`:** `restart` kills and relaunches the process. `reload` sends SIGHUP and asks the service to re-read its config without dropping connections — only works if the service supports it. nginx and most web servers do. Not everything does.
- **Ansible's `restarted` vs `reloaded` state:** Same distinction applies. Use `reloaded` in Ansible handlers when you're pushing config changes to a running service.
- **Checking if a service is masked:** A masked service can't be started at all. `systemctl status servicename` will tell you. Unmask with `sudo systemctl unmask servicename`.
- **On Fedora/RHEL:** SELinux can block a custom service from running even if systemd says it started fine. If you see permission errors in `journalctl`, check `ausearch -m avc` for SELinux denials.
## See Also
- [[wsl2-instance-migration-fedora43]]
- [[tuning-netdata-web-log-alerts]]

View File

View File

@@ -0,0 +1,208 @@
---
title: "Ansible Getting Started: Inventory, Playbooks, and Ad-Hoc Commands"
domain: linux
category: shell-scripting
tags: [ansible, automation, infrastructure, linux, idempotent]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Ansible Getting Started: Inventory, Playbooks, and Ad-Hoc Commands
Ansible is how I manage infrastructure at scale — or even just across a handful of machines. You write what you want the end state to look like, Ansible figures out how to get there. No agents needed on the remote machines, just SSH.
## The Short Answer
```bash
# Install Ansible
pip install ansible
# Run a one-off command on all hosts
ansible all -i inventory.ini -m ping
# Run a playbook
ansible-playbook -i inventory.ini site.yml
```
## Core Concepts
**Inventory** — the list of machines Ansible manages. Can be a static file or dynamically generated.
**Playbook** — a YAML file describing tasks to run on hosts. The main thing you write.
**Module** — the building blocks of tasks. `apt`, `dnf`, `service`, `copy`, `template`, `user`, etc. Ansible has modules for almost everything.
**Idempotency** — run the same playbook ten times, the result is the same as running it once. Ansible modules are designed this way. This matters because it means you can re-run playbooks safely without side effects.
## Inventory File
```ini
# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_user=admin
[databases]
db1.example.com ansible_user=ubuntu ansible_port=2222
[all:vars]
ansible_user=myuser
ansible_ssh_private_key_file=~/.ssh/id_ed25519
```
Test connectivity:
```bash
ansible all -i inventory.ini -m ping
```
A successful response looks like:
```
web1.example.com | SUCCESS => {
"ping": "pong"
}
```
## Ad-Hoc Commands
For quick one-offs without writing a playbook:
```bash
# Run a shell command
ansible all -i inventory.ini -m shell -a "uptime"
# Install a package
ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become
# Restart a service
ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" --become
# Copy a file
ansible all -i inventory.ini -m copy -a "src=./myfile dest=/tmp/myfile"
```
`--become` escalates to sudo.
## Writing a Playbook
```yaml
---
# site.yml
- name: Configure web servers
hosts: webservers
become: true
vars:
app_port: 8080
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Start and enable nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Deploy config file
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
```
Run it:
```bash
ansible-playbook -i inventory.ini site.yml
# Dry run — shows what would change without doing it
ansible-playbook -i inventory.ini site.yml --check
# Verbose output
ansible-playbook -i inventory.ini site.yml -v
```
## Handlers
Handlers run at the end of a play, only if notified. The canonical use is "reload service after config change":
```yaml
tasks:
- name: Deploy config
ansible.builtin.template:
src: templates/app.conf.j2
dest: /etc/app/app.conf
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.service:
name: myapp
state: restarted
```
If the config file didn't change (idempotent — it was already in the right state), the notify never fires and the service isn't restarted.
## Roles
Once playbooks get complex, organize them into roles:
```
roles/
webserver/
tasks/
main.yml
handlers/
main.yml
templates/
nginx.conf.j2
defaults/
main.yml
```
Use a role in a playbook:
```yaml
- name: Set up web servers
hosts: webservers
become: true
roles:
- webserver
```
Roles keep things organized and reusable across projects.
## Gotchas & Notes
- **YAML indentation matters.** Two spaces is standard. Tab characters will break your playbooks.
- **`--check` is your friend.** Always dry-run against production before applying changes.
- **SSH key access is required.** Ansible connects over SSH — password auth works but key auth is what you want for automation.
- **`gather_facts: false`** speeds up playbooks when you don't need host facts (OS, IP, etc.). Add it at the play level for simple playbooks.
- **Ansible is not idempotent by magic.** Shell and command modules run every time regardless of state. Use the appropriate module (`apt`, `service`, `file`, etc.) instead of `shell` whenever possible.
- **The `ansible-lint` tool** catches common mistakes before they run. Worth adding to your workflow.
## See Also
- [[managing-linux-services-systemd-ansible]]
- [[linux-server-hardening-checklist]]

View File

@@ -0,0 +1,215 @@
---
title: "Bash Scripting Patterns for Sysadmins"
domain: linux
category: shell-scripting
tags: [bash, scripting, automation, linux, shell]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Bash Scripting Patterns for Sysadmins
These are the patterns I reach for when writing bash scripts for server automation and maintenance tasks. Not a tutorial from scratch — this assumes you know basic bash syntax and want to write scripts that don't embarrass you later.
## The Short Answer
```bash
#!/usr/bin/env bash
set -euo pipefail
```
Start every script with these two lines. `set -e` exits on error. `set -u` treats unset variables as errors. `set -o pipefail` catches errors in pipes. Together they prevent a lot of silent failures.
## Script Header
```bash
#!/usr/bin/env bash
set -euo pipefail
# ── Config ─────────────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
LOG_FILE="/var/log/myscript.log"
# ───────────────────────────────────────────────────────────────────────────────
```
## Logging
```bash
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
log "Script started"
log "Processing $1"
```
## Error Handling
```bash
# Exit with a message
die() {
echo "ERROR: $*" >&2
exit 1
}
# Check a condition
[ -f "$CONFIG_FILE" ] || die "Config file not found: $CONFIG_FILE"
# Trap to run cleanup on exit
cleanup() {
log "Cleaning up temp files"
rm -f "$TMPFILE"
}
trap cleanup EXIT
```
## Checking Dependencies
```bash
check_deps() {
local deps=("curl" "jq" "rsync")
for dep in "${deps[@]}"; do
command -v "$dep" &>/dev/null || die "Required dependency not found: $dep"
done
}
check_deps
```
## Argument Parsing
```bash
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [OPTIONS] <target>
Options:
-v, --verbose Enable verbose output
-n, --dry-run Show what would be done without doing it
-h, --help Show this help
EOF
exit 0
}
VERBOSE=false
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose) VERBOSE=true; shift ;;
-n|--dry-run) DRY_RUN=true; shift ;;
-h|--help) usage ;;
--) shift; break ;;
-*) die "Unknown option: $1" ;;
*) TARGET="$1"; shift ;;
esac
done
[[ -z "${TARGET:-}" ]] && die "Target is required"
```
## Running Commands
```bash
# Dry-run aware command execution
run() {
if $DRY_RUN; then
echo "DRY RUN: $*"
else
"$@"
fi
}
run rsync -av /source/ /dest/
run systemctl restart myservice
```
## Working with Files and Directories
```bash
# Check existence before use
[[ -d "$DIR" ]] || mkdir -p "$DIR"
[[ -f "$FILE" ]] || die "Expected file not found: $FILE"
# Safe temp files
TMPFILE="$(mktemp)"
trap 'rm -f "$TMPFILE"' EXIT
# Loop over files
find /path/to/files -name "*.log" -mtime +30 | while read -r file; do
log "Processing: $file"
run gzip "$file"
done
```
## String Operations
```bash
# Extract filename without extension
filename="${filepath##*/}" # basename
stem="${filename%.*}" # strip extension
# Check if string contains substring
if [[ "$output" == *"error"* ]]; then
die "Error detected in output"
fi
# Convert to lowercase
lower="${str,,}"
# Trim whitespace
trimmed="${str#"${str%%[![:space:]]*}"}"
```
## Common Patterns
**Backup with timestamp:**
```bash
backup() {
local source="$1"
local dest="${2:-/backup}"
local timestamp
timestamp="$(date '+%Y%m%d_%H%M%S')"
local backup_path="${dest}/$(basename "$source")_${timestamp}.tar.gz"
log "Backing up $source to $backup_path"
run tar -czf "$backup_path" -C "$(dirname "$source")" "$(basename "$source")"
}
```
**Retry on failure:**
```bash
retry() {
local max_attempts="${1:-3}"
local delay="${2:-5}"
shift 2
local attempt=1
until "$@"; do
if ((attempt >= max_attempts)); then
die "Command failed after $max_attempts attempts: $*"
fi
log "Attempt $attempt failed, retrying in ${delay}s..."
sleep "$delay"
((attempt++))
done
}
retry 3 10 curl -f https://example.com/health
```
## Gotchas & Notes
- **Always quote variables.** `"$var"` not `$var`. Unquoted variables break on spaces and glob characters.
- **Use `[[` not `[` for conditionals.** `[[` is a bash built-in with fewer edge cases.
- **`set -e` exits on the first error — including in pipes.** Add `set -o pipefail` or you'll miss failures in `cmd1 | cmd2`.
- **`$?` after `if` is almost always wrong.** Use `if command; then` not `command; if [[ $? -eq 0 ]]; then`.
- **Bash isn't great for complex data.** If your script needs real data structures or error handling beyond strings, consider Python.
## See Also
- [[ansible-getting-started]]
- [[managing-linux-services-systemd-ansible]]

0
01-linux/storage/.keep Normal file
View File

View File

View File

@@ -0,0 +1,145 @@
---
title: "Tailscale for Homelab Remote Access"
domain: selfhosting
category: dns-networking
tags: [tailscale, vpn, remote-access, wireguard, homelab]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Tailscale for Homelab Remote Access
Tailscale is how I access my home services from anywhere. It creates a private encrypted mesh network between all your devices — no port forwarding, no dynamic DNS, no exposing anything to the internet. It just works, which is what you want from networking infrastructure.
## The Short Answer
Install Tailscale on every device you want connected. Sign in with the same account. Done — all devices can reach each other by hostname.
```bash
# Install on Linux
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# Check status
tailscale status
# See your devices and IPs
tailscale status --json | jq '.Peer[] | {Name: .HostName, IP: .TailscaleIPs[0]}'
```
## How It Works
Tailscale sits on top of WireGuard — the modern, fast, audited VPN protocol. Each device gets a `100.x.x.x` address on your tailnet (private network). Traffic between devices is encrypted WireGuard tunnels, peer-to-peer when possible, routed through Tailscale's DERP relay servers when direct connection isn't possible (restrictive NAT, cellular, etc.).
The key thing: your home server's services never need to be exposed to the public internet. They stay bound to `localhost` or your LAN IP, and Tailscale makes them accessible from your other devices over the tailnet.
## MagicDNS
Enable MagicDNS in the Tailscale admin console (login.tailscale.com → DNS → Enable MagicDNS). This assigns each device a stable hostname based on its machine name.
```
# Instead of remembering 100.64.x.x
http://homelab:3000
# Or the full MagicDNS name (works from anywhere on the tailnet)
http://homelab.tail-xxxxx.ts.net:3000
```
No manual DNS configuration on any device. When Tailscale is running, hostnames resolve automatically.
## Installation by Platform
**Linux (server/desktop):**
```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
```
**macOS:**
Download from Mac App Store or tailscale.com/download/mac. Sign in through the menu bar app.
**iOS/iPadOS:**
App Store → Tailscale. Sign in, enable the VPN.
**Windows:**
Download installer from tailscale.com. Runs as a system service.
## Making Services Accessible Over Tailscale
By default, Docker services bind to `0.0.0.0` — they're already reachable on the Tailscale interface. Verify:
```bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
```
Look for `0.0.0.0:PORT` in the output. If you see `127.0.0.1:PORT`, the service is bound to localhost only and won't be reachable. Fix it in the compose file by removing the `127.0.0.1:` prefix from the port mapping.
Ollama on Linux defaults to localhost. Override it:
```bash
# Add to /etc/systemd/system/ollama.service.d/override.conf
[Service]
Environment="OLLAMA_HOST=0.0.0.0"
```
```bash
sudo systemctl daemon-reload && sudo systemctl restart ollama
```
## Access Control
By default, all devices on your tailnet can reach all other devices. For a personal homelab this is fine. If you want to restrict access, Tailscale ACLs (in the admin console) let you define which devices can reach which others.
Simple ACL example — allow all devices to reach all others (the default):
```json
{
"acls": [
{"action": "accept", "src": ["*"], "dst": ["*:*"]}
]
}
```
More restrictive — only your laptop can reach the server:
```json
{
"acls": [
{
"action": "accept",
"src": ["tag:laptop"],
"dst": ["tag:server:*"]
}
]
}
```
## Subnet Router (Optional)
If you want all your home LAN devices accessible over Tailscale (not just devices with Tailscale installed), set up a subnet router on your home server:
```bash
# Advertise your home subnet
sudo tailscale up --advertise-routes=192.168.1.0/24
# Approve the route in the admin console
# Then on client devices, enable accepting routes:
sudo tailscale up --accept-routes
```
Now any device on your home LAN is reachable from anywhere on the tailnet, even if Tailscale isn't installed on that device.
## Gotchas & Notes
- **Tailscale is not a replacement for a firewall.** It secures device-to-device communication, but your server still needs proper firewall rules for LAN access.
- **Devices need to be approved in the admin console** unless you've enabled auto-approval. If a new device can't connect, check the admin console first.
- **Mobile devices will disconnect in the background** depending on OS settings. iOS aggressively kills VPN connections. Enable background app refresh for Tailscale in iOS Settings.
- **DERP relay adds latency** when direct connections aren't possible (common on cellular). Still encrypted and functional, just slower than direct peer-to-peer.
- **Exit nodes** let you route all traffic through a specific tailnet device — useful as a simple home VPN if you want all your internet traffic going through your home IP when traveling. Set with `--advertise-exit-node` on the server and `--exit-node=hostname` on the client.
## See Also
- [[self-hosting-starter-guide]]
- [[linux-server-hardening-checklist]]
- [[setting-up-caddy-reverse-proxy]]

View File

View File

@@ -0,0 +1,168 @@
---
title: "Debugging Broken Docker Containers"
domain: selfhosting
category: docker
tags: [docker, troubleshooting, debugging, containers, logs]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Debugging Broken Docker Containers
When something in Docker breaks, there's a sequence to work through. Check logs first, inspect the container second, deal with network and permissions third. Resist the urge to rebuild immediately — most failures tell you what's wrong if you look.
## The Short Answer
```bash
# Step 1: check logs
docker logs containername
# Step 2: inspect the container
docker inspect containername
# Step 3: get a shell inside
docker exec -it containername /bin/bash
# or /bin/sh if bash isn't available
```
## The Debugging Sequence
### 1. Check the logs first
Before anything else:
```bash
docker logs containername
# Follow live output
docker logs -f containername
# Last 100 lines
docker logs --tail 100 containername
# With timestamps
docker logs --timestamps containername
```
Most failures announce themselves in the logs. Crash loops, config errors, missing environment variables — it's usually right there.
### 2. Check if the container is actually running
```bash
docker ps
# Show stopped containers too
docker ps -a
```
If the container shows as `Exited (1)` or any non-zero exit code, it crashed. The logs will usually tell you why.
### 3. Inspect the container
`docker inspect` dumps everything — environment variables, mounts, network config, the actual command being run:
```bash
docker inspect containername
```
Too verbose? Filter for the part you need:
```bash
# Just the mounts
docker inspect --format='{{json .Mounts}}' containername | jq
# Just the environment variables
docker inspect --format='{{json .Config.Env}}' containername | jq
# Exit code
docker inspect --format='{{.State.ExitCode}}' containername
```
### 4. Get a shell inside
If the container is running but misbehaving:
```bash
docker exec -it containername /bin/bash
```
If it crashed and won't stay up, override the entrypoint to get in anyway:
```bash
docker run -it --entrypoint /bin/bash imagename:tag
```
### 5. Check port conflicts
Container starts but the service isn't reachable:
```bash
# See what ports are mapped
docker ps --format "table {{.Names}}\t{{.Ports}}"
# See what's using a port on the host
sudo ss -tlnp | grep :8080
```
Two containers can't bind the same host port. If something else grabbed the port first, the container will start but the port won't be accessible.
### 6. Check volume permissions
Permission errors inside containers are almost always a UID mismatch. The user inside the container doesn't own the files on the host volume.
```bash
# Check ownership of the mounted directory on the host
ls -la /path/to/host/volume
# Find out what UID the container runs as
docker inspect --format='{{.Config.User}}' containername
```
Fix by chowning the host directory to match, or by explicitly setting the user in your compose file:
```yaml
services:
myapp:
image: myapp:latest
user: "1000:1000"
volumes:
- ./data:/app/data
```
### 7. Recreate cleanly when needed
If you've made config changes and things are in a weird state:
```bash
docker compose down
docker compose up -d
# Nuclear option — also removes volumes (data gone)
docker compose down -v
```
Don't jump straight to the nuclear option. Only use `-v` if you want a completely clean slate and don't care about the data.
## Common Failure Patterns
| Symptom | Likely cause | Where to look |
|---|---|---|
| Exits immediately | Config error, missing env var | `docker logs` |
| Keeps restarting | Crash loop — app failing to start | `docker logs`, exit code |
| Port not reachable | Port conflict or wrong binding | `docker ps`, `ss -tlnp` |
| Permission denied inside container | UID mismatch on volume | `ls -la` on host path |
| "No such file or directory" | Wrong mount path or missing file | `docker inspect` mounts |
| Container runs but service is broken | App config error, not Docker | shell in, check app logs |
## Gotchas & Notes
- **`docker restart` doesn't pick up compose file changes.** Use `docker compose up -d` to apply changes.
- **Logs persist after a container exits.** You can still `docker logs` a stopped container — useful for post-mortem on crashes.
- **If a container won't stay up long enough to exec into it**, use the entrypoint override (`--entrypoint /bin/bash`) with `docker run` against the image directly.
- **Watch out for cached layers on rebuild.** If you're rebuilding an image and the behavior doesn't change, add `--no-cache` to `docker build`.
## See Also
- [[docker-vs-vms-homelab]]
- [[tuning-netdata-web-log-alerts]]

View File

@@ -0,0 +1,95 @@
---
title: "Docker vs VMs in the Homelab: Why Not Both?"
domain: selfhosting
category: docker
tags: [docker, vm, homelab, virtualization, containers]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Docker vs VMs in the Homelab: Why Not Both?
People treat this like an either/or decision. It's not. Docker and VMs solve different problems and the right homelab runs both. Here's how I think about which one to reach for.
## The Short Answer
Use Docker for services. Use VMs for things that need full OS isolation, a different kernel, or Windows. Run them side by side — they're complementary, not competing.
## What Docker Is Good At
Docker containers are great for running services — apps, databases, reverse proxies, monitoring stacks. They start fast, they're easy to move, and Docker Compose makes multi-service setups manageable with a single file.
```yaml
# docker-compose.yml — a simple example
services:
app:
image: myapp:latest
ports:
- "8080:8080"
volumes:
- ./data:/app/data
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pgdata:
```
The key advantages:
- **Density** — you can run a lot of containers on modest hardware
- **Portability** — move a service to another machine by copying the compose file and a data directory
- **Isolation from other services** (but not from the host kernel)
- **Easy updates** — pull a new image, recreate the container
## What VMs Are Good At
VMs give you a completely separate kernel and OS. That matters when:
- You need a **Windows environment** on Linux hardware (gaming server, specific Windows-only tools)
- You're running something that needs a **different kernel version** than the host
- You want **stronger isolation** — a compromised container can potentially escape to the host, a compromised VM is much harder to escape
- You're testing a full OS install, distro setup, or something destructive
- You need **hardware passthrough** — GPU, USB devices, etc.
On Linux, KVM + QEMU is the stack. `virt-manager` gives you a GUI if you want it.
```bash
# Install KVM stack on Fedora/RHEL
sudo dnf install qemu-kvm libvirt virt-install virt-manager
# Start and enable the libvirt daemon
sudo systemctl enable --now libvirtd
# Verify KVM is available
sudo virt-host-validate
```
## How I Actually Use Both
In practice:
- **Self-hosted services** (Nextcloud, Gitea, Jellyfin, monitoring stacks) → Docker Compose
- **Gaming/Windows stuff that needs the real deal** → VM with GPU passthrough
- **Testing a new distro or destructive experiments** → VM, snapshot before anything risky
- **Network appliances** (pfSense, OPNsense) → VM, not a container
The two coexist fine on the same host. Docker handles the service layer, KVM handles the heavier isolation needs.
## Gotchas & Notes
- **Containers share the host kernel.** That's a feature for performance and density, but it means a kernel exploit affects everything on the host. For sensitive workloads, VM isolation is worth the overhead.
- **Networking gets complicated when both are running.** Docker creates its own bridge networks, KVM does the same. Know which traffic is going where. Naming your Docker networks explicitly helps.
- **Backups are different.** Backing up a Docker service means backing up volumes + the compose file. Backing up a VM means snapshotting the QCOW2 disk file. Don't treat them the same.
- **Don't run Docker inside a VM on your homelab unless you have a real reason.** It works, but you're layering virtualization overhead for no benefit in most cases.
## See Also
- [[managing-linux-services-systemd-ansible]]
- [[tuning-netdata-web-log-alerts]]

View File

@@ -0,0 +1,115 @@
---
title: "Self-Hosting Starter Guide"
domain: selfhosting
category: docker
tags: [selfhosting, homelab, docker, getting-started, privacy]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Self-Hosting Starter Guide
Self-hosting is running your own services on hardware you control instead of depending on someone else's platform. It's more work than just signing up for a SaaS product, but you own your data, you don't have to worry about a company changing their pricing or disappearing, and there's no profit motive to do weird things with your stuff.
This is where to start if you want in but don't know where to begin.
## What You Actually Need
You don't need a rack full of servers. A single machine with Docker is enough to run most things people want to self-host. Options in rough order of entry cost:
- **Old laptop or desktop** — works fine for home use, just leave it on
- **Raspberry Pi or similar SBC** — low power, always-on, limited compute
- **Mini PC** (Intel NUC, Beelink, etc.) — better than SBC, still low power, actually useful
- **Old server hardware** — more capable, louder, more power draw
- **VPS** — if you want public internet access and don't want to deal with networking
For a first setup, a mini PC or a spare desktop you already have is the right call.
## The Core Stack
Three tools cover almost everything:
**Docker** — runs your services as containers. One command to start a service, one command to update it.
**Docker Compose** — defines multi-service setups in a YAML file. Most self-hosted apps publish a `compose.yml` you can use directly.
**A reverse proxy** (Nginx Proxy Manager, Caddy, or Traefik) — routes traffic to your services by hostname or path, handles SSL certificates.
```bash
# Install Docker (Linux)
curl -fsSL https://get.docker.com | sh
# Add your user to the docker group (so you don't need sudo every time)
sudo usermod -aG docker $USER
# Log out and back in after this
# Verify
docker --version
docker compose version
```
## Starting a Service
Most self-hosted apps have a Docker Compose file in their documentation. The pattern is the same for almost everything:
1. Create a directory for the service
2. Put the `compose.yml` in it
3. Run `docker compose up -d`
Example — Uptime Kuma (monitoring):
```yaml
# uptime-kuma/compose.yml
services:
uptime-kuma:
image: louislam/uptime-kuma:latest
ports:
- "3001:3001"
volumes:
- ./data:/app/data
restart: unless-stopped
```
```bash
mkdir uptime-kuma && cd uptime-kuma
# paste the compose.yml
docker compose up -d
# open http://your-server-ip:3001
```
## What to Self-Host First
Start with things that are low-stakes and high-value:
- **Uptime Kuma** — monitors your other services, alerts when things go down. Easy to set up, immediately useful.
- **Portainer** — web UI for managing Docker. Makes it easier to see what's running and pull updates.
- **Vaultwarden** — self-hosted Bitwarden-compatible password manager. Your passwords on your hardware.
- **Nextcloud** — file sync and storage. Replaces Dropbox/Google Drive.
- **Jellyfin** — media server for your own video library.
Don't try to spin everything up at once. Get one service working, understand how it runs, then add the next one.
## Networking Basics
By default, services are only accessible on your home network. You have options for accessing them remotely:
- **Tailscale** — install on your server and your other devices, everything is accessible over a private encrypted network. Zero port forwarding required. This is what I use.
- **Cloudflare Tunnel** — exposes services publicly through Cloudflare's network without opening ports. Good if you want things internet-accessible without exposing your home IP.
- **Port forwarding** — traditional method, opens a port on your router to the server. Works but exposes your home IP.
Tailscale is the easiest and safest starting point for personal use.
## Gotchas & Notes
- **Persistent storage:** Always map volumes for your service's data directory. If you run a container without a volume and it gets recreated, your data is gone.
- **Restart policies:** Use `restart: unless-stopped` on services you want to survive reboots. `always` also works but will restart even if you manually stopped the container.
- **Updates:** Pull the new image and recreate the container. `docker compose pull && docker compose up -d` is the standard pattern. Check the app's changelog first for anything that requires migration steps.
- **Backups:** Self-hosting means you're responsible for your own backups. Back up the data directories for your services regularly. The `compose.yml` files should be in version control or backed up separately.
- **Don't expose everything to the internet.** If you don't need public access to a service, don't create it. Tailscale for private access is safer than punching holes in your firewall.
## See Also
- [[docker-vs-vms-homelab]]
- [[debugging-broken-docker-containers]]
- [[linux-server-hardening-checklist]]

View File

View File

@@ -0,0 +1,88 @@
---
title: Tuning Netdata Web Log Alerts
domain: selfhosting
category: monitoring
tags:
- netdata
- apache
- monitoring
- alerts
- ubuntu
status: published
created: '2026-03-06'
updated: '2026-03-08'
---
# Tuning Netdata Web Log Alerts
To stop Netdata's `web_log_1m_redirects` alert from firing on normal HTTP-to-HTTPS redirect traffic, edit `/etc/netdata/health.d/web_log.conf` and raise the redirect threshold to 80%, then reload with `netdatacli reload-health`. The default threshold is too sensitive for any server that forces HTTPS — automated traffic hits port 80, gets a 301, and Netdata flags it as a WARNING even though nothing is wrong.
## The Short Answer
```bash
sudo /etc/netdata/edit-config health.d/web_log.conf
```
Change the `warn` line in the `web_log_1m_redirects` template to:
```bash
warn: ($web_log_1m_requests > 120) ? ($this > (($status >= $WARNING ) ? ( 1 ) : ( 80 )) ) : ( 0 )
```
Then reload:
```bash
netdatacli reload-health
```
## Background
Production nodes forcing HTTPS see a lot of 301s. The default Netdata threshold is too sensitive for sites with a high bot-to-human ratio — it was designed for environments where redirects are unexpected, not standard operating procedure.
The tuned logic warns only when there are more than 120 requests/minute AND redirects exceed 80% of traffic (dropping to 1% once already in WARNING state to prevent flapping).
## Steps
1. Identify what's actually generating the redirects
```bash
# Check status code distribution
awk '{print $9}' /var/log/apache2/access.log | sort | uniq -c | sort -nr
# Top redirected URLs
awk '$9 == "301" {print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -n 10
```
2. Open the Netdata health config
```bash
sudo /etc/netdata/edit-config health.d/web_log.conf
```
3. Find the `web_log_1m_redirects` template and update the `warn` line
```bash
warn: ($web_log_1m_requests > 120) ? ($this > (($status >= $WARNING ) ? ( 1 ) : ( 80 )) ) : ( 0 )
```
4. Reload without restarting the service
```bash
netdatacli reload-health
```
5. Verify the alert cleared
```bash
curl -s http://localhost:19999/api/v1/alarms?all | grep -A 15 "web_log_1m_redirec"
```
## Gotchas & Notes
- **Ubuntu/Debian only:** The `edit-config` path and `apache2` log location are Debian-specific. On Fedora/RHEL the log is at `/var/log/httpd/access_log`.
- **The 1% recovery threshold is intentional:** Without it, the alert will flap between WARNING and CLEAR constantly on busy sites. The hysteresis keeps it stable once triggered.
- **Adjust the 120 req/min floor to your traffic:** Low-traffic sites may need a lower threshold; high-traffic sites may need higher.
## See Also
- [[Netdata service monitoring]]

View File

View File

@@ -0,0 +1,140 @@
---
title: "Setting Up a Reverse Proxy with Caddy"
domain: selfhosting
category: reverse-proxy
tags: [caddy, reverse-proxy, ssl, https, selfhosting]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Setting Up a Reverse Proxy with Caddy
A reverse proxy sits in front of your services and routes traffic to the right one based on hostname or path. It also handles SSL certificates so you don't have to manage them per-service. Caddy is the one I reach for first because it gets HTTPS right automatically with zero configuration.
## The Short Answer
```bash
# Install Caddy (Debian/Ubuntu)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
Or via Docker (simpler for a homelab):
```yaml
services:
caddy:
image: caddy:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
```
## The Caddyfile
Caddy's config format is refreshingly simple compared to nginx. A Caddyfile that proxies two services:
```
# Caddyfile
nextcloud.yourdomain.com {
reverse_proxy localhost:8080
}
jellyfin.yourdomain.com {
reverse_proxy localhost:8096
}
```
That's it. Caddy automatically gets TLS certificates from Let's Encrypt for both hostnames, handles HTTP-to-HTTPS redirects, and renews certificates before they expire. No certbot, no cron jobs.
## For Local/Home Network Use (Without a Public Domain)
If you don't have a public domain or want to use Caddy purely on your LAN:
**Option 1: Local hostnames with a self-signed certificate**
```
:443 {
tls internal
reverse_proxy localhost:8080
}
```
Caddy generates a local CA and signs the cert. You'll get browser warnings unless you trust the Caddy root CA on your devices. Run `caddy trust` to install it locally.
**Option 2: Use a real domain with DNS challenge (home IP, no port forwarding)**
If you own a domain but don't want to expose your home IP:
```
nextcloud.yourdomain.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy localhost:8080
}
```
This uses a DNS challenge instead of HTTP — Caddy creates a TXT record to prove domain ownership, so no ports need to be open. Requires the Caddy DNS plugin for your registrar.
## Reloading After Changes
```bash
# Validate config before reloading
caddy validate --config /etc/caddy/Caddyfile
# Reload without restart
caddy reload --config /etc/caddy/Caddyfile
# Or with systemd
sudo systemctl reload caddy
```
## Adding Headers and Custom Options
```
yourdomain.com {
reverse_proxy localhost:3000
# Security headers
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
# Increase timeout for slow upstream
reverse_proxy localhost:3000 {
transport http {
response_header_timeout 120s
}
}
}
```
## Gotchas & Notes
- **Ports 80 and 443 must be open.** For automatic HTTPS, Caddy needs to reach Let's Encrypt. If you're behind a firewall, open those ports. For local-only setups, use `tls internal` instead.
- **The `caddy_data` volume is important.** Caddy stores its certificates there. Lose the volume, lose the certs (they'll be re-issued but it causes brief downtime).
- **Caddy hot-reloads config cleanly.** Unlike nginx, `caddy reload` doesn't drop connections. Safe to use on production.
- **Docker networking:** When proxying to other Docker containers, use the container name or Docker network IP instead of `localhost`. If everything is in the same compose stack, use the service name: `reverse_proxy app:8080`.
- **nginx comparison:** nginx is more widely documented and more feature-complete for edge cases, but Caddy's automatic HTTPS and simpler config makes it faster to set up for personal use. Both are good choices.
## See Also
- [[self-hosting-starter-guide]]
- [[linux-server-hardening-checklist]]
- [[debugging-broken-docker-containers]]

View File

View File

@@ -0,0 +1,208 @@
---
title: "Linux Server Hardening Checklist"
domain: selfhosting
category: security
tags: [security, hardening, linux, ssh, firewall, server]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Linux Server Hardening Checklist
When I set up a fresh Linux server, there's a standard set of things I do before I put anything on it. None of this is exotic — it's the basics that prevent the most common attacks. Do these before the server touches the public internet.
## The Short Answer
New server checklist: create a non-root user, disable root SSH login, use key-based auth only, configure a firewall, keep packages updated. That covers 90% of what matters.
## 1. Create a Non-Root User
Don't work as root. Create a user, give it sudo:
```bash
# Create user
adduser yourname
# Add to sudo group (Debian/Ubuntu)
usermod -aG sudo yourname
# Add to wheel group (Fedora/RHEL)
usermod -aG wheel yourname
```
Log out and log back in as that user before doing anything else.
## 2. SSH Key Authentication
Passwords over SSH are a liability. Set up key-based auth and disable password login.
On your local machine, generate a key if you don't have one:
```bash
ssh-keygen -t ed25519 -C "yourname@hostname"
```
Copy the public key to the server:
```bash
ssh-copy-id yourname@server-ip
```
Or manually append your public key to `~/.ssh/authorized_keys` on the server.
Test that key auth works **before** disabling passwords.
## 3. Harden sshd_config
Edit `/etc/ssh/sshd_config`:
```
# Disable root login
PermitRootLogin no
# Disable password authentication
PasswordAuthentication no
# Disable empty passwords
PermitEmptyPasswords no
# Limit to specific users (optional but good)
AllowUsers yourname
# Change the port (optional — reduces log noise, not real security)
Port 2222
```
Restart SSH after changes:
```bash
sudo systemctl restart sshd
```
Keep your current session open when testing — if you lock yourself out you'll need console access to fix it.
## 4. Configure a Firewall
**ufw (Ubuntu/Debian):**
```bash
sudo apt install ufw
# Default: deny incoming, allow outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (use your actual port if you changed it)
sudo ufw allow 22/tcp
# Allow whatever services you're running
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable
sudo ufw enable
# Check status
sudo ufw status verbose
```
**firewalld (Fedora/RHEL):**
```bash
sudo systemctl enable --now firewalld
# Allow SSH
sudo firewall-cmd --permanent --add-service=ssh
# Allow HTTP/HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Apply changes
sudo firewall-cmd --reload
# Check
sudo firewall-cmd --list-all
```
## 5. Keep Packages Updated
Security patches come through package updates. Automate this or do it regularly:
```bash
# Ubuntu/Debian — manual
sudo apt update && sudo apt upgrade
# Enable unattended security upgrades (Ubuntu)
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
# Fedora/RHEL — manual
sudo dnf upgrade
# Enable automatic updates (Fedora)
sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic.timer
```
## 6. Fail2ban
Fail2ban watches log files and bans IPs that fail authentication too many times. Helps with brute force noise.
```bash
# Ubuntu/Debian
sudo apt install fail2ban
# Fedora/RHEL
sudo dnf install fail2ban
# Start and enable
sudo systemctl enable --now fail2ban
```
Create `/etc/fail2ban/jail.local` to override defaults:
```ini
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
```
```bash
sudo systemctl restart fail2ban
# Check status
sudo fail2ban-client status sshd
```
## 7. Disable Unnecessary Services
Less running means less attack surface:
```bash
# See what's running
systemctl list-units --type=service --state=active
# Disable something you don't need
sudo systemctl disable --now servicename
```
Common ones to disable on a dedicated server: `avahi-daemon`, `cups`, `bluetooth`.
## Gotchas & Notes
- **Don't lock yourself out.** Test SSH key auth in a second terminal before disabling passwords. Keep the original session open.
- **If you changed the SSH port**, make sure the firewall allows the new port before restarting sshd. Block the old port after you've confirmed the new one works.
- **fail2ban and Docker don't always play nicely.** Docker bypasses iptables rules in some configurations. If you're running services in Docker, test that fail2ban is actually seeing traffic.
- **SELinux on RHEL/Fedora** may block things your firewall allows. Check `ausearch -m avc` if a service stops working after hardening.
- **This is a baseline, not a complete security posture.** For anything holding sensitive data, also look at: disk encryption, intrusion detection (AIDE, Tripwire), log shipping to a separate system, and regular audits.
## See Also
- [[managing-linux-services-systemd-ansible]]
- [[debugging-broken-docker-containers]]

View File

View File

View File

@@ -0,0 +1,162 @@
---
title: "rsync Backup Patterns"
domain: selfhosting
category: storage-backup
tags: [rsync, backup, linux, storage, automation]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# rsync Backup Patterns
rsync is the tool for moving files on Linux. Fast, resumable, bandwidth-efficient — it only transfers what changed. For local and remote backups, it's what I reach for first.
## The Short Answer
```bash
# Sync source to destination (local)
rsync -av /source/ /destination/
# Sync to remote server
rsync -avz /source/ user@server:/destination/
# Dry run first — see what would change
rsync -avnP /source/ /destination/
```
## Core Flags
| Flag | What it does |
|---|---|
| `-a` | Archive mode: preserves permissions, timestamps, symlinks, owner/group. Use this almost always. |
| `-v` | Verbose output — shows files being transferred. |
| `-z` | Compress during transfer. Useful over slow connections, overhead on fast LAN. |
| `-P` | Progress + partial transfers (resumes interrupted transfers). |
| `-n` | Dry run — shows what would happen without doing it. |
| `--delete` | Removes files from destination that no longer exist in source. Makes it a true mirror. |
| `--exclude` | Exclude patterns. |
| `--bwlimit` | Limit bandwidth in KB/s. |
## Local Backup
```bash
# Basic sync
rsync -av /home/major/ /backup/home/
# Mirror — destination matches source exactly (deletes removed files)
rsync -av --delete /home/major/ /backup/home/
# Exclude directories
rsync -av --exclude='.cache' --exclude='Downloads' /home/major/ /backup/home/
# Multiple excludes from a file
rsync -av --exclude-from=exclude.txt /home/major/ /backup/home/
```
**The trailing slash matters:**
- `/source/` — sync the contents of source into destination
- `/source` — sync the source directory itself into destination (creates `/destination/source/`)
Almost always want the trailing slash on the source.
## Remote Backup
```bash
# Local → remote
rsync -avz /home/major/ user@server:/backup/major/
# Remote → local (pull backup)
rsync -avz user@server:/var/data/ /local/backup/data/
# Specify SSH port or key
rsync -avz -e "ssh -p 2222 -i ~/.ssh/id_ed25519" /source/ user@server:/dest/
```
## Incremental Backups with Hard Links
The `--link-dest` pattern creates space-efficient incremental backups. Each backup looks like a full copy but only stores changed files — unchanged files are hard links to previous versions.
```bash
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/backup"
SOURCE="/home/major"
DATE="$(date +%Y-%m-%d)"
LATEST="${BACKUP_DIR}/latest"
DEST="${BACKUP_DIR}/${DATE}"
rsync -av --delete \
--link-dest="$LATEST" \
"$SOURCE/" \
"$DEST/"
# Update the 'latest' symlink
rm -f "$LATEST"
ln -s "$DEST" "$LATEST"
```
Each dated directory looks like a complete backup. Storage is only used for changed files. You can delete any dated directory without affecting others.
## Backup Script with Logging
```bash
#!/usr/bin/env bash
set -euo pipefail
SOURCE="/home/major/"
DEST="/backup/home/"
LOG="/var/log/rsync-backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
log "Backup started"
rsync -av --delete \
--exclude='.cache' \
--exclude='Downloads' \
--log-file="$LOG" \
"$SOURCE" "$DEST"
log "Backup complete"
```
Run via cron:
```bash
# Daily at 2am
0 2 * * * /usr/local/bin/backup.sh
```
Or systemd timer (preferred):
```ini
# /etc/systemd/system/rsync-backup.timer
[Unit]
Description=Daily rsync backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
```
```bash
sudo systemctl enable --now rsync-backup.timer
```
## Gotchas & Notes
- **Test with `--dry-run` first.** Especially when using `--delete`. See what would be removed before actually removing it.
- **`--delete` is destructive.** It removes files from the destination that don't exist in the source. That's the point, but know what you're doing.
- **Large files and slow connections:** Add `-P` for progress and partial transfer resume. An interrupted rsync picks up where it left off.
- **For network backups to untrusted locations**, consider using rsync over SSH + encryption at rest. rsync over SSH handles transit encryption; storage encryption is separate.
- **rsync vs Restic:** rsync is fast and simple. Restic gives you deduplication, encryption, and multiple backend support (S3, B2, etc.). For local backups, rsync. For offsite with encryption needs, Restic.
## See Also
- [[self-hosting-starter-guide]]
- [[bash-scripting-patterns]]

View File

View File

View File

View File

View File

0
04-streaming/audio/.keep Normal file
View File

View File

View File

0
04-streaming/obs/.keep Normal file
View File

View File

@@ -0,0 +1,132 @@
---
title: "OBS Studio Setup and Encoding Settings"
domain: streaming
category: obs
tags: [obs, streaming, encoding, twitch, youtube, linux]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# OBS Studio Setup and Encoding Settings
OBS Studio is the standard for streaming and recording — open source, cross-platform, and capable of everything you'd need for a production setup. The defaults are fine to get started but getting encoding settings right matters for stream quality and CPU/GPU load.
## Installation
**Linux:**
```bash
# Fedora
sudo dnf install obs-studio
# Ubuntu/Debian (flatpak recommended for latest version)
flatpak install flathub com.obsproject.Studio
# Ubuntu PPA (if you want apt)
sudo add-apt-repository ppa:obsproject/obs-studio
sudo apt update && sudo apt install obs-studio
```
**Windows/macOS:** Download from obsproject.com.
## Encoding Settings
The most important settings are in Settings → Output → Streaming.
**If you have an NVIDIA GPU (recommended):**
| Setting | Value |
|---|---|
| Encoder | NVENC H.264 (or AV1 if streaming to YouTube) |
| Rate Control | CBR |
| Bitrate | 6000 Kbps (Twitch max), up to 20000+ for YouTube |
| Keyframe Interval | 2 |
| Preset | Quality |
| Profile | high |
| Look-ahead | Enable |
| Psycho Visual Tuning | Enable |
| GPU | 0 |
| Max B-frames | 2 |
**If using CPU (x264):**
| Setting | Value |
|---|---|
| Encoder | x264 |
| Rate Control | CBR |
| Bitrate | 6000 Kbps |
| Keyframe Interval | 2 |
| CPU Usage Preset | veryfast or superfast |
| Profile | high |
| Tune | zerolatency |
NVENC offloads encoding to the GPU, leaving CPU free for the game/application. Use it whenever available. x264 on `veryfast` is a reasonable CPU fallback if your GPU doesn't support hardware encoding.
## Output Resolution and FPS
Settings → Video:
| Setting | Value |
|---|---|
| Base (Canvas) Resolution | Match your monitor (e.g., 1920×1080) |
| Output (Scaled) Resolution | 1920×1080 (or 1280×720 for lower bitrate streams) |
| Downscale Filter | Lanczos (best quality) or Bilinear (fastest) |
| Common FPS | 60 (or 30 if bandwidth-limited) |
For most Twitch streams: 1080p60 or 720p60 at 6000 Kbps. 1080p60 at 6000 Kbps is pushing it for fast-motion content — if you're seeing compression artifacts, drop to 720p60.
## Scene Setup
A basic streaming setup uses two scenes at minimum:
**Live scene** — your main content:
- Game Capture or Window Capture (Windows/macOS) or Screen Capture (Linux/Wayland)
- Browser Source for alerts/overlays
- Audio input (mic)
- Desktop audio
**BRB/Starting Soon scene** — a static image or video loop for transitions.
Add sources with the + button in the Sources panel. Order matters — sources higher in the list appear on top.
## Audio Setup
Settings → Audio:
- Desktop Audio: set to your main audio output
- Mic/Auxiliary Audio: set to your microphone
In the mixer, use the gear icon per source to apply filters:
- **Noise Suppression** (RNNoise): reduces background noise significantly
- **Noise Gate**: cuts audio below a threshold — stops background hiss when you're not talking
- **Compressor**: evens out volume levels
- **Gain**: boosts a quiet mic
Apply them in that order. Noise suppression first, gate second, compressor third.
## Linux-Specific Notes
**Wayland capture:** OBS on Wayland requires either the PipeWire screen capture plugin or using X11 compatibility mode (`obs --use-x11` or setting the env var `QT_QPA_PLATFORM=xcb`). The Flatpak version handles this better than the native package on some distros.
**Virtual camera on Linux:**
```bash
# Load the v4l2loopback kernel module
sudo modprobe v4l2loopback devices=1 video_nr=10 card_label="OBS Virtual Camera" exclusive_caps=1
# Make it persist across reboots
echo "v4l2loopback" | sudo tee /etc/modules-load.d/v4l2loopback.conf
echo "options v4l2loopback devices=1 video_nr=10 card_label=OBS Virtual Camera exclusive_caps=1" | sudo tee /etc/modprobe.d/v4l2loopback.conf
```
## Gotchas & Notes
- **Test your stream before going live.** Record a short clip and watch it back. Artifacts in the recording will be worse in the stream.
- **Keyframe interval at 2 is required by Twitch.** Other values cause issues with their ingest servers.
- **High CPU Usage Preset for x264 makes streams look better but uses more CPU.** `veryfast` is usually the sweet spot — `fast` and `medium` are noticeably heavier for marginal quality gain.
- **NVENC quality has improved significantly.** Old advice says x264 is better quality. That was true in 2018. Current NVENC (RTX series) is competitive with x264 at reasonable bitrates.
- **OBS logs** are in Help → Log Files. When something isn't working, this is where to look first.
## See Also
- [[linux-file-permissions]]
- [[bash-scripting-patterns]]

View File

View File

View File

View File

View File

@@ -0,0 +1,129 @@
---
title: ISP SNI Filtering Blocking Caddy Reverse Proxy
domain: troubleshooting
category: networking
tags:
- caddy
- tls
- sni
- isp
- google-fiber
- reverse-proxy
- troubleshooting
status: published
created: '2026-03-11'
updated: '2026-03-11'
---
# ISP SNI Filtering Blocking Caddy Reverse Proxy
Some ISPs — including Google Fiber — silently block TLS handshakes for certain hostnames at the network level. The connection reaches your server, TCP completes, but the TLS handshake never finishes. The symptom looks identical to a misconfigured Caddy setup or a missing certificate, which makes it a frustrating thing to debug.
## What Happened
Deployed a new Caddy vhost for `wiki.majorshouse.com` on a Google Fiber residential connection. Everything on the server was correct:
- Let's Encrypt cert provisioned successfully
- Caddy validated clean with `caddy validate`
- `curl --resolve wiki.majorshouse.com:443:127.0.0.1 https://wiki.majorshouse.com` returned 200 from loopback
- iptables had ACCEPT rules for ports 80 and 443
- All other Caddy vhosts on the same IP and port worked fine externally
But from any external host, `curl` timed out with no response. `ss -tn` showed SYN-RECV connections piling up on port 443 — the TCP handshake was completing, but the TLS handshake was stalling.
## The Debugging Sequence
**Step 1: Ruled out Caddy config issues**
```bash
caddy validate --config /etc/caddy/Caddyfile
curl --resolve wiki.majorshouse.com:443:127.0.0.1 https://wiki.majorshouse.com
```
Both clean. Loopback returned 200.
**Step 2: Ruled out certificate issues**
```bash
ls /var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wiki.majorshouse.com/
openssl x509 -in wiki.majorshouse.com.crt -noout -text | grep -E "Subject:|Not Before|Not After"
```
Valid cert, correct subject, not expired.
**Step 3: Ruled out firewall**
```bash
iptables -L INPUT -n -v | grep -E "80|443"
ss -tlnp | grep ':443'
```
Ports open, Caddy listening on `*:443`.
**Step 4: Ruled out hairpin NAT**
Testing `curl https://wiki.majorshouse.com` from the server itself returned "No route to host" — the server can't reach its own public IP. This is normal for residential connections without NAT loopback. It's not the problem.
**Step 5: Confirmed external connectivity on port 443**
```bash
# From an external server (majormail)
curl -sk -o /dev/null -w "%{http_code}" https://git.majorshouse.com # 200
curl -sk -o /dev/null -w "%{http_code}" https://wiki.majorshouse.com # 000
```
Same IP, same port, same Caddy process. `git` works, `wiki` doesn't.
**Step 6: Tested a different subdomain**
Added `notes.majorshouse.com` as a new Caddyfile entry pointing to the same upstream. Cert provisioned via HTTP-01 challenge successfully (proving port 80 is reachable). Then:
```bash
curl -sk -o /dev/null -w "%{http_code}" https://notes.majorshouse.com # 200
curl -sk -o /dev/null -w "%{http_code}" https://wiki.majorshouse.com # 000
```
`notes` worked immediately. `wiki` still timed out.
**Conclusion:** Google Fiber is performing SNI-based filtering and blocking TLS connections where the ClientHello contains `wiki.majorshouse.com` as the server name.
## The Fix
Rename the subdomain. Use anything that doesn't trigger the filter. `notes.majorshouse.com` works fine.
```bash
# Remove the blocked entry
sed -i '/^wiki\.majorshouse\.com/,/^}/d' /etc/caddy/Caddyfile
systemctl reload caddy
```
Update `mkdocs.yml` or whatever service's config references the domain, add DNS for the new subdomain, and done.
## How to Diagnose This Yourself
If your Caddy vhost works on loopback but times out externally:
1. Confirm other vhosts on the same IP and port work externally
2. Test the specific domain from multiple external networks (different ISP, mobile data)
3. Add a second vhost with a different subdomain pointing to the same upstream
4. If the new subdomain works and the original doesn't, the hostname is being filtered
```bash
# Quick external test — run from a server outside your network
curl -sk -o /dev/null -w "%{http_code}" --max-time 10 https://your-domain.com
```
If you get `000` (connection timeout, not a TLS error like `curl: (35)`), the TCP connection isn't completing — pointing to network-level blocking rather than a Caddy or cert issue.
## Gotchas & Notes
- **`curl: (35) TLS error` is different from `000`.** A TLS error means TCP connected but the handshake failed — usually a missing or invalid cert. A `000` timeout means TCP never completed — a network or firewall issue.
- **SYN-RECV in `ss -tn` means TCP is partially open.** If you see SYN-RECV entries for your domain but the connection never moves to ESTAB, something between the client and your TLS stack is dropping the handshake.
- **ISP SNI filtering is uncommon but real.** Residential ISPs sometimes filter on SNI for terms associated with piracy, proxies, or certain categories of content. "Wiki" may trigger a content-type heuristic.
- **Loopback testing isn't enough.** Always test from an external host before declaring a service working. The server can't test its own public IP on most residential connections.
## See Also
- [[setting-up-caddy-reverse-proxy]]
- [[linux-server-hardening-checklist]]
- [[tailscale-homelab-remote-access]]

View File

View File

@@ -0,0 +1,116 @@
---
tags:
- obsidian
- troubleshooting
- windows
- majortwin
created: '2026-03-11'
status: resolved
---
# Obsidian Vault Recovery — Loading Cache Hang
## Problem
Obsidian refused to open MajorVault, hanging indefinitely on "Loading cache" with no progress. The issue began with an `EACCES` permission error on a Python venv symlink inside the vault, then persisted even after the offending files were removed.
## Root Causes
Two compounding issues caused the hang:
1. **79GB of ML project files inside the vault.** The `20-Projects/MajorTwin` directory contained model weights, training artifacts, and venvs that Obsidian tried to index on every launch. Specifically:
- `06-Models` — ~39GB of model weights
- `09-Artifacts` — ~38GB of training artifacts
- `10-Training` — ~1.8GB of training data
- `11-Tools` — Python venvs and llama.cpp builds (with broken symlinks on Windows)
2. **Stale Electron app data.** After Obsidian attempted to index the 79GB, it wrote corrupt state into its global app data (`%APPDATA%\obsidian`). This persisted across vault config resets and caused the hang even after the large files were removed.
A secondary contributing factor was `"open": true` in `obsidian.json`, which forced Obsidian to resume the broken session on every launch.
## Resolution Steps
### 1. Remove large non-note directories from the vault
```powershell
Move-Item "C:\Users\majli\Documents\MajorVault\20-Projects\MajorTwin\06-Models" "D:\MajorTwin\06-Models"
Move-Item "C:\Users\majli\Documents\MajorVault\20-Projects\MajorTwin\09-Artifacts" "D:\MajorTwin\09-Artifacts"
Move-Item "C:\Users\majli\Documents\MajorVault\20-Projects\MajorTwin\10-Training" "D:\MajorTwin\10-Training"
Remove-Item -Recurse -Force "C:\Users\majli\Documents\MajorVault\20-Projects\MajorTwin\11-Tools\venv-unsloth"
Remove-Item -Recurse -Force "C:\Users\majli\Documents\MajorVault\20-Projects\MajorTwin\11-Tools\llama.cpp"
```
### 2. Reset the vault config
```powershell
Rename-Item "C:\Users\majli\Documents\MajorVault\.obsidian" "C:\Users\majli\Documents\MajorVault\.obsidian.bak"
```
### 3. Fix the open flag in obsidian.json
```powershell
'{"vaults":{"9147b890194dceb0":{"path":"C:\\Users\\majli\\Documents\\MajorVault","ts":1773207898521,"open":false}}}' | Set-Content "$env:APPDATA\obsidian\obsidian.json"
```
### 4. Wipe Obsidian global app data (the key fix)
```powershell
Stop-Process -Name "Obsidian" -Force -ErrorAction SilentlyContinue
Rename-Item "$env:APPDATA\obsidian" "$env:APPDATA\obsidian.bak"
```
### 5. Launch Obsidian and reselect the vault
Obsidian will treat it as a fresh install. Select MajorVault — it should load cleanly.
### 6. Restore vault config and plugins
```powershell
Copy-Item "$env:APPDATA\obsidian.bak\obsidian.json" "$env:APPDATA\obsidian\obsidian.json"
Copy-Item "C:\Users\majli\Documents\MajorVault\.obsidian.bak\*.json" "C:\Users\majli\Documents\MajorVault\.obsidian\"
Copy-Item "C:\Users\majli\Documents\MajorVault\.obsidian.bak\plugins.bak" "C:\Users\majli\Documents\MajorVault\.obsidian\plugins" -Recurse
```
### 7. Clean up backups
```powershell
Remove-Item -Recurse -Force "$env:APPDATA\obsidian.bak"
Remove-Item -Recurse -Force "C:\Users\majli\Documents\MajorVault\.obsidian.bak"
```
## Prevention
### Add a .obsidianignore file to the vault root
```
20-Projects/MajorTwin/06-Models
20-Projects/MajorTwin/09-Artifacts
20-Projects/MajorTwin/10-Training
20-Projects/MajorTwin/11-Tools
```
### Add exclusions in Obsidian settings
Settings → Files & Links → Excluded files → add `20-Projects/MajorTwin/11-Tools`
### Keep ML project files off the vault entirely
Model weights, venvs, training artifacts, and datasets do not belong in Obsidian. Store them on D drive or in WSL2. WSL2 (Fedora43) can access D drive at `/mnt/d/MajorTwin/`.
## Key Diagnostic Commands
```powershell
# Check vault size by top-level directory
Get-ChildItem "C:\Users\majli\Documents\MajorVault" -Directory | ForEach-Object {
$size = (Get-ChildItem $_.FullName -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
[PSCustomObject]@{ Name = $_.Name; SizeMB = [math]::Round($size/1MB, 1) }
} | Sort-Object SizeMB -Descending
# Check obsidian.json vault state
Get-Content "$env:APPDATA\obsidian\obsidian.json"
# Check Obsidian log
Get-Content "$env:APPDATA\obsidian\obsidian.log" -Tail 50
# Check if Obsidian is running/frozen
Get-Process -Name "Obsidian" | Select-Object CPU, WorkingSet, PagedMemorySize
```

View File

View File

@@ -0,0 +1,137 @@
# yt-dlp YouTube JS Challenge Fix (Fedora)
## Problem
When running `yt-dlp` on Fedora, downloads may fail with the following warnings and errors:
```
WARNING: [youtube] No supported JavaScript runtime could be found.
WARNING: [youtube] [jsc:deno] Challenge solver lib script version 0.3.2 is not supported (supported version: 0.4.0)
WARNING: [youtube] 0qhgPKRzlvs: n challenge solving failed: Some formats may be missing.
ERROR: Did not get any data blocks
ERROR: fragment 1 not found, unable to continue
```
This causes subtitle downloads (and sometimes video formats) to fail silently, with the MP4 completing but subtitles being skipped.
### Root Causes
1. **No JavaScript runtime installed** — yt-dlp requires Deno or Node.js to solve YouTube's JS challenges
2. **Outdated yt-dlp** — the bundled challenge solver script is behind the required version
3. **Remote challenge solver not enabled** — the updated solver script must be explicitly fetched
---
## Fix
### 1. Install Deno
Deno is not in the Fedora repos. Install via the official installer:
```bash
curl -fsSL https://deno.land/install.sh | sh
sudo mv ~/.deno/bin/deno /usr/local/bin/deno
deno --version
```
> Alternatively, Node.js works and is available via `sudo dnf install nodejs`.
### 2. Update yt-dlp
```bash
sudo pip install -U yt-dlp --break-system-packages
```
> If installed via standalone binary: `yt-dlp -U`
### 3. Enable Remote Challenge Solver
Add `--remote-components ejs:github` to the yt-dlp command. This fetches the latest JS challenge solver from GitHub at runtime:
```bash
yt-dlp -f 'bestvideo[vcodec^=avc]+bestaudio[ext=m4a]/bestvideo+bestaudio' \
--merge-output-format mp4 \
-o "/plex/plex/%(title)s.%(ext)s" \
--write-auto-subs --embed-subs \
--remote-components ejs:github \
https://www.youtube.com/watch?v=VIDEO_ID
```
### 4. Persist the Config
Create a yt-dlp config file so `--remote-components` is applied automatically:
```bash
mkdir -p ~/.config/yt-dlp
echo '--remote-components ejs:github' > ~/.config/yt-dlp/config
```
---
## Maintenance
YouTube pushes extractor changes frequently. Keep yt-dlp current:
```bash
sudo pip install -U yt-dlp --break-system-packages
```
---
## Tags
#yt-dlp #fedora #youtube #plex #self-hosted
## Known Limitations
### n-Challenge Failure: "found 0 n function possibilities"
Even with Deno installed, the remote solver downloaded, and yt-dlp up to date, some YouTube player versions can still fail n-challenge solving:
```
WARNING: [youtube] [jsc] Error solving n challenge request using "deno" provider:
Error running deno process (returncode: 1): error: Uncaught (in promise)
"found 0 n function possibilities".
WARNING: [youtube] n challenge solving failed: Some formats may be missing.
ERROR: [youtube] Requested format is not available.
```
This is a known upstream issue tied to specific YouTube player builds (e.g. `e42f4bf8`). It is not fixable locally — it requires a yt-dlp patch when YouTube rotates the player.
**Workaround:** Use a permissive format fallback instead of forcing AVC:
```bash
yt-dlp -f 'bestvideo+bestaudio/best' \
--merge-output-format mp4 \
-o "/plex/plex/%(title)s.%(ext)s" \
--write-auto-subs --embed-subs \
--remote-components ejs:github \
https://www.youtube.com/watch?v=VIDEO_ID
```
This lets yt-dlp pick the best available format rather than failing on a missing AVC stream. To inspect what formats are actually available:
```bash
yt-dlp --list-formats --remote-components ejs:github \
https://www.youtube.com/watch?v=VIDEO_ID
```
### SABR-Only Streaming Warning
Some videos may show:
```
WARNING: [youtube] Some android_vr client https formats have been skipped as they
are missing a URL. YouTube may have enabled the SABR-only streaming experiment.
```
This is a YouTube-side experiment. yt-dlp falls back to other clients automatically — no action needed.
### pip Version Check
`pip show` does not accept `--break-system-packages`. Run separately:
```bash
yt-dlp --version
pip show yt-dlp
```

View File

@@ -0,0 +1,52 @@
---
title: MajorWiki Deployment Status
status: deployed
project: MajorTwin
updated: '2026-03-11'
---
# MajorWiki Deployment Status
## Status: Deployed ✅
**URL:** https://notes.majorshouse.com
**Host:** majorlab (`/opt/majwiki`)
**Port:** 8092 (internal), proxied via Caddy
**Engine:** MkDocs Material (squidfunk/mkdocs-material)
## Stack
- Docker Compose at `/opt/majwiki/compose.yml`
- `network_mode: host` — container binds directly to host network
- MkDocs serves on `0.0.0.0:8092`
- Caddy proxies `notes.majorshouse.com → :8092`
- Let's Encrypt cert provisioned 2026-03-11, valid until 2026-06-09
## Why notes.majorshouse.com
`wiki.majorshouse.com` was provisioned and configured correctly but Google Fiber
blocks TLS handshakes for SNI hostnames containing "wiki" at the ISP level.
`notes.majorshouse.com` works fine on the same IP and port. `wiki.majorshouse.com`
DNS record and Caddy entry have been removed.
## Content
- 15 articles across 4 domains
- Source of truth: `MajorVault/20-Projects/MajorTwin/08-Wiki/`
- Deployed via rsync from MajorRig WSL2
## Update Workflow
```bash
# From MajorRig (majorlinux user)
rsync -av --include="*.md" --include="*/" --exclude="*" \
/mnt/c/Users/majli/Documents/MajorVault/20-Projects/MajorTwin/08-Wiki/ \
root@majorlab:/opt/majwiki/docs/
# MkDocs hot-reloads automatically — no container restart needed
```
## Backlog
- [ ] Gitea webhook for auto-deploy on push
- [ ] Add `03-opensource` and `05-troubleshooting` articles

29
Network/overview.md Normal file
View File

@@ -0,0 +1,29 @@
# 🌐 Network Overview
The **[[MajorInfrastructure|MajorsHouse]]** infrastructure is connected via a private **[[Network Overview#Tailscale|Tailscale]]** mesh network. This allows secure, peer-to-peer communication between devices across different geographic locations (US and UK) without exposing services to the public internet.
## 🏛️ Infrastructure Summary
- **Address Space:** 100.x.x.x (Tailscale CGNAT)
- **Management:** Centralized via **[[Network Overview#Ansible|Ansible]]** (`MajorAnsible` repo)
- **Host Groupings:** Functional (web, mail, homelab, bots), OS (Fedora, Ubuntu), and Location (US, UK).
## 🌍 Geographic Nodes
| Host | Location | IP | OS |
|---|---|---|---|
| `[[dca|dca]]` | 🇺🇸 US | 100.104.11.146 | Ubuntu 24.04 |
| `[[majortoot|majortoot]]` | 🇺🇸 US | 100.110.197.17 | Ubuntu 24.04 |
| `[[majorhome|majorhome]]` | 🇺🇸 US | 100.120.209.106 | Fedora 43 |
| `[[teelia|teelia]]` | 🇬🇧 UK | 100.120.32.69 | Ubuntu 24.04 |
## 🔗 Tailscale Setup
Tailscale is configured as a persistent service on all nodes. Key features used include:
- **Tailscale SSH:** Enabled for secure management via Ansible.
- **MagicDNS:** Used for internal hostname resolution (e.g., `majorlab.tailscale.net`).
- **ACLs:** Managed via the Tailscale admin console to restrict cross-group communication where necessary.
---
*Last updated: 2026-03-04*

View File

@@ -1,3 +0,0 @@
# MajorWiki
MajorLinux Tech Wiki — MkDocs source

0
_drafts/.keep Normal file
View File

125
index.md Normal file
View File

@@ -0,0 +1,125 @@
---
title: "MajorLinux Tech Wiki — Index"
updated: 2026-03-08
---
# MajorLinux Tech Wiki — Index
> A growing reference of Linux, self-hosting, open source, streaming, and troubleshooting guides. Written by MajorLinux. Used by MajorTwin.
>
> **Last updated:** 2026-03-08
> **Article count:** 15
---
## Domains
| Domain | Folder | Articles |
|---|---|---|
| 🐧 Linux & Sysadmin | `01-linux/` | 7 |
| 🏠 Self-Hosting & Homelab | `02-selfhosting/` | 7 |
| 🔓 Open Source Tools | `03-opensource/` | 0 |
| 🎙️ Streaming & Podcasting | `04-streaming/` | 1 |
| 🔧 General Troubleshooting | `05-troubleshooting/` | 0 |
---
## 🐧 Linux & Sysadmin
### Files & Permissions
- [[linux-file-permissions]] — chmod, chown, special bits, finding permission problems
### Process Management
- [[managing-linux-services-systemd-ansible]] — systemctl, journalctl, writing service files, Ansible service management
### Networking
- [[ssh-config-key-management]] — key generation, ssh-copy-id, ~/.ssh/config, managing multiple keys
### Package Management
- [[package-management-reference]] — apt, dnf, pacman side-by-side reference, Flatpak/Snap
### Shell & Scripting
- [[ansible-getting-started]] — inventory, ad-hoc commands, playbooks, handlers, roles
- [[bash-scripting-patterns]] — set -euo pipefail, logging, error handling, argument parsing, common patterns
### Distro-Specific
- [[linux-distro-guide-beginners]] — Ubuntu recommendation, distro comparison, desktop environments
- [[wsl2-instance-migration-fedora43]] — moving WSL2 VHDX from C: to another drive
---
## 🏠 Self-Hosting & Homelab
### Docker & Containers
- [[self-hosting-starter-guide]] — hardware options, Docker install, first services, networking basics
- [[docker-vs-vms-homelab]] — when to use containers vs VMs, KVM setup, how to run both
- [[debugging-broken-docker-containers]] — logs, inspect, exec, port conflicts, permission errors
### Reverse Proxies
- [[setting-up-caddy-reverse-proxy]] — Caddyfile basics, automatic HTTPS, local TLS, DNS challenge
### DNS & Networking
- [[tailscale-homelab-remote-access]] — installation, MagicDNS, making services accessible, subnet router, ACLs
### Storage & Backup
- [[rsync-backup-patterns]] — flags reference, remote backup, incremental with hard links, cron/systemd
### Monitoring
- [[tuning-netdata-web-log-alerts]] — tuning web_log_1m_redirects threshold for HTTPS-forcing servers
### Security
- [[linux-server-hardening-checklist]] — non-root user, SSH key auth, sshd_config, firewall, fail2ban
---
## 🔓 Open Source Tools
*(Articles coming)*
---
## 🎙️ Streaming & Podcasting
### OBS Studio
- [[obs-studio-setup-encoding]] — installation, NVENC/x264 settings, scene setup, audio filters, Linux Wayland notes
---
## 🔧 General Troubleshooting
- [[isp-sni-filtering-caddy]] — troubleshooting why wiki.majorshouse.com was blocked by Google Fiber
- [[obsidian-cache-hang-recovery]] — resolving "Loading cache" hang in Obsidian by cleaning Electron app data and ML artifacts
- [[yt-dlp-fedora-js-challenge]] — fixing YouTube JS challenge solver errors and missing formats on Fedora
---
## Recently Updated
| Date | Article | Domain |
|---|---|---|
| 2026-03-11 | [[obsidian-cache-hang-recovery]] | Troubleshooting |
| 2026-03-11 | [[yt-dlp-fedora-js-challenge]] | Troubleshooting |
| 2026-03-08 | [[obs-studio-setup-encoding]] | Streaming |
| 2026-03-08 | [[linux-file-permissions]] | Linux |
| 2026-03-08 | [[rsync-backup-patterns]] | Self-Hosting |
| 2026-03-08 | [[tailscale-homelab-remote-access]] | Self-Hosting |
| 2026-03-08 | [[package-management-reference]] | Linux |
| 2026-03-08 | [[bash-scripting-patterns]] | Linux |
| 2026-03-08 | [[setting-up-caddy-reverse-proxy]] | Self-Hosting |
| 2026-03-08 | [[ssh-config-key-management]] | Linux |
| 2026-03-08 | [[ansible-getting-started]] | Linux |
| 2026-03-08 | [[self-hosting-starter-guide]] | Self-Hosting |
---
## Writing Backlog
| Topic | Domain | Priority | From Gap? |
|---|---|---|---|
| KeePassXC self-hosted password management | Open Source | High | No |
| Docker Compose networking deep dive | Self-Hosting | High | No |
| Troubleshooting NVIDIA on Linux | Troubleshooting | Medium | No |
| Pi-hole setup and local DNS | Self-Hosting | Medium | No |
| OBS audio routing on Linux (PipeWire) | Streaming | Medium | No |
| Nextcloud setup with Docker | Self-Hosting | Medium | No |
| tmux basics | Linux | Low | No |