mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-14 14:10:33 +03:00
Merge remote-tracking branch 'origin/master' into staging-next
This commit is contained in:
commit
0717a4da77
97 changed files with 14181 additions and 1740 deletions
|
@ -3,25 +3,33 @@
|
|||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
}: let
|
||||
cfg = config.services.headscale;
|
||||
|
||||
dataDir = "/var/lib/headscale";
|
||||
runDir = "/run/headscale";
|
||||
|
||||
cliConfig = {
|
||||
# Turn off update checks since the origin of our package
|
||||
# is nixpkgs and not Github.
|
||||
disable_check_updates = true;
|
||||
|
||||
unix_socket = "${runDir}/headscale.sock";
|
||||
};
|
||||
|
||||
settingsFormat = pkgs.formats.yaml {};
|
||||
configFile = settingsFormat.generate "headscale.yaml" cfg.settings;
|
||||
cliConfigFile = settingsFormat.generate "headscale.yaml" cliConfig;
|
||||
in {
|
||||
options = {
|
||||
services.headscale = {
|
||||
enable = mkEnableOption "headscale, Open Source coordination server for Tailscale";
|
||||
enable = lib.mkEnableOption "headscale, Open Source coordination server for Tailscale";
|
||||
|
||||
package = mkPackageOption pkgs "headscale" { };
|
||||
package = lib.mkPackageOption pkgs "headscale" {};
|
||||
|
||||
user = mkOption {
|
||||
user = lib.mkOption {
|
||||
default = "headscale";
|
||||
type = types.str;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
User account under which headscale runs.
|
||||
|
||||
|
@ -33,9 +41,9 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
group = lib.mkOption {
|
||||
default = "headscale";
|
||||
type = types.str;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Group under which headscale runs.
|
||||
|
||||
|
@ -47,8 +55,8 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = ''
|
||||
Listening address of headscale.
|
||||
|
@ -56,8 +64,8 @@ in {
|
|||
example = "0.0.0.0";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8080;
|
||||
description = ''
|
||||
Listening port of headscale.
|
||||
|
@ -65,18 +73,33 @@ in {
|
|||
example = 443;
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
settings = lib.mkOption {
|
||||
description = ''
|
||||
Overrides to {file}`config.yaml` as a Nix attribute set.
|
||||
Check the [example config](https://github.com/juanfont/headscale/blob/main/config-example.yaml)
|
||||
for possible options.
|
||||
'';
|
||||
type = types.submodule {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
imports = with lib; [
|
||||
(mkAliasOptionModule ["acl_policy_path"] ["policy" "path"])
|
||||
(mkAliasOptionModule ["db_host"] ["database" "postgres" "host"])
|
||||
(mkAliasOptionModule ["db_name"] ["database" "postgres" "name"])
|
||||
(mkAliasOptionModule ["db_password_file"] ["database" "postgres" "password_file"])
|
||||
(mkAliasOptionModule ["db_path"] ["database" "sqlite" "path"])
|
||||
(mkAliasOptionModule ["db_port"] ["database" "postgres" "port"])
|
||||
(mkAliasOptionModule ["db_type"] ["database" "type"])
|
||||
(mkAliasOptionModule ["db_user"] ["database" "postgres" "user"])
|
||||
(mkAliasOptionModule ["dns_config" "base_domain"] ["dns" "base_domain"])
|
||||
(mkAliasOptionModule ["dns_config" "domains"] ["dns" "search_domains"])
|
||||
(mkAliasOptionModule ["dns_config" "magic_dns"] ["dns" "magic_dns"])
|
||||
(mkAliasOptionModule ["dns_config" "nameservers"] ["dns" "nameservers" "global"])
|
||||
];
|
||||
|
||||
options = {
|
||||
server_url = mkOption {
|
||||
type = types.str;
|
||||
server_url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "http://127.0.0.1:8080";
|
||||
description = ''
|
||||
The url clients will connect to.
|
||||
|
@ -84,25 +107,49 @@ in {
|
|||
example = "https://myheadscale.example.com:443";
|
||||
};
|
||||
|
||||
private_key_path = mkOption {
|
||||
type = types.path;
|
||||
default = "${dataDir}/private.key";
|
||||
description = ''
|
||||
Path to private key file, generated automatically if it does not exist.
|
||||
'';
|
||||
};
|
||||
|
||||
noise.private_key_path = mkOption {
|
||||
type = types.path;
|
||||
noise.private_key_path = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "${dataDir}/noise_private.key";
|
||||
description = ''
|
||||
Path to noise private key file, generated automatically if it does not exist.
|
||||
'';
|
||||
};
|
||||
|
||||
prefixes = let
|
||||
prefDesc = ''
|
||||
Each prefix consists of either an IPv4 or IPv6 address,
|
||||
and the associated prefix length, delimited by a slash.
|
||||
It must be within IP ranges supported by the Tailscale
|
||||
client - i.e., subnets of 100.64.0.0/10 and fd7a:115c:a1e0::/48.
|
||||
'';
|
||||
in {
|
||||
v4 = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "100.64.0.0/10";
|
||||
description = prefDesc;
|
||||
};
|
||||
|
||||
v6 = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "fd7a:115c:a1e0::/48";
|
||||
description = prefDesc;
|
||||
};
|
||||
|
||||
allocation = lib.mkOption {
|
||||
type = lib.types.enum ["sequential" "random"];
|
||||
example = "random";
|
||||
default = "sequential";
|
||||
description = ''
|
||||
Strategy used for allocation of IPs to nodes, available options:
|
||||
- sequential (default): assigns the next free IP from the previous given IP.
|
||||
- random: assigns the next free IP from a pseudo-random IP generator (crypto/rand).
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
derp = {
|
||||
urls = mkOption {
|
||||
type = types.listOf types.str;
|
||||
urls = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = ["https://controlplane.tailscale.com/derpmap/default"];
|
||||
description = ''
|
||||
List of urls containing DERP maps.
|
||||
|
@ -110,8 +157,8 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
paths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
paths = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.path;
|
||||
default = [];
|
||||
description = ''
|
||||
List of file paths containing DERP maps.
|
||||
|
@ -119,8 +166,8 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
auto_update_enable = mkOption {
|
||||
type = types.bool;
|
||||
auto_update_enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to automatically update DERP maps on a set frequency.
|
||||
|
@ -128,18 +175,26 @@ in {
|
|||
example = false;
|
||||
};
|
||||
|
||||
update_frequency = mkOption {
|
||||
type = types.str;
|
||||
update_frequency = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "24h";
|
||||
description = ''
|
||||
Frequency to update DERP maps.
|
||||
'';
|
||||
example = "5m";
|
||||
};
|
||||
|
||||
server.private_key_path = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "${dataDir}/derp_server_private.key";
|
||||
description = ''
|
||||
Path to derp private key file, generated automatically if it does not exist.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
ephemeral_node_inactivity_timeout = mkOption {
|
||||
type = types.str;
|
||||
ephemeral_node_inactivity_timeout = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "30m";
|
||||
description = ''
|
||||
Time before an inactive ephemeral node is deleted.
|
||||
|
@ -147,104 +202,100 @@ in {
|
|||
example = "5m";
|
||||
};
|
||||
|
||||
db_type = mkOption {
|
||||
type = types.enum ["sqlite3" "postgres"];
|
||||
example = "postgres";
|
||||
default = "sqlite3";
|
||||
description = "Database engine to use.";
|
||||
};
|
||||
|
||||
db_host = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "127.0.0.1";
|
||||
description = "Database host address.";
|
||||
};
|
||||
|
||||
db_port = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
example = 3306;
|
||||
description = "Database host port.";
|
||||
};
|
||||
|
||||
db_name = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "headscale";
|
||||
description = "Database name.";
|
||||
};
|
||||
|
||||
db_user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "headscale";
|
||||
description = "Database user.";
|
||||
};
|
||||
|
||||
db_password_file = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/run/keys/headscale-dbpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
{option}`database.user`.
|
||||
'';
|
||||
};
|
||||
|
||||
db_path = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "${dataDir}/db.sqlite";
|
||||
description = "Path to the sqlite3 database file.";
|
||||
};
|
||||
|
||||
log.level = mkOption {
|
||||
type = types.str;
|
||||
default = "info";
|
||||
description = ''
|
||||
headscale log level.
|
||||
'';
|
||||
example = "debug";
|
||||
};
|
||||
|
||||
log.format = mkOption {
|
||||
type = types.str;
|
||||
default = "text";
|
||||
description = ''
|
||||
headscale log format.
|
||||
'';
|
||||
example = "json";
|
||||
};
|
||||
|
||||
dns_config = {
|
||||
nameservers = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = ["1.1.1.1"];
|
||||
database = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum ["sqlite" "sqlite3" "postgres"];
|
||||
example = "postgres";
|
||||
default = "sqlite";
|
||||
description = ''
|
||||
List of nameservers to pass to Tailscale clients.
|
||||
Database engine to use.
|
||||
Please note that using Postgres is highly discouraged as it is only supported for legacy reasons.
|
||||
All new development, testing and optimisations are done with SQLite in mind.
|
||||
'';
|
||||
};
|
||||
|
||||
override_local_dns = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to use [Override local DNS](https://tailscale.com/kb/1054/dns/).
|
||||
'';
|
||||
example = true;
|
||||
sqlite = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = "${dataDir}/db.sqlite";
|
||||
description = "Path to the sqlite3 database file.";
|
||||
};
|
||||
|
||||
write_ahead_log = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable WAL mode for SQLite. This is recommended for production environments.
|
||||
https://www.sqlite.org/wal.html
|
||||
'';
|
||||
example = true;
|
||||
};
|
||||
};
|
||||
|
||||
domains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
postgres = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "127.0.0.1";
|
||||
description = "Database host address.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.port;
|
||||
default = null;
|
||||
example = 3306;
|
||||
description = "Database host port.";
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "headscale";
|
||||
description = "Database name.";
|
||||
};
|
||||
|
||||
user = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "headscale";
|
||||
description = "Database user.";
|
||||
};
|
||||
|
||||
password_file = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/run/keys/headscale-dbpassword";
|
||||
description = ''
|
||||
A file containing the password corresponding to
|
||||
{option}`database.user`.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
log = {
|
||||
level = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "info";
|
||||
description = ''
|
||||
Search domains to inject to Tailscale clients.
|
||||
headscale log level.
|
||||
'';
|
||||
example = ["mydomain.internal"];
|
||||
example = "debug";
|
||||
};
|
||||
|
||||
magic_dns = mkOption {
|
||||
type = types.bool;
|
||||
format = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "text";
|
||||
description = ''
|
||||
headscale log format.
|
||||
'';
|
||||
example = "json";
|
||||
};
|
||||
};
|
||||
|
||||
dns = {
|
||||
magic_dns = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
|
||||
|
@ -253,8 +304,8 @@ in {
|
|||
example = false;
|
||||
};
|
||||
|
||||
base_domain = mkOption {
|
||||
type = types.str;
|
||||
base_domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Defines the base domain to create the hostnames for MagicDNS.
|
||||
|
@ -264,11 +315,30 @@ in {
|
|||
`myhost.mynamespace.example.com`).
|
||||
'';
|
||||
};
|
||||
|
||||
nameservers = {
|
||||
global = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
List of nameservers to pass to Tailscale clients.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
search_domains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Search domains to inject to Tailscale clients.
|
||||
'';
|
||||
example = ["mydomain.internal"];
|
||||
};
|
||||
};
|
||||
|
||||
oidc = {
|
||||
issuer = mkOption {
|
||||
type = types.str;
|
||||
issuer = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
URL to OpenID issuer.
|
||||
|
@ -276,33 +346,33 @@ in {
|
|||
example = "https://openid.example.com";
|
||||
};
|
||||
|
||||
client_id = mkOption {
|
||||
type = types.str;
|
||||
client_id = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
OpenID Connect client ID.
|
||||
'';
|
||||
};
|
||||
|
||||
client_secret_path = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
client_secret_path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to OpenID Connect client secret file. Expands environment variables in format ''${VAR}.
|
||||
'';
|
||||
};
|
||||
|
||||
scope = mkOption {
|
||||
type = types.listOf types.str;
|
||||
scope = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = ["openid" "profile" "email"];
|
||||
description = ''
|
||||
Scopes used in the OIDC flow.
|
||||
'';
|
||||
};
|
||||
|
||||
extra_params = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = { };
|
||||
extra_params = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
Custom query parameters to send with the Authorize Endpoint request.
|
||||
'';
|
||||
|
@ -311,27 +381,27 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
allowed_domains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
allowed_domains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Allowed principal domains. if an authenticated user's domain
|
||||
is not in this list authentication request will be rejected.
|
||||
'';
|
||||
example = [ "example.com" ];
|
||||
example = ["example.com"];
|
||||
};
|
||||
|
||||
allowed_users = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
allowed_users = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Users allowed to authenticate even if not in allowedDomains.
|
||||
'';
|
||||
example = [ "alice@example.com" ];
|
||||
example = ["alice@example.com"];
|
||||
};
|
||||
|
||||
strip_email_domain = mkOption {
|
||||
type = types.bool;
|
||||
strip_email_domain = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the domain part of the email address should be removed when generating namespaces.
|
||||
|
@ -339,16 +409,16 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
tls_letsencrypt_hostname = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
tls_letsencrypt_hostname = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Domain name to request a TLS certificate for.
|
||||
'';
|
||||
};
|
||||
|
||||
tls_letsencrypt_challenge_type = mkOption {
|
||||
type = types.enum ["TLS-ALPN-01" "HTTP-01"];
|
||||
tls_letsencrypt_challenge_type = lib.mkOption {
|
||||
type = lib.types.enum ["TLS-ALPN-01" "HTTP-01"];
|
||||
default = "HTTP-01";
|
||||
description = ''
|
||||
Type of ACME challenge to use, currently supported types:
|
||||
|
@ -356,8 +426,8 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
tls_letsencrypt_listen = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
tls_letsencrypt_listen = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = ":http";
|
||||
description = ''
|
||||
When HTTP-01 challenge is chosen, letsencrypt must set up a
|
||||
|
@ -366,28 +436,40 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
tls_cert_path = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
tls_cert_path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to already created certificate.
|
||||
'';
|
||||
};
|
||||
|
||||
tls_key_path = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
tls_key_path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to key for already created certificate.
|
||||
'';
|
||||
};
|
||||
|
||||
acl_policy_path = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to a file containing ACL policies.
|
||||
'';
|
||||
policy = {
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.enum ["file" "database"];
|
||||
default = "file";
|
||||
description = ''
|
||||
The mode can be "file" or "database" that defines
|
||||
where the ACL policies are stored and read from.
|
||||
'';
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
If the mode is set to "file", the path to a
|
||||
HuJSON file containing ACL policies.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -395,67 +477,49 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
# TODO address + port = listen_addr
|
||||
(mkRenamedOptionModule ["services" "headscale" "serverUrl"] ["services" "headscale" "settings" "server_url"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "privateKeyFile"] ["services" "headscale" "settings" "private_key_path"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "derp" "urls"] ["services" "headscale" "settings" "derp" "urls"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "derp" "paths"] ["services" "headscale" "settings" "derp" "paths"])
|
||||
imports = with lib; [
|
||||
(mkRenamedOptionModule ["services" "headscale" "derp" "autoUpdate"] ["services" "headscale" "settings" "derp" "auto_update_enable"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "derp" "paths"] ["services" "headscale" "settings" "derp" "paths"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "derp" "updateFrequency"] ["services" "headscale" "settings" "derp" "update_frequency"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "derp" "urls"] ["services" "headscale" "settings" "derp" "urls"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "ephemeralNodeInactivityTimeout"] ["services" "headscale" "settings" "ephemeral_node_inactivity_timeout"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "database" "type"] ["services" "headscale" "settings" "db_type"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "database" "path"] ["services" "headscale" "settings" "db_path"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "database" "host"] ["services" "headscale" "settings" "db_host"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "database" "port"] ["services" "headscale" "settings" "db_port"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "database" "name"] ["services" "headscale" "settings" "db_name"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "database" "user"] ["services" "headscale" "settings" "db_user"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "database" "passwordFile"] ["services" "headscale" "settings" "db_password_file"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "logLevel"] ["services" "headscale" "settings" "log" "level"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "dns" "nameservers"] ["services" "headscale" "settings" "dns_config" "nameservers"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "dns" "domains"] ["services" "headscale" "settings" "dns_config" "domains"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "dns" "magicDns"] ["services" "headscale" "settings" "dns_config" "magic_dns"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "dns" "baseDomain"] ["services" "headscale" "settings" "dns_config" "base_domain"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientId"] ["services" "headscale" "settings" "oidc" "client_id"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_path"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "serverUrl"] ["services" "headscale" "settings" "server_url"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "certFile"] ["services" "headscale" "settings" "tls_cert_path"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "keyFile"] ["services" "headscale" "settings" "tls_key_path"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "aclPolicyFile"] ["services" "headscale" "settings" "acl_policy_path"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"])
|
||||
(mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"])
|
||||
|
||||
(mkRemovedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ''
|
||||
Headscale no longer uses domain_map. If you're using an old version of headscale you can still set this option via services.headscale.settings.oidc.domain_map.
|
||||
'')
|
||||
];
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.headscale.settings = {
|
||||
listen_addr = mkDefault "${cfg.address}:${toString cfg.port}";
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.headscale.settings = lib.mkMerge [
|
||||
cliConfig
|
||||
{
|
||||
listen_addr = lib.mkDefault "${cfg.address}:${toString cfg.port}";
|
||||
|
||||
# Turn off update checks since the origin of our package
|
||||
# is nixpkgs and not Github.
|
||||
disable_check_updates = true;
|
||||
|
||||
unix_socket = "${runDir}/headscale.sock";
|
||||
|
||||
tls_letsencrypt_cache_dir = "${dataDir}/.cache";
|
||||
};
|
||||
tls_letsencrypt_cache_dir = "${dataDir}/.cache";
|
||||
}
|
||||
];
|
||||
|
||||
environment = {
|
||||
# Setup the headscale configuration in a known path in /etc to
|
||||
# allow both the Server and the Client use it to find the socket
|
||||
# for communication.
|
||||
etc."headscale/config.yaml".source = configFile;
|
||||
# Headscale CLI needs a minimal config to be able to locate the unix socket
|
||||
# to talk to the server instance.
|
||||
etc."headscale/config.yaml".source = cliConfigFile;
|
||||
|
||||
systemPackages = [ cfg.package ];
|
||||
systemPackages = [cfg.package];
|
||||
};
|
||||
|
||||
users.groups.headscale = mkIf (cfg.group == "headscale") {};
|
||||
users.groups.headscale = lib.mkIf (cfg.group == "headscale") {};
|
||||
|
||||
users.users.headscale = mkIf (cfg.user == "headscale") {
|
||||
users.users.headscale = lib.mkIf (cfg.user == "headscale") {
|
||||
description = "headscale user";
|
||||
home = dataDir;
|
||||
group = cfg.group;
|
||||
|
@ -464,23 +528,20 @@ in {
|
|||
|
||||
systemd.services.headscale = {
|
||||
description = "headscale coordination server for Tailscale";
|
||||
wants = [ "network-online.target" ];
|
||||
wants = ["network-online.target"];
|
||||
after = ["network-online.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
restartTriggers = [configFile];
|
||||
|
||||
environment.GIN_MODE = "release";
|
||||
|
||||
script = ''
|
||||
${optionalString (cfg.settings.db_password_file != null) ''
|
||||
export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})"
|
||||
${lib.optionalString (cfg.settings.database.postgres.password_file != null) ''
|
||||
export HEADSCALE_DATABASE_POSTGRES_PASS="$(head -n1 ${lib.escapeShellArg cfg.settings.database.postgres.password_file})"
|
||||
''}
|
||||
|
||||
exec ${cfg.package}/bin/headscale serve
|
||||
exec ${lib.getExe cfg.package} serve --config ${configFile}
|
||||
'';
|
||||
|
||||
serviceConfig = let
|
||||
capabilityBoundingSet = ["CAP_CHOWN"] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
|
||||
capabilityBoundingSet = ["CAP_CHOWN"] ++ lib.optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
|
||||
in {
|
||||
Restart = "always";
|
||||
Type = "simple";
|
||||
|
@ -525,5 +586,5 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [kradalby misterio77];
|
||||
meta.maintainers = with lib.maintainers; [kradalby misterio77];
|
||||
}
|
||||
|
|
132
nixos/modules/services/networking/tailscale-derper.nix
Normal file
132
nixos/modules/services/networking/tailscale-derper.nix
Normal file
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.tailscale.derper;
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];
|
||||
|
||||
options = {
|
||||
services.tailscale.derper = {
|
||||
enable = lib.mkEnableOption "Tailscale Derper. See upstream doc <https://tailscale.com/kb/1118/custom-derp-servers> how to configure it on clients";
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Domain name under which the derper server is reachable.";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to open the firewall for the specified port.
|
||||
Derper requires the used ports to be opened, otherwise it doesn't work as expected.
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs [
|
||||
"tailscale"
|
||||
"derper"
|
||||
] { };
|
||||
|
||||
stunPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3478;
|
||||
description = ''
|
||||
STUN port to listen on.
|
||||
See online docs <https://tailscale.com/kb/1118/custom-derp-servers#prerequisites> on how to configure a different external port.
|
||||
'';
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8010;
|
||||
description = "The port the derper process will listen on. This is not the port tailscale will connect to.";
|
||||
};
|
||||
|
||||
verifyClients = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to verify clients against a locally running tailscale daemon if they are allowed to connect to this node or not.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
# port 80 and 443 are opened by nginx already
|
||||
allowedUDPPorts = [ cfg.stunPort ];
|
||||
};
|
||||
|
||||
services = {
|
||||
nginx = {
|
||||
enable = true;
|
||||
upstreams.tailscale-derper = {
|
||||
servers."127.0.0.1:${toString cfg.port}" = { };
|
||||
extraConfig = ''
|
||||
keepalive 64;
|
||||
'';
|
||||
};
|
||||
virtualHosts."${cfg.domain}" = {
|
||||
addSSL = true; # this cannot be forceSSL as derper sends some information over port 80, too.
|
||||
locations."/" = {
|
||||
proxyPass = "http://tailscale-derper";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
keepalive_timeout 0;
|
||||
proxy_buffering off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
tailscale.enable = lib.mkIf cfg.verifyClients true;
|
||||
};
|
||||
|
||||
systemd.services.tailscale-derper = {
|
||||
serviceConfig = {
|
||||
ExecStart =
|
||||
"${lib.getExe' cfg.package "derper"} -a :${toString cfg.port} -c /var/lib/derper/derper.key -hostname=${cfg.domain} -stun-port ${toString cfg.stunPort}"
|
||||
+ lib.optionalString cfg.verifyClients " -verify-clients";
|
||||
DynamicUser = true;
|
||||
Restart = "always";
|
||||
RestartSec = "5sec"; # don't crash loop immediately
|
||||
StateDirectory = "derper";
|
||||
Type = "simple";
|
||||
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
DeviceAllow = null;
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" ];
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -10,7 +10,7 @@ in
|
|||
options.services.node-red = {
|
||||
enable = mkEnableOption "the Node-RED service";
|
||||
|
||||
package = mkPackageOption pkgs [ "nodePackages" "node-red" ] { };
|
||||
package = mkPackageOption pkgs [ "node-red" ] { };
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
|
@ -31,8 +31,8 @@ in
|
|||
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.package}/lib/node_modules/node-red/settings.js";
|
||||
defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/settings.js"'';
|
||||
default = "${cfg.package}/lib/node_modules/node-red/packages/node_modules/node-red/settings.js";
|
||||
defaultText = literalExpression ''"''${package}/lib/node_modules/node-red/packages/node_modules/node-red/settings.js"'';
|
||||
description = ''
|
||||
Path to the JavaScript configuration file.
|
||||
See <https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue