diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
index 5188c51c71805..b410a660c5518 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
@@ -100,7 +100,7 @@
- minio removed support for it’s legacy
+ minio removed support for its legacy
filesystem backend in
RELEASE.2022-10-29T06-21-33Z.
This means if your storage was created with the old format,
@@ -113,9 +113,9 @@
minio_legacy_fs. Use it via
services.minio.package = minio_legacy_fs;
to export your data before switching to the new version. See
- the corresponding issue
- https://github.com/NixOS/nixpkgs/issues/199318) for more
- details.
+ the corresponding
+ issue
+ for more details.
@@ -288,6 +288,29 @@
remote PostgreSQL database.
+
+
+ The module services.headscale was
+ refactored to be compliant with
+ RFC
+ 0042. To be precise, this means that the following
+ things have changed:
+
+
+
+
+ Most settings has been migrated under
+ services.headscale.settings
+ which is an attribute-set that will be converted into
+ headscale’s YAML config format. This means that the
+ configuration from
+ headscale’s
+ example configuration can be directly written as
+ attribute-set in Nix within this option.
+
+
+
+
A new virtualisation.rosetta module was
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index 9cbeea2fd6217..911575d8ab530 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -61,7 +61,7 @@ In addition to numerous new and upgraded packages, this release has the followin
-- `vim_configurable` has been renamed to `vim-full` to avoid confusion: `vim-full`'s build-time features are configurable, but both `vim` and `vim-full` are *customizable* (in the sense of user configuration, like vimrc).
+- `vim_configurable` has been renamed to `vim-full` to avoid confusion: `vim-full`'s build-time features are configurable, but both `vim` and `vim-full` are _customizable_ (in the sense of user configuration, like vimrc).
- The module for the application firewall `opensnitch` got the ability to configure rules. Available as [services.opensnitch.rules](#opt-services.opensnitch.rules)
@@ -80,6 +80,13 @@ In addition to numerous new and upgraded packages, this release has the followin
- `mastodon` now supports connection to a remote `PostgreSQL` database.
+- The module `services.headscale` was refactored to be compliant with [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md). To be precise, this means that the following things have changed:
+
+ - Most settings has been migrated under [services.headscale.settings](#opt-services.headscale.settings) which is an attribute-set that
+ will be converted into headscale's YAML config format. This means that the configuration from
+ [headscale's example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml)
+ can be directly written as attribute-set in Nix within this option.
+
- A new `virtualisation.rosetta` module was added to allow running `x86_64` binaries through [Rosetta](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment) inside virtualised NixOS guests on Apple silicon. This feature works by default with the [UTM](https://docs.getutm.app/) virtualisation [package](https://search.nixos.org/packages?channel=unstable&show=utm&from=0&size=1&sort=relevance&type=packages&query=utm).
- The new option `users.motdFile` allows configuring a Message Of The Day that can be updated dynamically.
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 29b632ff5d22a..cc46819eed5a6 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -1,15 +1,18 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
cfg = config.services.headscale;
dataDir = "/var/lib/headscale";
runDir = "/run/headscale";
- settingsFormat = pkgs.formats.yaml { };
+ settingsFormat = pkgs.formats.yaml {};
configFile = settingsFormat.generate "headscale.yaml" cfg.settings;
-in
-{
+in {
options = {
services.headscale = {
enable = mkEnableOption (lib.mdDoc "headscale, Open Source coordination server for Tailscale");
@@ -51,15 +54,6 @@ in
'';
};
- serverUrl = mkOption {
- type = types.str;
- default = "http://127.0.0.1:8080";
- description = lib.mdDoc ''
- The url clients will connect to.
- '';
- example = "https://myheadscale.example.com:443";
- };
-
address = mkOption {
type = types.str;
default = "127.0.0.1";
@@ -78,337 +72,346 @@ in
example = 443;
};
- privateKeyFile = mkOption {
- type = types.path;
- default = "${dataDir}/private.key";
- description = lib.mdDoc ''
- Path to private key file, generated automatically if it does not exist.
- '';
- };
-
- derp = {
- urls = mkOption {
- type = types.listOf types.str;
- default = [ "https://controlplane.tailscale.com/derpmap/default" ];
- description = lib.mdDoc ''
- List of urls containing DERP maps.
- See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
- '';
- };
-
- paths = mkOption {
- type = types.listOf types.path;
- default = [ ];
- description = lib.mdDoc ''
- List of file paths containing DERP maps.
- See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
- '';
- };
-
-
- autoUpdate = mkOption {
- type = types.bool;
- default = true;
- description = lib.mdDoc ''
- Whether to automatically update DERP maps on a set frequency.
- '';
- example = false;
- };
-
- updateFrequency = mkOption {
- type = types.str;
- default = "24h";
- description = lib.mdDoc ''
- Frequency to update DERP maps.
- '';
- example = "5m";
- };
-
- };
-
- ephemeralNodeInactivityTimeout = mkOption {
- type = types.str;
- default = "30m";
- description = lib.mdDoc ''
- Time before an inactive ephemeral node is deleted.
- '';
- example = "5m";
- };
-
- database = {
- type = mkOption {
- type = types.enum [ "sqlite3" "postgres" ];
- example = "postgres";
- default = "sqlite3";
- description = lib.mdDoc "Database engine to use.";
- };
-
- host = mkOption {
- type = types.nullOr types.str;
- default = null;
- example = "127.0.0.1";
- description = lib.mdDoc "Database host address.";
- };
-
- port = mkOption {
- type = types.nullOr types.port;
- default = null;
- example = 3306;
- description = lib.mdDoc "Database host port.";
- };
-
- name = mkOption {
- type = types.nullOr types.str;
- default = null;
- example = "headscale";
- description = lib.mdDoc "Database name.";
- };
-
- user = mkOption {
- type = types.nullOr types.str;
- default = null;
- example = "headscale";
- description = lib.mdDoc "Database user.";
- };
-
- passwordFile = mkOption {
- type = types.nullOr types.path;
- default = null;
- example = "/run/keys/headscale-dbpassword";
- description = lib.mdDoc ''
- A file containing the password corresponding to
- {option}`database.user`.
- '';
- };
-
- path = mkOption {
- type = types.nullOr types.str;
- default = "${dataDir}/db.sqlite";
- description = lib.mdDoc "Path to the sqlite3 database file.";
- };
- };
-
- logLevel = mkOption {
- type = types.str;
- default = "info";
- description = lib.mdDoc ''
- headscale log level.
- '';
- example = "debug";
- };
-
- dns = {
- nameservers = mkOption {
- type = types.listOf types.str;
- default = [ "1.1.1.1" ];
- description = lib.mdDoc ''
- List of nameservers to pass to Tailscale clients.
- '';
- };
-
- domains = mkOption {
- type = types.listOf types.str;
- default = [ ];
- description = lib.mdDoc ''
- Search domains to inject to Tailscale clients.
- '';
- example = [ "mydomain.internal" ];
- };
-
- magicDns = mkOption {
- type = types.bool;
- default = true;
- description = lib.mdDoc ''
- Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
- Only works if there is at least a nameserver defined.
- '';
- example = false;
- };
-
- baseDomain = mkOption {
- type = types.str;
- default = "";
- description = lib.mdDoc ''
- Defines the base domain to create the hostnames for MagicDNS.
- {option}`baseDomain` must be a FQDNs, without the trailing dot.
- The FQDN of the hosts will be
- `hostname.namespace.base_domain` (e.g.
- `myhost.mynamespace.example.com`).
- '';
- };
- };
-
- openIdConnect = {
- issuer = mkOption {
- type = types.str;
- default = "";
- description = lib.mdDoc ''
- URL to OpenID issuer.
- '';
- example = "https://openid.example.com";
- };
-
- clientId = mkOption {
- type = types.str;
- default = "";
- description = lib.mdDoc ''
- OpenID Connect client ID.
- '';
- };
-
- clientSecretFile = mkOption {
- type = types.nullOr types.path;
- default = null;
- description = lib.mdDoc ''
- Path to OpenID Connect client secret file.
- '';
- };
-
- domainMap = mkOption {
- type = types.attrsOf types.str;
- default = { };
- description = lib.mdDoc ''
- Domain map is used to map incoming users (by their email) to
- a namespace. The key can be a string, or regex.
- '';
- example = {
- ".*" = "default-namespace";
- };
- };
-
- };
-
- tls = {
- letsencrypt = {
- hostname = mkOption {
- type = types.nullOr types.str;
- default = "";
- description = lib.mdDoc ''
- Domain name to request a TLS certificate for.
- '';
- };
- challengeType = mkOption {
- type = types.enum [ "TLS-ALPN-01" "HTTP-01" ];
- default = "HTTP-01";
- description = lib.mdDoc ''
- Type of ACME challenge to use, currently supported types:
- `HTTP-01` or `TLS-ALPN-01`.
- '';
- };
- httpListen = mkOption {
- type = types.nullOr types.str;
- default = ":http";
- description = lib.mdDoc ''
- When HTTP-01 challenge is chosen, letsencrypt must set up a
- verification endpoint, and it will be listening on:
- `:http = port 80`.
- '';
- };
- };
-
- certFile = mkOption {
- type = types.nullOr types.path;
- default = null;
- description = lib.mdDoc ''
- Path to already created certificate.
- '';
- };
- keyFile = mkOption {
- type = types.nullOr types.path;
- default = null;
- description = lib.mdDoc ''
- Path to key for already created certificate.
- '';
- };
- };
-
- aclPolicyFile = mkOption {
- type = types.nullOr types.path;
- default = null;
- description = lib.mdDoc ''
- Path to a file containing ACL policies.
- '';
- };
-
settings = mkOption {
- type = settingsFormat.type;
- default = { };
description = lib.mdDoc ''
Overrides to {file}`config.yaml` as a Nix attribute set.
- This option is ideal for overriding settings not exposed as Nix options.
Check the [example config](https://github.com/juanfont/headscale/blob/main/config-example.yaml)
for possible options.
'';
+ type = types.submodule {
+ freeformType = settingsFormat.type;
+
+ options = {
+ server_url = mkOption {
+ type = types.str;
+ default = "http://127.0.0.1:8080";
+ description = lib.mdDoc ''
+ The url clients will connect to.
+ '';
+ example = "https://myheadscale.example.com:443";
+ };
+
+ private_key_path = mkOption {
+ type = types.path;
+ default = "${dataDir}/private.key";
+ description = lib.mdDoc ''
+ Path to private key file, generated automatically if it does not exist.
+ '';
+ };
+
+ noise.private_key_path = mkOption {
+ type = types.path;
+ default = "${dataDir}/noise_private.key";
+ description = lib.mdDoc ''
+ Path to noise private key file, generated automatically if it does not exist.
+ '';
+ };
+
+ derp = {
+ urls = mkOption {
+ type = types.listOf types.str;
+ default = ["https://controlplane.tailscale.com/derpmap/default"];
+ description = lib.mdDoc ''
+ List of urls containing DERP maps.
+ See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
+ '';
+ };
+
+ paths = mkOption {
+ type = types.listOf types.path;
+ default = [];
+ description = lib.mdDoc ''
+ List of file paths containing DERP maps.
+ See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
+ '';
+ };
+
+ auto_update_enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = lib.mdDoc ''
+ Whether to automatically update DERP maps on a set frequency.
+ '';
+ example = false;
+ };
+
+ update_frequency = mkOption {
+ type = types.str;
+ default = "24h";
+ description = lib.mdDoc ''
+ Frequency to update DERP maps.
+ '';
+ example = "5m";
+ };
+ };
+
+ ephemeral_node_inactivity_timeout = mkOption {
+ type = types.str;
+ default = "30m";
+ description = lib.mdDoc ''
+ Time before an inactive ephemeral node is deleted.
+ '';
+ example = "5m";
+ };
+
+ db_type = mkOption {
+ type = types.enum ["sqlite3" "postgres"];
+ example = "postgres";
+ default = "sqlite3";
+ description = lib.mdDoc "Database engine to use.";
+ };
+
+ db_host = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "127.0.0.1";
+ description = lib.mdDoc "Database host address.";
+ };
+
+ db_port = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ example = 3306;
+ description = lib.mdDoc "Database host port.";
+ };
+
+ db_name = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "headscale";
+ description = lib.mdDoc "Database name.";
+ };
+
+ db_user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "headscale";
+ description = lib.mdDoc "Database user.";
+ };
+
+ db_password_file = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = "/run/keys/headscale-dbpassword";
+ description = lib.mdDoc ''
+ A file containing the password corresponding to
+ {option}`database.user`.
+ '';
+ };
+
+ db_path = mkOption {
+ type = types.nullOr types.str;
+ default = "${dataDir}/db.sqlite";
+ description = lib.mdDoc "Path to the sqlite3 database file.";
+ };
+
+ log.level = mkOption {
+ type = types.str;
+ default = "info";
+ description = lib.mdDoc ''
+ headscale log level.
+ '';
+ example = "debug";
+ };
+
+ log.format = mkOption {
+ type = types.str;
+ default = "text";
+ description = lib.mdDoc ''
+ headscale log format.
+ '';
+ example = "json";
+ };
+
+ dns_config = {
+ nameservers = mkOption {
+ type = types.listOf types.str;
+ default = ["1.1.1.1"];
+ description = lib.mdDoc ''
+ List of nameservers to pass to Tailscale clients.
+ '';
+ };
+
+ override_local_dns = mkOption {
+ type = types.bool;
+ default = false;
+ description = lib.mdDoc ''
+ Whether to use [Override local DNS](https://tailscale.com/kb/1054/dns/).
+ '';
+ example = true;
+ };
+
+ domains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = lib.mdDoc ''
+ Search domains to inject to Tailscale clients.
+ '';
+ example = ["mydomain.internal"];
+ };
+
+ magic_dns = mkOption {
+ type = types.bool;
+ default = true;
+ description = lib.mdDoc ''
+ Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
+ Only works if there is at least a nameserver defined.
+ '';
+ example = false;
+ };
+
+ base_domain = mkOption {
+ type = types.str;
+ default = "";
+ description = lib.mdDoc ''
+ Defines the base domain to create the hostnames for MagicDNS.
+ {option}`baseDomain` must be a FQDNs, without the trailing dot.
+ The FQDN of the hosts will be
+ `hostname.namespace.base_domain` (e.g.
+ `myhost.mynamespace.example.com`).
+ '';
+ };
+ };
+
+ oidc = {
+ issuer = mkOption {
+ type = types.str;
+ default = "";
+ description = lib.mdDoc ''
+ URL to OpenID issuer.
+ '';
+ example = "https://openid.example.com";
+ };
+
+ client_id = mkOption {
+ type = types.str;
+ default = "";
+ description = lib.mdDoc ''
+ OpenID Connect client ID.
+ '';
+ };
+
+ client_secret_file = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = lib.mdDoc ''
+ Path to OpenID Connect client secret file.
+ '';
+ };
+
+ domain_map = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = lib.mdDoc ''
+ Domain map is used to map incomming users (by their email) to
+ a namespace. The key can be a string, or regex.
+ '';
+ example = {
+ ".*" = "default-namespace";
+ };
+ };
+ };
+
+ tls_letsencrypt_hostname = mkOption {
+ type = types.nullOr types.str;
+ default = "";
+ description = lib.mdDoc ''
+ Domain name to request a TLS certificate for.
+ '';
+ };
+
+ tls_letsencrypt_challenge_type = mkOption {
+ type = types.enum ["TLS-ALPN-01" "HTTP-01"];
+ default = "HTTP-01";
+ description = lib.mdDoc ''
+ Type of ACME challenge to use, currently supported types:
+ `HTTP-01` or `TLS-ALPN-01`.
+ '';
+ };
+
+ tls_letsencrypt_listen = mkOption {
+ type = types.nullOr types.str;
+ default = ":http";
+ description = lib.mdDoc ''
+ When HTTP-01 challenge is chosen, letsencrypt must set up a
+ verification endpoint, and it will be listening on:
+ `:http = port 80`.
+ '';
+ };
+
+ tls_cert_path = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = lib.mdDoc ''
+ Path to already created certificate.
+ '';
+ };
+
+ tls_key_path = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = lib.mdDoc ''
+ Path to key for already created certificate.
+ '';
+ };
+
+ acl_policy_path = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = lib.mdDoc ''
+ Path to a file containg ACL policies.
+ '';
+ };
+ };
+ };
};
-
-
};
-
};
- config = mkIf cfg.enable {
+ 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"])
+ (mkRenamedOptionModule ["services" "headscale" "derp" "autoUpdate"] ["services" "headscale" "settings" "derp" "auto_update_enable"])
+ (mkRenamedOptionModule ["services" "headscale" "derp" "updateFrequency"] ["services" "headscale" "settings" "derp" "update_frequency"])
+ (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_file"])
+ (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ["services" "headscale" "settings" "oidc" "domain_map"])
+ (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" "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"])
+ ];
+
+ config = mkIf cfg.enable {
services.headscale.settings = {
- server_url = mkDefault cfg.serverUrl;
listen_addr = mkDefault "${cfg.address}:${toString cfg.port}";
- private_key_path = mkDefault cfg.privateKeyFile;
-
- derp = {
- urls = mkDefault cfg.derp.urls;
- paths = mkDefault cfg.derp.paths;
- auto_update_enable = mkDefault cfg.derp.autoUpdate;
- update_frequency = mkDefault cfg.derp.updateFrequency;
- };
-
# Turn off update checks since the origin of our package
# is nixpkgs and not Github.
disable_check_updates = true;
- ephemeral_node_inactivity_timeout = mkDefault cfg.ephemeralNodeInactivityTimeout;
-
- db_type = mkDefault cfg.database.type;
- db_path = mkDefault cfg.database.path;
-
- log_level = mkDefault cfg.logLevel;
-
- dns_config = {
- nameservers = mkDefault cfg.dns.nameservers;
- domains = mkDefault cfg.dns.domains;
- magic_dns = mkDefault cfg.dns.magicDns;
- base_domain = mkDefault cfg.dns.baseDomain;
- };
-
unix_socket = "${runDir}/headscale.sock";
- # OpenID Connect
- oidc = {
- issuer = mkDefault cfg.openIdConnect.issuer;
- client_id = mkDefault cfg.openIdConnect.clientId;
- domain_map = mkDefault cfg.openIdConnect.domainMap;
- };
-
tls_letsencrypt_cache_dir = "${dataDir}/.cache";
-
- } // optionalAttrs (cfg.database.host != null) {
- db_host = mkDefault cfg.database.host;
- } // optionalAttrs (cfg.database.port != null) {
- db_port = mkDefault cfg.database.port;
- } // optionalAttrs (cfg.database.name != null) {
- db_name = mkDefault cfg.database.name;
- } // optionalAttrs (cfg.database.user != null) {
- db_user = mkDefault cfg.database.user;
- } // optionalAttrs (cfg.tls.letsencrypt.hostname != null) {
- tls_letsencrypt_hostname = mkDefault cfg.tls.letsencrypt.hostname;
- } // optionalAttrs (cfg.tls.letsencrypt.challengeType != null) {
- tls_letsencrypt_challenge_type = mkDefault cfg.tls.letsencrypt.challengeType;
- } // optionalAttrs (cfg.tls.letsencrypt.httpListen != null) {
- tls_letsencrypt_listen = mkDefault cfg.tls.letsencrypt.httpListen;
- } // optionalAttrs (cfg.tls.certFile != null) {
- tls_cert_path = mkDefault cfg.tls.certFile;
- } // optionalAttrs (cfg.tls.keyFile != null) {
- tls_key_path = mkDefault cfg.tls.keyFile;
- } // optionalAttrs (cfg.aclPolicyFile != null) {
- acl_policy_path = mkDefault cfg.aclPolicyFile;
};
# Setup the headscale configuration in a known path in /etc to
@@ -416,7 +419,7 @@ in
# for communication.
environment.etc."headscale/config.yaml".source = configFile;
- users.groups.headscale = mkIf (cfg.group == "headscale") { };
+ users.groups.headscale = mkIf (cfg.group == "headscale") {};
users.users.headscale = mkIf (cfg.user == "headscale") {
description = "headscale user";
@@ -427,70 +430,68 @@ in
systemd.services.headscale = {
description = "headscale coordination server for Tailscale";
- after = [ "network-online.target" ];
- wantedBy = [ "multi-user.target" ];
- restartTriggers = [ configFile ];
+ after = ["network-online.target"];
+ wantedBy = ["multi-user.target"];
+ restartTriggers = [configFile];
environment.GIN_MODE = "release";
script = ''
- ${optionalString (cfg.database.passwordFile != null) ''
- export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.database.passwordFile})"
+ ${optionalString (cfg.settings.db_password_file != null) ''
+ export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})"
''}
- ${optionalString (cfg.openIdConnect.clientSecretFile != null) ''
- export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.openIdConnect.clientSecretFile})"
+ ${optionalString (cfg.settings.oidc.client_secret_file != null) ''
+ export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.settings.oidc.client_secret_file})"
''}
exec ${cfg.package}/bin/headscale serve
'';
- serviceConfig =
- let
- capabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
- in
- {
- Restart = "always";
- Type = "simple";
- User = cfg.user;
- Group = cfg.group;
-
- # Hardening options
- RuntimeDirectory = "headscale";
- # Allow headscale group access so users can be added and use the CLI.
- RuntimeDirectoryMode = "0750";
-
- StateDirectory = "headscale";
- StateDirectoryMode = "0750";
-
- ProtectSystem = "strict";
- ProtectHome = true;
- PrivateTmp = true;
- PrivateDevices = true;
- ProtectKernelTunables = true;
- ProtectControlGroups = true;
- RestrictSUIDSGID = true;
- PrivateMounts = true;
- ProtectKernelModules = true;
- ProtectKernelLogs = true;
- ProtectHostname = true;
- ProtectClock = true;
- ProtectProc = "invisible";
- ProcSubset = "pid";
- RestrictNamespaces = true;
- RemoveIPC = true;
- UMask = "0077";
-
- CapabilityBoundingSet = capabilityBoundingSet;
- AmbientCapabilities = capabilityBoundingSet;
- NoNewPrivileges = true;
- LockPersonality = true;
- RestrictRealtime = true;
- SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ];
- SystemCallArchitectures = "native";
- RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
- };
+ serviceConfig = let
+ capabilityBoundingSet = ["CAP_CHOWN"] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
+ in {
+ Restart = "always";
+ Type = "simple";
+ User = cfg.user;
+ Group = cfg.group;
+
+ # Hardening options
+ RuntimeDirectory = "headscale";
+ # Allow headscale group access so users can be added and use the CLI.
+ RuntimeDirectoryMode = "0750";
+
+ StateDirectory = "headscale";
+ StateDirectoryMode = "0750";
+
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ PrivateDevices = true;
+ ProtectKernelTunables = true;
+ ProtectControlGroups = true;
+ RestrictSUIDSGID = true;
+ PrivateMounts = true;
+ ProtectKernelModules = true;
+ ProtectKernelLogs = true;
+ ProtectHostname = true;
+ ProtectClock = true;
+ ProtectProc = "invisible";
+ ProcSubset = "pid";
+ RestrictNamespaces = true;
+ RemoveIPC = true;
+ UMask = "0077";
+
+ CapabilityBoundingSet = capabilityBoundingSet;
+ AmbientCapabilities = capabilityBoundingSet;
+ NoNewPrivileges = true;
+ LockPersonality = true;
+ RestrictRealtime = true;
+ SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
+ SystemCallArchitectures = "native";
+ RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+ };
};
};
- meta.maintainers = with maintainers; [ kradalby ];
+ meta.maintainers = with maintainers; [kradalby misterio77];
}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 30bcfcf6111a3..6f056de2ed5cc 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -257,6 +257,7 @@ in {
haste-server = handleTest ./haste-server.nix {};
haproxy = handleTest ./haproxy.nix {};
hardened = handleTest ./hardened.nix {};
+ headscale = handleTest ./headscale.nix {};
healthchecks = handleTest ./web-apps/healthchecks.nix {};
hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
hbase_2_4 = handleTest ./hbase.nix { package=pkgs.hbase_2_4; };
diff --git a/nixos/tests/headscale.nix b/nixos/tests/headscale.nix
new file mode 100644
index 0000000000000..48658b5dade42
--- /dev/null
+++ b/nixos/tests/headscale.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+ name = "headscale";
+ meta.maintainers = with lib.maintainers; [ misterio77 ];
+
+ nodes.machine = { ... }: {
+ services.headscale.enable = true;
+ environment.systemPackages = [ pkgs.headscale ];
+ };
+
+ testScript = ''
+ machine.wait_for_unit("headscale")
+ machine.wait_for_open_port(8080)
+ # Test basic funcionality
+ machine.succeed("headscale namespaces create test")
+ machine.succeed("headscale preauthkeys -n test create")
+ '';
+})
diff --git a/pkgs/servers/headscale/default.nix b/pkgs/servers/headscale/default.nix
index 34455918c2fff..a577b6251c52f 100644
--- a/pkgs/servers/headscale/default.nix
+++ b/pkgs/servers/headscale/default.nix
@@ -1,21 +1,28 @@
-{ lib, buildGoModule, fetchFromGitHub, installShellFiles }:
-
+{
+ lib,
+ buildGoModule,
+ fetchFromGitHub,
+ installShellFiles,
+}:
buildGoModule rec {
pname = "headscale";
- version = "0.16.4";
+ version = "0.17.1";
src = fetchFromGitHub {
owner = "juanfont";
repo = "headscale";
rev = "v${version}";
- sha256 = "sha256-j5fbWxRMkYlsgL1QDEDlitKB3FOmDTy17FcuztALISw=";
+ sha256 = "sha256-/NJUtmH67VZERCvExcX4W4T9Rcixc5m28ujNcrQduWg=";
};
- vendorSha256 = "sha256-RzmnAh81BN4tbzAGzJbb6CMuws8kuPJDw7aPkRRnSS8=";
+ vendorSha256 = "sha256-Y1IK9Tx2sv0v27ZYtSxDP9keHQ7skctDOa+37pNGEC8=";
+
+ ldflags = ["-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}"];
- ldflags = [ "-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}" ];
+ nativeBuildInputs = [installShellFiles];
+ checkFlags = ["-short"];
- nativeBuildInputs = [ installShellFiles ];
+ tags = ["ts2019"];
postInstall = ''
installShellCompletion --cmd headscale \
@@ -44,6 +51,6 @@ buildGoModule rec {
Headscale implements this coordination server.
'';
license = licenses.bsd3;
- maintainers = with maintainers; [ nkje jk kradalby ];
+ maintainers = with maintainers; [nkje jk kradalby misterio77 ghuntley];
};
}