NixOS
Sandhole is available as a flake, containing an overlay and a NixOS service.
Setup
If you’re using Nix Flakes for your system, you can install the NixOS service like so:
{
inputs = {
# ...
sandhole.url = "github:EpicEric/sandhole";
};
outputs =
{
nixpkgs,
sandhole,
...
}@inputs:
{
nixosConfigurations."your-hostname" = nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
modules = [
# ...
sandhole.nixosModules.sandhole
];
};
};
}
Here’s an example configuration.nix with Sandhole and Agnos. You can find full options in the NixOS module options page:
{
pkgs,
...
}:
let
# ...
# Add admin keys to this directory
adminKeys = pkgs.linkFarm "sandhole-admin-keys" [
{
name = "example-admin.pub";
path = pkgs.writeText "example-admin.pub" ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPH3e5SFdwLOuleypjfgauqEUAmgpm9r8lqfvc6G1o1D example-admin
'';
}
];
# Add user keys to this directory
userKeys = pkgs.linkFarm "sandhole-user-keys" [
{
name = "example-user.pub";
path = pkgs.writeText "example-user.pub" ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtH7kS+q8/8TXWAp4OJvRh/7GNkQ6FR/QBOhGJuEwEC example-user
'';
}
];
admin-keys-directory = "/etc/sandhole/admin-keys";
user-keys-directory = "/etc/sandhole/user-keys";
certificates-directory = "/var/lib/sandhole/certificates";
in
{
# ...
# By symlinking to /etc, Sandhole doesn't have to restart when modifying keys
environment.etc = {
"sandhole/admin-keys".source = adminKeys;
"sandhole/user-keys".source = userKeys;
};
# Configurations for Sandhole
services.sandhole = {
# Install the Sandhole package and enable the systemd service
enable = true;
# Let Sandhole manage the firewall and open ports from its configuration.
# Note: If `disableTcp` is `false` (default), it will open all ports >= 1024
openFirewall = true;
# These are the same CLI options from Sandhole, except without leading hyphens.
# See: http://sandhole.com.br/cli.html
# Make sure to change at least `domain` and `acme-contact-email` below
settings = {
domain = "sandhole.com.br";
acme-contact-email = "admin@sandhole.com.br";
disable-tcp = true;
force-https = true;
inherit
admin-keys-directory
user-keys-directory
certificates-directory
;
};
};
security.agnos = {
enable = true;
temporarilyOpenFirewall = true;
user = "sandhole";
generateKeys.enable = true;
settings =
{
dns_listen_addr = "[::]:53";
accounts =
[
{
# Change this to your e-mail address
email = "admin@sandhole.com.br";
private_key_path = "./letsencrypt_key.pem";
certificates =
[
{
# Change these from `sandhole.com.br` to your domain
domains = [ "sandhole.com.br" "*.sandhole.com.br" ];
fullchain_output_file = "${certificatesDirectory}/sandhole.com.br/fullchain.pem";
key_output_file = "${certificatesDirectory}/sandhole.com.br/privkey.pem";
}
];
}
];
};
};
}
You can then connect services with the provided keys. For example, to use a Vaultwarden NixOS container in the same machine:
{
lib,
...
}:
{
# ...
networking.nat = {
enable = true;
internalInterfaces = ["ve-+"];
externalInterface = "eno0"; # Change to the appropriate interface
enableIPv6 = true;
};
# Example: Setting up Vaultwarden
containers.vaultwarden = {
autoStart = true;
privateNetwork = true;
hostAddress = "192.168.102.1";
localAddress = "192.168.102.2";
hostAddress6 = "fc00::2:1";
localAddress6 = "fc00::2:2";
extraFlags = [ "-U" ];
config =
{ lib, ... }:
{
services.vaultwarden = {
enable = true;
config = {
DOMAIN = "https://vaultwarden.sandhole.com.br";
SIGNUPS_ALLOWED = false;
ROCKET_ADDRESS = "::";
ROCKET_PORT = 8222;
ROCKET_LOG = "warning";
};
};
networking = {
firewall.allowedTCPPorts = [ 8222 ];
useHostResolvConf = lib.mkForce false;
};
services.resolved.enable = true;
system.stateVersion = "25.11";
};
};
# Proxy Vaultwarden to the local Sandhole instance
services.autossh.sessions = [
{
name = "vaultwarden";
user = "root";
# Change the arguments as necessary
extraArguments = ''
-i /path/to/ssh/key/example-user \
-o StrictHostKeyChecking=accept-new \
-o ServerAliveInterval=30 \
-R vaultwarden.sandhole.com.br:80:192.168.102.2:8222 \
-p 2222 \
127.0.0.1
'';
}
];
}
Binary caching
In order to avoid re-building Sandhole for each update, you can use either of the Sandhole binary caches. In configuration.nix:
nix.settings = {
substituters = [
"https://sandhole.cachix.org"
"https://cache.garnix.io"
];
trusted-public-keys = [
"sandhole.cachix.org-1:cZadr6kgjQcRvsr++Nv9kgtMOrbLahiZBpuI9WpIXvA="
"cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="
];
};