# This file configures camden.tazj.in, my homeserver.
{ depot, pkgs, lib, ... }:
config: let
nixpkgs = import depot.third_party.nixpkgsSrc {
config.allowUnfree = true;
};
nginxRedirect = { from, to, acmeHost }: {
serverName = from;
useACMEHost = acmeHost;
forceSSL = true;
extraConfig = "return 301 https://${to}$request_uri;";
};
in lib.fix(self: {
imports = [
"${depot.depotPath}/ops/nixos/depot.nix"
"${depot.depotPath}/ops/nixos/monorepo-gerrit.nix"
"${depot.depotPath}/ops/nixos/sourcegraph.nix"
"${depot.depotPath}/ops/nixos/smtprelay.nix"
"${depot.depotPath}/ops/nixos/tvl-slapd/default.nix"
"${pkgs.nixpkgsSrc}/nixos/modules/services/web-apps/gerrit.nix"
];
depot = depot;
# camden is intended to boot unattended, despite having an encrypted
# root partition.
#
# The below configuration uses an externally connected USB drive
# that contains a LUKS key file to unlock the disk automatically at
# boot.
#
# TODO(tazjin): Configure LUKS unlocking via SSH instead.
boot = {
initrd = {
availableKernelModules = [
"ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod" "sdhci_pci"
"rtsx_usb_sdmmc" "r8169"
];
kernelModules = [ "dm-snapshot" ];
luks.devices.camden-crypt = {
fallbackToPassword = true;
device = "/dev/disk/by-label/camden-crypt";
keyFile = "/dev/sdb";
keyFileSize = 4096;
};
};
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
cleanTmpDir = true;
};
fileSystems = {
"/" = {
device = "/dev/disk/by-label/camden-root";
fsType = "ext4";
};
"/home" = {
device = "/dev/disk/by-label/camden-home";
fsType = "ext4";
};
"/boot" = {
device = "/dev/disk/by-label/BOOT";
fsType = "vfat";
};
};
nix = {
maxJobs = lib.mkDefault 4;
nixPath = [
"depot=/home/tazjin/depot"
"nixpkgs=${depot.third_party.nixpkgsSrc}"
];
trustedUsers = [ "root" "tazjin" ];
binaryCaches = [
"https://tazjin.cachix.org"
];
binaryCachePublicKeys = [
"tazjin.cachix.org-1:IZkgLeqfOr1kAZjypItHMg1NoBjm4zX9Zzep8oRSh7U="
];
};
nixpkgs.pkgs = nixpkgs;
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
networking = {
hostName = "camden";
interfaces.enp1s0.useDHCP = true;
interfaces.enp1s0.ipv6.addresses = [
{
address = "2a01:4b00:821a:ce02::5";
prefixLength = 64;
}
];
firewall.enable = false;
};
time.timeZone = "UTC";
# System-wide application setup
programs.fish.enable = true;
programs.mosh.enable = true;
environment.systemPackages =
# programs from the depot
(with depot; [
fun.idual.script
fun.idual.setAlarm
third_party.pounce
]) ++
# programs from nixpkgs
(with nixpkgs; [
bat
curl
direnv
emacs26-nox
git
gnupg
google-cloud-sdk
htop
jq
pass
pciutils
restic
ripgrep
]);
users = {
# Set up my own user for logging in and doing things ...
users.tazjin = {
isNormalUser = true;
uid = 1000;
extraGroups = [ "git" "wheel" ];
shell = nixpkgs.fish;
};
# Set up a user & group for general git shenanigans
groups.git = {};
users.git = {
group = "git";
isNormalUser = false;
};
};
# Services setup
services.openssh.enable = true;
services.haveged.enable = true;
# Join Tailscale into home network
services.tailscale.enable = true;
# Allow sudo-ing via the forwarded SSH agent.
security.pam.enableSSHAgentAuth = true;
# Run cgit for the depot. The onion here is nginx(thttpd(cgit)).
systemd.services.cgit = {
wantedBy = [ "multi-user.target" ];
script = "${depot.web.cgit-taz}/bin/cgit-launch";
serviceConfig = {
Restart = "on-failure";
User = "git";
Group = "git";
};
};
# Run honk as the ActivityPub server, using all the fancy systemd
# magic.
systemd.services.honk = {
wantedBy = [ "multi-user.target" ];
script = lib.concatStringsSep " " [
"${depot.third_party.honk}/bin/honk"
"-datadir /var/lib/honk"
"-viewdir ${depot.third_party.honk.src}"
];
serviceConfig = {
Restart = "always";
DynamicUser = true;
StateDirectory = "honk";
WorkingDirectory = "/var/lib/honk";
};
};
# NixOS 20.03 broke nginx and I can't be bothered to debug it
# anymore, all solution attempts have failed, so here's a
# brute-force fix.
systemd.services.fix-nginx = {
script = "${nixpkgs.coreutils}/bin/chown -R nginx: /var/spool/nginx /var/cache/nginx";
serviceConfig = {
User = "root";
Type = "oneshot";
};
};
systemd.timers.fix-nginx = {
wantedBy = [ "multi-user.target" ];
timerConfig = {
OnCalendar = "minutely";
};
};
# Provision a TLS certificate outside of nginx to avoid
# nixpkgs#38144
security.acme = {
acceptTerms = true;
email = "mail@tazj.in";
certs."tazj.in" = {
user = "nginx";
group = "nginx";
webroot = "/var/lib/acme/acme-challenge";
extraDomains = {
"cs.tazj.in" = null;
"git.tazj.in" = null;
"www.tazj.in" = null;
# Local domains (for this machine only)
"camden.tazj.in" = null;
};
postRun = "systemctl reload nginx";
};
certs."tvl.fyi" = {
user = "nginx";
group = "nginx";
webroot = "/var/lib/acme/acme-challenge";
postRun = "systemctl reload nginx";
extraDomains = {
"b.tvl.fyi" = null;
"cl.tvl.fyi" = null;
"code.tvl.fyi" = null;
"cs.tvl.fyi" = null;
};
};
};
# Forward logs to Google Cloud Platform
services.journaldriver = {
enable = true;
logStream = "home";
googleCloudProject = "tazjins-infrastructure";
applicationCredentials = "/etc/gcp/key.json";
};
# Run a SourceGraph code search instance
services.depot.sourcegraph.enable = true;
# Start a local SMTP relay to Gmail (used by gerrit)
services.depot.smtprelay = {
enable = true;
args = {
listen = ":2525";
remote_host = "smtp.gmail.com:587";
remote_auth = "plain";
remote_user = "tvlbot@tazj.in";
};
};
# serve my website(s)
services.nginx = {
enable = true;
enableReload = true;
package = with nixpkgs; nginx.override {
modules = [ nginxModules.rtmp ];
};
recommendedTlsSettings = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
appendConfig = ''
rtmp_auto_push on;
rtmp {
server {
listen 1935;
chunk_size 4000;
application tvl {
live on;
allow publish 88.98.195.213;
allow publish 10.0.1.0/24;
deny publish all;
allow play all;
}
}
}
'';
commonHttpConfig = ''
log_format json_combined escape=json
'{'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"request_size":$request_length,'
'"response_size":$body_bytes_sent,'
'"response_time":$request_time,'
'"referrer":"$http_referer",'
'"user_agent":"$http_user_agent"'
'}';
access_log syslog:server=unix:/dev/log,nohostname json_combined;
'';
virtualHosts.homepage = {
serverName = "tazj.in";
serverAliases = [ "camden.tazj.in" ];
default = true;
useACMEHost = "tazj.in";
root = depot.web.homepage;
forceSSL = true;
extraConfig = ''
${depot.web.blog.oldRedirects}
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
location ~* \.(webp|woff2)$ {
add_header Cache-Control "public, max-age=31536000";
}
location /blog/ {
alias ${depot.web.blog.rendered}/;
if ($request_uri ~ ^/(.*)\.html$) {
return 302 /$1;
}
try_files $uri $uri.html $uri/ =404;
}
location /blobs/ {
alias /var/www/blobs/;
}
'';
};
virtualHosts.tvl = {
serverName = "tvl.fyi";
useACMEHost = "tvl.fyi";
root = depot.web.tvl;
forceSSL = true;
extraConfig = ''
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
rewrite ^/builds/?$ https://builds.sr.ht/~tazjin/depot last;
rewrite ^/meet/?$ https://meet.google.com/mng-biyw-xbb last;
rewrite ^/monorepo-doc/?$ https://docs.google.com/document/d/1nnyByXcH0F6GOmEezNOUa2RFelpeRpDToBLYD_CtjWE/edit?usp=sharing last;
rewrite ^/irc/?$ ircs://chat.freenode.net:6697/##tvl last;
location ~* \.(webp|woff2)$ {
add_header Cache-Control "public, max-age=31536000";
}
'';
};
virtualHosts.cgit = {
serverName = "code.tvl.fyi";
useACMEHost = "tvl.fyi";
forceSSL = true;
extraConfig = ''
# Static assets must always hit the root.
location ~ ^/(favicon\.ico|cgit\.(css|png))$ {
proxy_pass http://localhost:2448;
}
# Everything else hits the depot directly.
location / {
proxy_pass http://localhost:2448/cgit.cgi/depot/;
}
'';
};
virtualHosts.sourcegraph = {
serverName = "cs.tvl.fyi";
useACMEHost = "tvl.fyi";
forceSSL = true;
extraConfig = ''
location = / {
return 301 https://cs.tvl.fyi/depot;
}
location / {
proxy_set_header X-Sg-Auth "Anonymous";
proxy_pass http://localhost:3463;
}
location /users/Anonymous/settings {
return 301 https://cs.tvl.fyi;
}
'';
};
virtualHosts.gerrit = {
serverName = "cl.tvl.fyi";
useACMEHost = "tvl.fyi";
forceSSL = true;
extraConfig = ''
location / {
proxy_pass http://localhost:4778;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
}
'';
};
virtualHosts.cgit-old = nginxRedirect {
from = "git.tazj.in";
to = "code.tvl.fyi";
acmeHost = "tazj.in";
};
virtualHosts.cs-old = nginxRedirect {
from = "cs.tazj.in";
to = "cs.tvl.fyi";
acmeHost = "tazj.in";
};
};
# Timer units that can be started with systemd-run to set my alarm.
systemd.user.services.light-alarm = {
script = "${depot.fun.idual.script}/bin/idualctl wakey";
postStart = "${pkgs.systemd}/bin/systemctl --user stop light-alarm.timer";
serviceConfig = {
Type = "oneshot";
};
};
# Regularly back up Gerrit to Google Cloud Storage.
systemd.user.services.restic-gerrit = {
description = "Gerrit backups to Google Cloud Storage";
script = "${nixpkgs.restic}/bin/restic backup /var/lib/gerrit";
environment = {
RESTIC_REPOSITORY = "gs:tvl-fyi-backups:/camden";
RESTIC_PASSWORD_FILE = "%h/.config/restic/secret";
RESTIC_EXCLUDE_FILE = builtins.toFile "exclude-files" ''
/var/lib/gerrit/etc/secure.config
/var/lib/gerrit/etc/ssh_host_*_key
/var/lib/gerrit/etc/ssh_host_*_key
/var/lib/gerrit/etc/ssh_host_*_key
/var/lib/gerrit/etc/ssh_host_*_key
/var/lib/gerrit/etc/ssh_host_*_key
/var/lib/gerrit/tmp
'';
};
};
systemd.user.timers.restic-gerrit = {
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "hourly";
};
system.stateVersion = "19.09";
})