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 5188c51c7180..b410a660c551 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 9cbeea2fd621..911575d8ab53 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 29b632ff5d22..cc46819eed5a 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. + ''; + }; + }; + }; }; - - }; - }; + + 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; + 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"; + # Hardening options + RuntimeDirectory = "headscale"; + # Allow headscale group access so users can be added and use the CLI. + RuntimeDirectoryMode = "0750"; - StateDirectory = "headscale"; - StateDirectoryMode = "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"; + 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"; - }; + 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 30bcfcf6111a..6f056de2ed5c 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 000000000000..48658b5dade4 --- /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 34455918c2ff..a577b6251c52 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 ]; + nativeBuildInputs = [installShellFiles]; + checkFlags = ["-short"]; + + 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]; }; }