NixOS Module
Declarative server deployment with NixOS
The funnel NixOS module runs the server as a systemd service with hardened security settings.
Import
{
imports = [ inputs.funnel.nixosModules.default ];
}Basic configuration
{
services.funnel.server = {
enable = true;
host = "0.0.0.0";
port = 8080;
quicPort = 4433;
seedApiKey = true;
};
}Database
Embedded Turso
{
services.funnel.server = {
enable = true;
database.tursoPath = "/var/lib/funnel/funnel.db";
};
}PostgreSQL
{
services.funnel.server = {
enable = true;
database = {
postgresUrl = "postgres://funnel:password@localhost/funnel";
maxConnections = 20;
};
};
}To create a local PostgreSQL database automatically:
{
services.funnel.server = {
enable = true;
database.createLocally = true;
};
}TLS
{
services.funnel.server = {
enable = true;
tls = {
enable = true;
port = 443;
certDir = "/var/lib/funnel/certs";
acme = {
email = "admin@example.com";
staging = false;
dnsProvidersConfigFile = "/run/secrets/dns-providers.json";
};
};
};
}OAuth
GitHub
{
services.funnel.server = {
enable = true;
auth = {
baseUrl = "https://tunnel.example.com";
initialAdminEmail = "admin@example.com";
github.clientId = "your-client-id";
};
# secret via environment file
environmentFile = "/run/secrets/funnel-env";
};
}The environment file should contain:
GITHUB_CLIENT_SECRET=your-client-secretGeneric OIDC
{
services.funnel.server.auth.oauth = {
providerName = "gitlab";
clientId = "your-client-id";
authorizeUrl = "https://gitlab.com/oauth/authorize";
tokenUrl = "https://gitlab.com/oauth/token";
userinfoUrl = "https://gitlab.com/api/v4/user";
};
}Secrets
Use environmentFile or environmentFiles to inject secrets without putting them in the Nix store:
{
services.funnel.server.environmentFile = "/run/secrets/funnel-env";
}This integrates with sops-nix, agenix, or any secret management tool that writes files.
Firewall
{
services.funnel.server = {
enable = true;
openFirewall = true; # opens port and quicPort
};
}Systemd hardening
The module applies these security settings by default:
DynamicUser: runs as a transient system userPrivateTmp: isolated temp directoryProtectSystem=strict: read-only filesystemProtectHome: no access to home directoriesNoNewPrivileges: prevents privilege escalation- Restricted syscall filters and namespaces
Override with serviceConfig:
{
services.funnel.server.serviceConfig = {
MemoryMax = "512M";
};
}