This guide provides copy-pasteable, step-by-step templates to go from a blank server to a working system. Follow these phases in order.
Before you begin, you need:
Connect to your server:
ssh root@your-server-ip
}}}
Update and install essentials:
apt update && apt upgrade -y
apt install -y vim git curl wget
}}}
Add these DNS A records pointing to your server IP:
snek.cc. A YOUR_SERVER_IP
*.snek.cc. A YOUR_SERVER_IP
pds.snek.cc. A YOUR_SERVER_IP
*.pds.snek.cc. A YOUR_SERVER_IP
}}}
Wait for propagation:
dig +short snek.cc
dig +short test.pds.snek.cc
}}}
Download and boot the NixOS minimal ISO:
# From your server control panel
# 1. Mount the NixOS minimal ISO
# 2. Reboot into the ISO
}}}
Once booted into the NixOS installer:
# Partition your disk (example for single disk)
parted /dev/vda -- mklabel gpt
parted /dev/vda -- mkpart primary 512MiB -8GiB
parted /dev/vda -- mkpart primary linux-swap -8GiB 100%
parted /dev/vda -- mkpart ESP fat32 1MiB 512MiB
parted /dev/vda -- set 3 esp on
# Format partitions
mkfs.ext4 -L nixos /dev/vda1
mkswap -L swap /dev/vda2
mkfs.fat -F 32 -n boot /dev/vda3
# Mount partitions
mount /dev/disk/by-label/nixos /mnt
mkdir -p /mnt/boot
mount /dev/disk/by-label/boot /mnt/boot
swapon /dev/vda2
# Generate config
nixos-generate-config --root /mnt
# Edit configuration
vim /mnt/etc/nixos/configuration.nix
}}}
Basic configuration.nix:
{ config, pkgs, ... }: {
imports = [ ./hardware-configuration.nix ];
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/vda";
networking.hostName = "snek";
networking.useDHCP = true;
time.timeZone = "UTC";
i18n.defaultLocale = "en_US.UTF-8";
users.users.atproto = {
isNormalUser = true;
home = "/home/atproto";
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 YOUR_PUBLIC_KEY"
];
};
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
environment.systemPackages = with pkgs; [
vim git wget
];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
system.stateVersion = "24.11";
}
}}}
Install:
nixos-install --no-root-passwd
reboot
}}}
Once NixOS is installed and you can SSH in as atproto:
Create /etc/nixos/flake.nix:
{
description = "snek.cc NixOS configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }: {
nixosConfigurations.snek = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./configuration.nix ];
};
};
}
}}}
=== Step 2: Configure Caddy ===
Add to `configuration.nix`:
services.caddy = {
enable = true;
virtualHosts = {
"snek.cc" = {
extraConfig = ''
root * /var/www/snek.cc
file_server
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
}
'';
};
};
};
}}}
Create web root:
sudo mkdir -p /var/www/snek.cc
echo "Hello from snek.cc
" | sudo tee /var/www/snek.cc/index.html
}}}
=== Step 3: Test and Apply ===
cd /etc/nixos
sudo nixos-rebuild test --flake .#snek
}}}
If it works:
sudo nixos-rebuild switch --flake .#snek
}}}
Visit https://snek.cc - you should see your hello message!
== Phase 2: Add Bluesky PDS ==
=== Step 1: Generate PDS Keys ===
# Generate admin password
openssl rand -hex 16
# Generate JWT secret
openssl rand -hex 32
# Generate PLC rotation key
cd /tmp
openssl ecparam -name prime256v1 -genkey -noout -out plc-rotation.key
openssl ec -in plc-rotation.key -pubout -out plc-rotation.pub
# The private key hex is the PLC rotation key
}}}
Install sops-nix and configure secrets (see Secrets Setup for full details).
Briefly:
# Create .sops.yaml
mkdir -p /etc/nixos/secrets
}}}
Create `/etc/nixos/secrets/.sops.yaml`:
keys:
creation_rules:
key_groups:
}}}
Create /etc/nixos/secrets/pds.yaml with your secrets (encrypted).
Add to configuration.nix:
services.bluesky-pds = {
enable = true;
package = pkgs.bluesky-pds;
environmentFiles = [ config.sops.templates."pds-env".path ];
settings = {
PDS_HOSTNAME = "pds.snek.cc";
PDS_SERVICE_DID = "did:web:pds.snek.cc";
PDS_PORT = 2583;
PDS_DATA_DIRECTORY = "/var/lib/pds";
PDS_BLOBSTORE_DISK_LOCATION = "/var/lib/pds/blocks";
PDS_DID_PLC_URL = "https://plc.directory";
PDS_BSKY_APP_VIEW_URL = "https://api.bsky.app";
PDS_BSKY_APP_VIEW_DID = "did:web:api.bsky.app";
PDS_INVITE_REQUIRED = "true";
};
};
sops.defaultSopsFile = ./secrets/pds.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
sops.secrets.PDS_JWT_SECRET = { };
sops.secrets.PDS_ADMIN_PASSWORD = { };
sops.secrets.PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX = { };
sops.templates."pds-env".content = ''
PDS_JWT_SECRET=''${config.sops.placeholder.PDS_JWT_SECRET}
PDS_ADMIN_PASSWORD=''${config.sops.placeholder.PDS_ADMIN_PASSWORD}
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=''${config.sops.placeholder.PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX}
'';
}}}
=== Step 4: Add Caddy Config for PDS ===
Add to Caddy virtualHosts:
"*.pds.snek.cc, pds.snek.cc" = {
extraConfig = ''
tls {
on_demand
}
reverse_proxy http://127.0.0.1:2583 {
transport http {
read_timeout 0
write_timeout 0
dial_timeout 30s
}
}
'';
};
}}}
sudo nixos-rebuild switch --flake .#snek
}}}
=== Step 6: Create First User ===
export PDSADMINPASSWORD=$(sudo cat /run/secrets/PDSADMINPASSWORD)
pdsadmin create-account
}}}
services.prometheus = {
enable = true;
port = 9090;
scrapeConfigs = [
{
job_name = "prometheus";
static_configs = [{
targets = [ "localhost:9090" ];
}];
}
];
exporters.node = {
enable = true;
port = 9100;
};
};
}}}
=== Step 2: Configure Grafana ===
services.grafana = {
enable = true;
settings = {
server = {
http_addr = "127.0.0.1";
http_port = 3001;
};
};
};
}}}
"grafana.snek.cc" = {
extraConfig = ''
reverse_proxy http://127.0.0.1:3001
'';
};
}}}
== Next Steps ==
Once you have these basics working:
1. Explore [[17-service-recipes|Service Recipes]] to add more services
2. Read [[03-caddy-reverse-proxy|Caddy Reverse Proxy]] to understand the proxy setup
3. Check [[14-troubleshooting|Troubleshooting]] if anything breaks
4. Review [[11-building-incrementally|Building Incrementally]] for development workflow
== Common Issues ==
*PDS not responding:*
- Check logs: `journalctl -u bluesky-pds -f`
- Verify DNS: `dig +short pds.snek.cc`
- Check secrets are decrypted: `ls -la /run/secrets/`
*Caddy not getting certificates:*
- Verify DNS points to server IP
- Check Caddy logs: `journalctl -u caddy -f`
- Temporarily disable firewall for testing
*Rebuild fails:*
- Check syntax: `nix-instantiate --eval ./configuration.nix`
- Review error messages carefully
- Test with `nixos-rebuild test` first