feat(portal): add Vultr NixOS host configuration
- use the shared global base and Tailscale module - configure Portal SSH and VM memory settings - add Richie's minimal Home Manager profile - document SOPS and nixos-anywhere deployment
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
# portal_1
|
||||||
|
|
||||||
|
Minimal NixOS target for a Vultr VM, installed with nixos-anywhere. The Nix
|
||||||
|
flake target is `portal_1`; the machine hostname is `portal-1` because DNS
|
||||||
|
hostnames cannot contain underscores.
|
||||||
|
|
||||||
|
## Before deploying
|
||||||
|
|
||||||
|
1. Confirm the VM's system disk is `/dev/vda`. If it is not, update both
|
||||||
|
references in `disk-config.nix`.
|
||||||
|
2. Confirm the SSH public key in `default.nix` is the key that should have
|
||||||
|
administrator access.
|
||||||
|
3. Boot the VM into a NixOS installer or another nixos-anywhere-compatible
|
||||||
|
Linux rescue environment with root SSH access. Keep this environment
|
||||||
|
running while completing the SOPS bootstrap below.
|
||||||
|
|
||||||
|
## Bootstrap SOPS
|
||||||
|
|
||||||
|
Use the rescue environment's SSH host key as the permanent portal identity.
|
||||||
|
Replace `VM_IP` below:
|
||||||
|
|
||||||
|
```console
|
||||||
|
ssh root@VM_IP 'cat /etc/ssh/ssh_host_ed25519_key.pub' | \
|
||||||
|
nix shell nixpkgs#ssh-to-age --command ssh-to-age
|
||||||
|
```
|
||||||
|
|
||||||
|
This prints an `age1...` recipient; it does not copy the private key. Add the
|
||||||
|
recipient to `.sops.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- &system_portal_1 age1...
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add `*system_portal_1` to the age recipients for
|
||||||
|
`users/secrets.yaml`. Re-encrypt the existing file for the new recipient and
|
||||||
|
add the Tailscale key:
|
||||||
|
|
||||||
|
```console
|
||||||
|
nix shell nixpkgs#sops --command sops updatekeys users/secrets.yaml
|
||||||
|
nix shell nixpkgs#sops --command sops users/secrets.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the OAuth client secret from the `Auth Keys: Write` credential in the SOPS
|
||||||
|
editor and save it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tailscale_auth_key: tskey-client-...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
From the repository root, replace `VM_IP` with the VM's public IP:
|
||||||
|
|
||||||
|
```console
|
||||||
|
nix run github:nix-community/nixos-anywhere -- \
|
||||||
|
--copy-host-keys --flake .#portal_1 root@VM_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
This repartitions `/dev/vda`, so anything already on that disk is erased. The
|
||||||
|
layout reserves 8 GiB for swap and assigns the remaining space to the root
|
||||||
|
filesystem.
|
||||||
|
|
||||||
|
`--copy-host-keys` preserves the same private SSH host key at
|
||||||
|
`/etc/ssh/ssh_host_ed25519_key` on the installed system. SOPS-Nix converts that
|
||||||
|
key to an age identity during activation. After the reboot, connect as
|
||||||
|
`richie` and verify that automatic Tailscale enrollment succeeded:
|
||||||
|
|
||||||
|
```console
|
||||||
|
ssh -p 278 richie@VM_IP
|
||||||
|
sudo tailscale status
|
||||||
|
```
|
||||||
|
|
||||||
|
The installed OpenSSH service listens on port 278. Port 22 is served by
|
||||||
|
Endlessh and will not provide an SSH login.
|
||||||
|
|
||||||
|
HAProxy uses the same frontend, routing, and rate-limiting configuration as
|
||||||
|
Jeeves. Portal manages the ACME certificates for the existing public domains;
|
||||||
|
their DNS records must resolve to Portal for HTTP-01 issuance and renewal.
|
||||||
|
|
||||||
|
The application backends still use Jeeves' original `127.0.0.1` addresses.
|
||||||
|
Replace them with the corresponding Tailscale addresses before directing
|
||||||
|
application traffic through Portal. Ports 80 and 443 are allowed through the
|
||||||
|
firewall.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.disko.nixosModules.disko
|
||||||
|
"${inputs.self}/common/global"
|
||||||
|
"${inputs.self}/users/richie"
|
||||||
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
|
./disk-config.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
|
|
||||||
|
boot = {
|
||||||
|
# Avoid consuming the VM's limited memory for /tmp.
|
||||||
|
tmp.useTmpfs = false;
|
||||||
|
|
||||||
|
# The Vultr system disk and NIC are exposed as virtio devices.
|
||||||
|
initrd.availableKernelModules = [
|
||||||
|
"virtio_pci"
|
||||||
|
"virtio_blk"
|
||||||
|
"virtio_scsi"
|
||||||
|
"sd_mod"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
networking = {
|
||||||
|
hostName = "portal-1";
|
||||||
|
useDHCP = lib.mkDefault true;
|
||||||
|
|
||||||
|
firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [ 278 ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.openssh.ports = [ 278 ];
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
curl
|
||||||
|
htop
|
||||||
|
vim
|
||||||
|
];
|
||||||
|
|
||||||
|
# The VM uses its regular swap instead of compressed RAM swap.
|
||||||
|
zramSwap.enable = false;
|
||||||
|
|
||||||
|
time.timeZone = "Etc/UTC";
|
||||||
|
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
# Vultr's first virtio disk is normally /dev/vda. Change this before
|
||||||
|
# deployment if the selected image exposes its system disk differently.
|
||||||
|
disko.devices.disk.main = {
|
||||||
|
type = "disk";
|
||||||
|
device = "/dev/vda";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
bios = {
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [
|
||||||
|
"fmask=0077"
|
||||||
|
"dmask=0077"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
swap = {
|
||||||
|
size = "8G";
|
||||||
|
content = {
|
||||||
|
type = "swap";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Install GRUB for both legacy BIOS and EFI-capable Vultr plans.
|
||||||
|
boot.loader = {
|
||||||
|
grub = {
|
||||||
|
enable = true;
|
||||||
|
devices = [ "/dev/vda" ];
|
||||||
|
efiSupport = true;
|
||||||
|
efiInstallAsRemovable = true;
|
||||||
|
};
|
||||||
|
efi.canTouchEfiVariables = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../home/minimal.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user