aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md372
1 files changed, 372 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..420356c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,372 @@
+# karanj.com EU Server - NixOS on Hetzner Cloud
+
+A flake-based, modular NixOS configuration for a personal server running in Europe.
+
+## Services
+
+| URL | Service |
+|-----|---------|
+| https://dns.karanj.com | AdGuard Home (DNS ad-blocker + resolver) |
+| https://rss.karanj.com | Miniflux (RSS/Atom reader) |
+| https://budget.karanj.com | Actual Budget (personal finance) |
+| https://git.karanj.com | cgit (git repository browser) |
+
+All services are reverse-proxied by **Caddy** with automatic TLS via Let's Encrypt.
+AdGuard also listens directly on **port 53 (UDP + TCP)** for DNS.
+
+---
+
+## Repository Layout
+
+```
+flake.nix top-level flake; single host "eurovm"
+.sops.yaml sops-nix age recipient configuration
+secrets/secrets.yaml sops-encrypted secrets (miniflux + adguard creds)
+hosts/eurovm/
+ default.nix host assembly, users, SSH keys
+ hardware.nix Hetzner Cloud virtio/qemu-guest profile
+ disko.nix disk partitioning for nixos-anywhere
+modules/
+ common.nix SSH hardening, firewall, timezone, nix settings
+ sops.nix sops-nix wiring (age key from SSH host key)
+ caddy.nix reverse proxy + HTTPS virtual hosts
+ adguard.nix AdGuard Home DNS + web UI
+ miniflux.nix Miniflux + PostgreSQL
+ actual.nix Actual Budget
+ cgit.nix cgit + fcgiwrap + git push user
+```
+
+---
+
+## Bootstrap: First Deploy
+
+### 1. Create the Hetzner Cloud VM
+
+- Log in to https://console.hetzner.cloud
+- Create a new server: **CX22** (2 vCPU / 4 GB RAM), location **Nuremberg** or **Falkenstein**
+- Base image: **Debian 12** (nixos-anywhere will replace it)
+- Add your SSH public keys to the Hetzner project so root access works during install
+- Note the assigned **public IPv4 address**
+
+### 2. Create DNS records
+
+At your DNS provider (for karanj.com), create **A records** pointing at the server IP:
+
+```
+dns.karanj.com A <server-ipv4>
+rss.karanj.com A <server-ipv4>
+budget.karanj.com A <server-ipv4>
+git.karanj.com A <server-ipv4>
+```
+
+Or a single wildcard: `*.karanj.com A <server-ipv4>`
+
+DNS must resolve **before** the first `nixos-rebuild` so Caddy can obtain TLS certificates.
+
+### 3. Set up sops-nix secrets
+
+**3a. Convert your SSH public keys to age format:**
+
+On your local machine (Einstein):
+```bash
+cat ~/.ssh/id_ed25519.pub | ssh-to-age
+# -> age1xxxx... (copy this)
+```
+
+Repeat on Galileo (or use the public key directly):
+```bash
+echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH+qnLTnorv+I2rSSfGjNiCuX/W5AxoNgAdu+cTOyKzW Galileo" | ssh-to-age
+# -> age1yyyy... (copy this)
+```
+
+**3b. Update `.sops.yaml`:**
+
+Replace the placeholder values:
+```yaml
+keys:
+ - &einstein age1xxxx... # output from step 3a (Einstein)
+ - &galileo age1yyyy... # output from step 3a (Galileo)
+```
+
+**3c. Generate secrets:**
+
+AdGuard password hash (uses Apache htpasswd bcrypt format):
+```bash
+# Install apache2-utils if needed: apt install apache2-utils
+htpasswd -nB admin
+# Enter password when prompted; copy everything AFTER "admin:"
+# Example output: admin:$2y$05$abc123...
+# Put only the hash part: $2y$05$abc123...
+```
+
+Miniflux admin password: choose any strong password.
+
+**3d. Fill in and encrypt secrets.yaml:**
+```bash
+# Edit the plaintext file first
+vim secrets/secrets.yaml
+# Set ADMIN_PASSWORD and password_hash to real values
+
+# Then encrypt it in place
+sops --encrypt --in-place secrets/secrets.yaml
+```
+
+The file is now safe to commit. To edit it later: `sops secrets/secrets.yaml`
+
+### 4. Install NixOS with nixos-anywhere
+
+```bash
+# From your local machine (requires nix with flakes enabled)
+nix run github:nix-community/nixos-anywhere -- \
+ --flake .#eurovm \
+ root@<server-ipv4>
+```
+
+nixos-anywhere will:
+1. Copy the flake to the server
+2. Run disko to partition the disk
+3. Install NixOS
+4. Reboot into the new system
+
+### 5. Add the server host key as a sops recipient
+
+After the server reboots, grab its SSH host key and convert it to age:
+```bash
+ssh-keyscan <server-ipv4> | grep ed25519 | ssh-to-age
+# -> age1zzzz...
+```
+
+Update `.sops.yaml` - uncomment the `eurovm` key and fill in the age value:
+```yaml
+ - &eurovm age1zzzz...
+```
+
+And update the `creation_rules` section to include `*eurovm`.
+
+Re-encrypt secrets with the new recipient:
+```bash
+sops updatekeys secrets/secrets.yaml
+```
+
+Commit and push, then redeploy:
+```bash
+nixos-rebuild switch --flake .#eurovm --target-host admin@<server-ipv4> --use-remote-sudo
+```
+
+---
+
+## Day-to-Day Operations
+
+### Deploy updates
+```bash
+nixos-rebuild switch --flake .#eurovm --target-host admin@<server-ipv4> --use-remote-sudo
+```
+
+### Edit secrets
+```bash
+sops secrets/secrets.yaml
+# Save and exit; sops re-encrypts automatically
+# Then redeploy to apply
+```
+
+### Create a new git repository
+```bash
+ssh git@<server-ipv4> init --bare /srv/git/myrepo.git
+# On your local machine:
+git remote add origin git@<server-ipv4>:/srv/git/myrepo.git
+git push -u origin main
+# It appears automatically at https://git.karanj.com
+```
+
+### Clone a public repository
+```bash
+git clone https://git.karanj.com/myrepo.git
+```
+
+---
+
+## First Access - Per App
+
+### AdGuard Home (https://dns.karanj.com)
+
+The admin account is pre-created from the sops secret during deployment.
+
+- **Username:** `admin`
+- **Password:** whatever you set as the plaintext before encrypting `secrets.yaml`
+
+Log in, then go to **Settings -> General** to verify DNS is working.
+To use the server as your DNS resolver, point your device's DNS to `<server-ipv4>`.
+
+If you need to reset the password:
+```bash
+# On the server, edit /var/lib/AdGuardHome/AdGuardHome.yaml
+# Replace the users[].password bcrypt hash with a new one
+# Then: systemctl restart adguardhome
+```
+
+### Miniflux (https://rss.karanj.com)
+
+The admin account is pre-created from the sops secret on first startup.
+
+- **Username:** `admin` (or whatever you set as `ADMIN_USERNAME` in secrets.yaml)
+- **Password:** whatever you set as `ADMIN_PASSWORD` in secrets.yaml
+
+After logging in, go to **Settings -> Users** to change the password or
+add additional accounts.
+
+To reset the password from the server:
+```bash
+sudo -u miniflux miniflux -reset-password
+```
+
+### Actual Budget (https://budget.karanj.com)
+
+No pre-configuration needed. On first visit:
+
+1. Open https://budget.karanj.com in your browser
+2. The app will display a **"Create server password"** prompt
+3. Enter a strong password - this is what all your devices use to sync
+4. Click **OK** and the app is ready to use
+
+To reset the password, delete `/var/lib/actual/server-files/account.json` on the
+server and restart the service: `systemctl restart actual`
+
+### cgit (https://git.karanj.com)
+
+No login required - the web interface is fully public and read-only.
+
+- **Browse:** open https://git.karanj.com in any browser
+- **Clone/Pull:** `git clone https://git.karanj.com/<repo>.git`
+- **Push:** SSH only - `git push git@<server-ipv4>:/srv/git/<repo>.git`
+
+### Caddy / TLS
+
+TLS certificates are obtained automatically from Let's Encrypt on first startup
+(registered to me@karanj.com). No manual action needed. Certificates auto-renew.
+
+Caddy logs: `journalctl -u caddy -f`
+
+---
+
+## Next Steps
+
+These are the actions to take immediately after cloning this repo, in order.
+
+### Step 1 - Fill in `.sops.yaml` with your age keys
+
+On **Einstein**:
+```bash
+cat ~/.ssh/id_ed25519.pub | ssh-to-age
+```
+
+On **Galileo** (or derive from the public key directly):
+```bash
+echo "ssh-to-age AAAAC3NzaC1lZDI1NTE5AAAAIH+qnLTnorv+I2rSSfGjNiCuX/W5AxoNgAdu+cTOyKzW Galileo" | ssh-to-age
+```
+
+Paste both `age1...` values into `.sops.yaml` replacing the `REPLACE_WITH_...` placeholders.
+
+### Step 2 - Set real passwords in `secrets/secrets.yaml`
+
+Generate the AdGuard bcrypt hash:
+```bash
+htpasswd -nB admin
+# Copy only the hash part (after "admin:")
+```
+
+Then edit the secrets file and fill in real values:
+```bash
+vim secrets/secrets.yaml
+# Set ADMIN_PASSWORD (Miniflux) and password_hash (AdGuard)
+```
+
+Encrypt it:
+```bash
+sops --encrypt --in-place secrets/secrets.yaml
+```
+
+Commit both `.sops.yaml` and the encrypted `secrets/secrets.yaml`.
+
+### Step 3 - Create the Hetzner Cloud VM
+
+1. Go to https://console.hetzner.cloud and create a new project (or use an existing one)
+2. Add both your SSH public keys to the project under **Security -> SSH Keys**
+3. Create a server with the following spec:
+ - **Minimum:** CAX11 (2 ARM vCPU / 4 GB RAM / 40 GB disk, ~3.79 EUR/month) - all services work fine on ARM64
+ - **Alternative:** CX22 (2 x86 vCPU / 4 GB RAM / 40 GB disk, ~4.35 EUR/month) - use if you need x86 compatibility later
+ - Location: **Nuremberg** or **Falkenstein**
+ - Image: **Debian 12** (nixos-anywhere replaces it)
+4. Note the **public IPv4 address**
+
+### Step 4 - Point DNS at the server
+
+At your DNS registrar for karanj.com, add:
+```
+dns.karanj.com A <server-ipv4>
+rss.karanj.com A <server-ipv4>
+budget.karanj.com A <server-ipv4>
+git.karanj.com A <server-ipv4>
+```
+
+Wait for propagation (usually a few minutes with most registrars).
+Verify: `dig dns.karanj.com +short` should return your server IP.
+
+### Step 5 - Install NixOS with nixos-anywhere
+
+Make sure you have Nix with flakes enabled locally, then:
+```bash
+nix run github:nix-community/nixos-anywhere -- \
+ --flake .#eurovm \
+ root@<server-ipv4>
+```
+
+The install takes 5-10 minutes. The server reboots into NixOS at the end.
+
+### Step 6 - Add the server host key to sops
+
+After the server is up:
+```bash
+ssh-keyscan <server-ipv4> | grep ed25519 | ssh-to-age
+# -> age1zzzz...
+```
+
+Uncomment and fill in the `eurovm` key in `.sops.yaml`, add `*eurovm` to the
+`creation_rules` age list, then re-encrypt:
+```bash
+sops updatekeys secrets/secrets.yaml
+```
+
+### Step 7 - Final deploy
+
+Push the updated `.sops.yaml` and re-encrypted `secrets/secrets.yaml`, then run:
+```bash
+nixos-rebuild switch --flake .#eurovm --target-host admin@<server-ipv4> --use-remote-sudo
+```
+
+### Step 8 - First login to each service
+
+| Service | URL | Action |
+|---------|-----|--------|
+| AdGuard Home | https://dns.karanj.com | Log in with `admin` + your sops password |
+| Miniflux | https://rss.karanj.com | Log in with `admin` + your sops password |
+| Actual Budget | https://budget.karanj.com | Set server password in browser on first visit |
+| cgit | https://git.karanj.com | No login needed - public read-only |
+
+### Step 9 - Update `common.nix` flake URL (optional)
+
+If you push this repo to GitHub/Forgejo/cgit, update the `autoUpgrade.flake` line
+in `modules/common.nix` and set `enable = true` to get automatic OS updates.
+
+---
+
+## Firewall Summary
+
+| Port | Protocol | Purpose |
+|------|----------|---------|
+| 22 | TCP | SSH (admin + git push) |
+| 80 | TCP | HTTP (Caddy redirects to HTTPS) |
+| 443 | TCP | HTTPS (all web services) |
+| 53 | TCP + UDP | DNS (AdGuard Home) |
+
+All other ports are closed. App-level ports (3000, 5006, 8080, 8086) are
+bound to 127.0.0.1 and never exposed directly.