diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 329 |
1 files changed, 99 insertions, 230 deletions
@@ -40,20 +40,18 @@ modules/ ## Prerequisites (Local Machine) -Before running any deploy commands you need the following installed on your laptop. +Install these on your laptop before running any deploy commands. ### 1. Nix (with flakes enabled) -Nix must be installed locally. It is used to evaluate the flake, run `nixos-anywhere`, -and build the system closure. It is **not** needed on the Hetzner VM in advance - -`nixos-anywhere` takes care of installing NixOS there. +Used to evaluate the flake, run `nixos-anywhere`, and build the system closure. Not needed on +the Hetzner VM in advance - `nixos-anywhere` installs NixOS there for you. -The easiest installer (enables flakes automatically): ```bash curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install ``` -Or use the official installer and then add this to `/etc/nix/nix.conf`: +Or use the official installer and add this to `/etc/nix/nix.conf`: ``` experimental-features = nix-command flakes ``` @@ -66,20 +64,19 @@ nix run nixpkgs#hello ### 2. sops + ssh-to-age + age -These are needed to set up encrypted secrets before the first deploy. -With Nix installed, you can run them directly without a permanent install: +Needed to set up encrypted secrets before the first deploy. With Nix installed, run without a +permanent install: ```bash nix shell nixpkgs#sops nixpkgs#ssh-to-age nixpkgs#age ``` -Or install them permanently: +Or install permanently: ```bash nix profile install nixpkgs#sops nixpkgs#ssh-to-age nixpkgs#age ``` ### 3. apache2-utils (for AdGuard bcrypt hash) -Needed to generate the AdGuard admin password hash: ```bash # macOS brew install httpd @@ -93,105 +90,101 @@ nix shell nixpkgs#apacheHttpd --- -## Bootstrap: First Deploy +## First-Time Setup -### 1. Create the Hetzner Cloud VM +Follow these steps in order, once, right after cloning this repo. -- 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** +### Step 1 - Convert SSH keys to age and fill in `.sops.yaml` -### 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): +On **Einstein**: ```bash cat ~/.ssh/id_ed25519.pub | ssh-to-age -# -> age1xxxx... (copy this) +# -> age1xxxx... ``` -Repeat on Galileo (or use the public key directly): +On **Galileo** (or derive from the public key directly): ```bash echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH+qnLTnorv+I2rSSfGjNiCuX/W5AxoNgAdu+cTOyKzW Galileo" | ssh-to-age -# -> age1yyyy... (copy this) +# -> age1yyyy... ``` -**3b. Update `.sops.yaml`:** - -Replace the placeholder values: +Paste both `age1...` values into `.sops.yaml`: ```yaml keys: - - &einstein age1xxxx... # output from step 3a (Einstein) - - &galileo age1yyyy... # output from step 3a (Galileo) + - &einstein age1xxxx... # Einstein + - &galileo age1yyyy... # Galileo ``` -**3c. Generate secrets:** +### Step 2 - Fill in and encrypt `secrets/secrets.yaml` -AdGuard password hash (uses Apache htpasswd bcrypt format): +Generate the AdGuard bcrypt hash: ```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... +# Example output: admin:$2y$05$abc123... -> keep only $2y$05$abc123... ``` -Miniflux admin password: choose any strong password. - -**3d. Fill in and encrypt secrets.yaml:** +Edit the plaintext file with real values: ```bash -# Edit the plaintext file first vim secrets/secrets.yaml -# Set ADMIN_PASSWORD and password_hash to real values +# Set ADMIN_PASSWORD (Miniflux) and password_hash (AdGuard) +``` -# Then encrypt it in place +Encrypt it in place: +```bash sops --encrypt --in-place secrets/secrets.yaml ``` -The file is now safe to commit. To edit it later: `sops secrets/secrets.yaml` +The file is now safe to commit. To edit it later: `sops secrets/secrets.yaml`. + +### Step 3 - Create the Hetzner Cloud VM + +1. Log in 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: + - **Recommended:** CX23 (2 x86 vCPU / 4 GB RAM / 80 GB disk, Cost Optimized line) - good + value, x86-64, all services supported + - **Cheaper ARM option:** CAX11 (2 ARM vCPU / 4 GB RAM / 40 GB disk) - all services work + fine on ARM64 + - Location: **Nuremberg** or **Falkenstein** + - Image: **Debian 12** (nixos-anywhere replaces it) +4. Note the assigned **public IPv4 address**. + +> If you chose CAX11 (ARM64) instead of CX23 (x86_64), change `system = "x86_64-linux"` to +> `system = "aarch64-linux"` in `flake.nix` first. + +### Step 4 - Point DNS at the server + +At your DNS provider for karanj.com, create **A records**: +``` +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>`. -### 4. Install NixOS with nixos-anywhere +DNS must resolve **before** the first deploy so Caddy can obtain TLS certificates. Verify with +`dig dns.karanj.com +short`. + +### Step 5 - Install NixOS with nixos-anywhere If deploying from a non-x86_64-linux machine (e.g. an Apple Silicon Mac), add -`--build-on remote` so the server builds its own closure rather than your local -machine attempting a cross-architecture build: +`--build-on remote` so the server builds its own closure rather than attempting a +cross-architecture build locally: ```bash -# From your local machine (requires nix with flakes enabled) nix run github:nix-community/nixos-anywhere -- \ --flake .#eurovm \ --build-on remote \ root@<server-ipv4> ``` -> **Note:** if you chose a CAX11 (ARM64) server instead of a CX23 (x86_64), change -> `system = "x86_64-linux"` to `system = "aarch64-linux"` in `flake.nix` first. - -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 +This copies the flake to the server, partitions the disk with disko, installs NixOS, and +reboots into the new system. Takes 5-10 minutes. -### 5. Add the server host key as a sops recipient +### Step 6 - Add the server host key and redeploy After the server reboots, grab its SSH host key and convert it to age: ```bash @@ -199,19 +192,18 @@ ssh-keyscan <server-ipv4> | grep ed25519 | ssh-to-age # -> age1zzzz... ``` -Update `.sops.yaml` - uncomment the `eurovm` key and fill in the age value: +Update `.sops.yaml` - uncomment the `eurovm` key, fill in the age value, and add `*eurovm` to +the `creation_rules` age list: ```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: +Commit and push `.sops.yaml` and the re-encrypted `secrets/secrets.yaml`, then deploy: ```bash nixos-rebuild switch --flake .#eurovm \ --target-host admin@<server-ipv4> \ @@ -219,15 +211,30 @@ nixos-rebuild switch --flake .#eurovm \ --use-remote-sudo ``` +If your local machine is also x86_64-linux you can omit `--build-host`. + +### Step 7 - First login to each service + +| Service | URL | Action | +|---------|-----|--------| +| AdGuard Home | https://dns.karanj.com | Log in with `admin` + the password you set in Step 2 | +| Miniflux | https://rss.karanj.com | Log in with `admin` (or your `ADMIN_USERNAME`) + your sops password | +| Actual Budget | https://budget.karanj.com | Set a server password in the browser on first visit - no pre-configuration needed | +| cgit | https://git.karanj.com | No login needed - public read-only | + +To use the server as your device's DNS resolver, point its DNS settings to `<server-ipv4>`. + +### Step 8 - (Optional) enable automatic OS updates + +If you push this repo to GitHub/Forgejo/cgit, update the `autoUpgrade.flake` line in +`modules/common.nix` to point at it and set `enable = true`. + --- ## Day-to-Day Operations ### Deploy updates -If deploying from a non-x86_64-linux machine (e.g. an Apple Silicon Mac), pass -`--build-host` so the server compiles its own closure instead of the local machine: - ```bash nixos-rebuild switch --flake .#eurovm \ --target-host admin@<server-ipv4> \ @@ -260,184 +267,46 @@ git clone https://git.karanj.com/myrepo.git --- -## First Access - Per App - -### AdGuard Home (https://dns.karanj.com) +## Per-App Operational Notes -The admin account is pre-created from the sops secret during deployment. +### AdGuard Home -- **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: +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. +### Miniflux To reset the password from the server: ```bash sudo -u miniflux miniflux -reset-password ``` -### Actual Budget (https://budget.karanj.com) +Additional accounts and password changes can also be managed from **Settings -> Users** in the +web UI. -No pre-configuration needed. On first visit: +### Actual Budget -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 server password, delete `/var/lib/actual/server-files/account.json` on the server +and restart the service: `systemctl restart actual`. -To reset the password, delete `/var/lib/actual/server-files/account.json` on the -server and restart the service: `systemctl restart actual` +### cgit -### 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` +- **Clone/Pull:** `git clone https://git.karanj.com/<repo>.git` (public, read-only) - **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. +TLS certificates are obtained automatically from Let's Encrypt on first startup (registered to +me@karanj.com) and auto-renew. No manual action needed. 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: - - **Recommended:** CX23 (2 x86 vCPU / 4 GB RAM / 80 GB disk, Cost Optimized line) - good value, x86-64, all services supported - - **Cheaper ARM option:** CAX11 (2 ARM vCPU / 4 GB RAM / 40 GB disk) - all services work fine on ARM64 - - 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 \ - --build-on remote \ - 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> \ - --build-host admin@<server-ipv4> \ - --elevate=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 | @@ -447,5 +316,5 @@ in `modules/common.nix` and set `enable = true` to get automatic OS updates. | 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. +All other ports are closed. App-level ports (3000, 5006, 8080, 8086) are bound to +127.0.0.1 and never exposed directly. |
