diff options
Diffstat (limited to 'nixos')
24 files changed, 1813 insertions, 42 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 7be39a69f5b0..5df8473babae 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -79,6 +79,8 @@ * [NS-USBLoader](https://github.com/developersu/ns-usbloader/), an all-in-one tool for managing Nintendo Switch homebrew. Available as [programs.ns-usbloader](#opt-programs.ns-usbloader.enable). +- [athens](https://github.com/gomods/athens), a Go module datastore and proxy. Available as [services.athens](#opt-services.athens.enable). + - [Mobilizon](https://joinmobilizon.org/), a Fediverse platform for publishing events. - [Anuko Time Tracker](https://github.com/anuko/timetracker), a simple, easy to use, open source time tracking system. Available as [services.anuko-time-tracker](#opt-services.anuko-time-tracker.enable). @@ -621,6 +623,9 @@ The module update takes care of the new config syntax and the data itself (user - `python3.pkgs.flitBuildHook` has been removed. Use `flit-core` and `format = "pyproject"` instead. +- Now `magma` defaults to `magma-hip` instead of `magma-cuda`. It also + respects the `config.cudaSupport` and `config.rocmSupport` options. + - The `extend` function of `llvmPackages` has been removed due it coming from the `tools` attrset thus only extending the `tool` attrset. A possible replacement is to construct the set from `libraries` and `tools`, or patch nixpkgs. - The `qemu-vm.nix` module now supports disabling overriding `fileSystems` with @@ -660,7 +665,8 @@ The module update takes care of the new config syntax and the data itself (user designed to be easy and safe to use. This aims to be a replacement for `lib.sources`-based filtering. - To learn more about it, see [the tutorial](https://nix.dev/tutorials/file-sets). + To learn more about it, see [the blog post](https://www.tweag.io/blog/2023-11-28-file-sets/) + or [the tutorial](https://nix.dev/tutorials/file-sets). - [`lib.gvariant`](https://nixos.org/manual/nixpkgs/unstable#sec-functions-library-gvariant): A partial and basic implementation of GVariant formatted strings. diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index e2f99c20cfc8..b6b343145d78 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -14,16 +14,19 @@ In addition to numerous new and upgraded packages, this release has the followin <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> -- Create the first release note entry in this section! +- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable). ## Backward Incompatibilities {#sec-release-24.05-incompatibilities} <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> -- Create the first release note entry in this section! +- `mkosi` was updated to v19. Parts of the user interface have changed. Consult the + [release notes](https://github.com/systemd/mkosi/releases/tag/v19) for a list of changes. ## Other Notable Changes {#sec-release-24.05-notable-changes} <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> -- Create the first release note entry in this section! +- Programs written in [Nim](https://nim-lang.org/) are built with libraries selected by lockfiles. + The `nimPackages` and `nim2Packages` sets have been removed. + See https://nixos.org/manual/nixpkgs/unstable#nim for more information. diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix index 2a35afad2ac7..a81ce828b13d 100644 --- a/nixos/modules/i18n/input-method/ibus.nix +++ b/nixos/modules/i18n/input-method/ibus.nix @@ -47,7 +47,7 @@ in panel = mkOption { type = with types; nullOr path; default = null; - example = literalExpression ''"''${pkgs.plasma5Packages.plasma-desktop}/lib/libexec/kimpanel-ibus-panel"''; + example = literalExpression ''"''${pkgs.plasma5Packages.plasma-desktop}/libexec/kimpanel-ibus-panel"''; description = lib.mdDoc "Replace the IBus panel with another panel."; }; }; diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix index 15e10128ac9a..9ccc76a82c95 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -130,7 +130,7 @@ in ''; }; - config = lib.mkIf (config.nix.enable && !config.system.disableInstallerTools) { + config = lib.mkMerge [ (lib.mkIf (config.nix.enable && !config.system.disableInstallerTools) { system.nixos-generate-config.configuration = mkDefault '' # Edit this configuration file to define what should be installed on @@ -257,10 +257,13 @@ in documentation.man.man-db.skipPackages = [ nixos-version ]; + }) + + # These may be used in auxiliary scripts (ie not part of toplevel), so they are defined unconditionally. + ({ system.build = { inherit nixos-install nixos-generate-config nixos-option nixos-rebuild nixos-enter; }; - - }; + })]; } diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2ef049beeca4..e40b7ed8015f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -480,6 +480,7 @@ ./services/desktops/telepathy.nix ./services/desktops/tumbler.nix ./services/desktops/zeitgeist.nix + ./services/development/athens.nix ./services/development/blackfire.nix ./services/development/bloop.nix ./services/development/distccd.nix @@ -621,6 +622,7 @@ ./services/matrix/appservice-irc.nix ./services/matrix/conduit.nix ./services/matrix/dendrite.nix + ./services/matrix/maubot.nix ./services/matrix/mautrix-facebook.nix ./services/matrix/mautrix-telegram.nix ./services/matrix/mautrix-whatsapp.nix @@ -1440,6 +1442,7 @@ ./system/boot/stratisroot.nix ./system/boot/modprobe.nix ./system/boot/networkd.nix + ./system/boot/unl0kr.nix ./system/boot/plymouth.nix ./system/boot/resolved.nix ./system/boot/shutdown.nix @@ -1537,9 +1540,10 @@ ./virtualisation/waydroid.nix ./virtualisation/xe-guest-utilities.nix ./virtualisation/xen-dom0.nix - { documentation.nixos.extraModules = [ - ./virtualisation/qemu-vm.nix - ./image/repart.nix + { + documentation.nixos.extraModules = [ + ./virtualisation/qemu-vm.nix + ./image/repart.nix ]; } ] diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix index d48afed18f7e..6c2602881d6b 100644 --- a/nixos/modules/profiles/macos-builder.nix +++ b/nixos/modules/profiles/macos-builder.nix @@ -103,6 +103,19 @@ in # server that QEMU provides (normally 10.0.2.3) networking.nameservers = [ "8.8.8.8" ]; + # The linux builder is a lightweight VM for remote building; not evaluation. + nix.channel.enable = false; + # remote builder uses `nix-daemon` (ssh-ng:) or `nix-store --serve` (ssh:) + # --force: do not complain when missing + # TODO: install a store-only nix + # https://github.com/NixOS/rfcs/blob/master/rfcs/0134-nix-store-layer.md#detailed-design + environment.extraSetup = '' + rm --force $out/bin/{nix-instantiate,nix-build,nix-shell,nix-prefetch*,nix} + ''; + # Deployment is by image. + # TODO system.switch.enable = false;? + system.disableInstallerTools = true; + nix.settings = { auto-optimise-store = true; diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix index 6cf9257d035a..8ac723f42f61 100644 --- a/nixos/modules/programs/environment.nix +++ b/nixos/modules/programs/environment.nix @@ -45,7 +45,7 @@ in GTK_PATH = [ "/lib/gtk-2.0" "/lib/gtk-3.0" "/lib/gtk-4.0" ]; XDG_CONFIG_DIRS = [ "/etc/xdg" ]; XDG_DATA_DIRS = [ "/share" ]; - LIBEXEC_PATH = [ "/lib/libexec" ]; + LIBEXEC_PATH = [ "/libexec" ]; }; environment.pathsToLink = [ "/lib/gtk-2.0" "/lib/gtk-3.0" "/lib/gtk-4.0" ]; diff --git a/nixos/modules/programs/mininet.nix b/nixos/modules/programs/mininet.nix index 02272729d233..01ffd811e70e 100644 --- a/nixos/modules/programs/mininet.nix +++ b/nixos/modules/programs/mininet.nix @@ -5,26 +5,39 @@ with lib; let - cfg = config.programs.mininet; + cfg = config.programs.mininet; - generatedPath = with pkgs; makeSearchPath "bin" [ - iperf ethtool iproute2 socat + telnet = pkgs.runCommand "inetutils-telnet" + { } + '' + mkdir -p $out/bin + ln -s ${pkgs.inetutils}/bin/telnet $out/bin + ''; + + generatedPath = with pkgs; makeSearchPath "bin" [ + iperf + ethtool + iproute2 + socat + # mn errors out without a telnet binary + # pkgs.inetutils brings an undesired ifconfig into PATH see #43105 + nettools + telnet ]; - pyEnv = pkgs.python.withPackages(ps: [ ps.mininet-python ]); + pyEnv = pkgs.python3.withPackages (ps: [ ps.mininet-python ]); mnexecWrapped = pkgs.runCommand "mnexec-wrapper" - { nativeBuildInputs = [ pkgs.makeWrapper pkgs.pythonPackages.wrapPython ]; } + { nativeBuildInputs = [ pkgs.makeWrapper pkgs.python3Packages.wrapPython ]; } '' makeWrapper ${pkgs.mininet}/bin/mnexec \ $out/bin/mnexec \ --prefix PATH : "${generatedPath}" - ln -s ${pyEnv}/bin/mn $out/bin/mn - - # mn errors out without a telnet binary - # pkgs.inetutils brings an undesired ifconfig into PATH see #43105 - ln -s ${pkgs.inetutils}/bin/telnet $out/bin/telnet + makeWrapper ${pyEnv}/bin/mn \ + $out/bin/mn \ + --prefix PYTHONPATH : "${pyEnv}/${pyEnv.sitePackages}" \ + --prefix PATH : "${generatedPath}" ''; in { diff --git a/nixos/modules/services/computing/foldingathome/client.nix b/nixos/modules/services/computing/foldingathome/client.nix index 1229e5ac987e..a4e79fd1c141 100644 --- a/nixos/modules/services/computing/foldingathome/client.nix +++ b/nixos/modules/services/computing/foldingathome/client.nix @@ -63,7 +63,7 @@ in default = []; description = lib.mdDoc '' Extra startup options for the FAHClient. Run - `FAHClient --help` to find all the available options. + `fah-client --help` to find all the available options. ''; }; }; @@ -74,7 +74,7 @@ in after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; script = '' - exec ${cfg.package}/bin/FAHClient ${lib.escapeShellArgs args} + exec ${lib.getExe cfg.package} ${lib.escapeShellArgs args} ''; serviceConfig = { DynamicUser = true; diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix index 315a0282cd73..4a5c19240ec6 100644 --- a/nixos/modules/services/databases/redis.nix +++ b/nixos/modules/services/databases/redis.nix @@ -393,9 +393,7 @@ in { ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; - RestrictAddressFamilies = - optionals (conf.port != 0) ["AF_INET" "AF_INET6"] ++ - optional (conf.unixSocket != null) "AF_UNIX"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; RestrictNamespaces = true; LockPersonality = true; MemoryDenyWriteExecute = true; diff --git a/nixos/modules/services/development/athens.md b/nixos/modules/services/development/athens.md new file mode 100644 index 000000000000..77663db509d5 --- /dev/null +++ b/nixos/modules/services/development/athens.md @@ -0,0 +1,52 @@ +# Athens {#module-athens} + +*Source:* {file}`modules/services/development/athens.nix` + +*Upstream documentation:* <https://docs.gomods.io/> + +[Athens](https://github.com/gomods/athens) +is a Go module datastore and proxy + +The main goal of Athens is providing a Go proxy (`$GOPROXY`) in regions without access to `https://proxy.golang.org` or to +improve the speed of Go module downloads for CI/CD systems. + +## Configuring {#module-services-development-athens-configuring} + +A complete list of options for the Athens module may be found +[here](#opt-services.athens.enable). + +## Basic usage for a caching proxy configuration {#opt-services-development-athens-caching-proxy} + +A very basic configuration for Athens that acts as a caching and forwarding HTTP proxy is: +``` +{ + services.athens = { + enable = true; + }; +} +``` + +If you want to prevent Athens from writing to disk, you can instead configure it to cache modules only in memory: + +``` +{ + services.athens = { + enable = true; + storageType = "memory"; + }; +} +``` + +To use the local proxy in Go builds, you can set the proxy as environment variable: + +``` +{ + environment.variables = { + GOPROXY = "http://localhost:3000" + }; +} +``` + +It is currently not possible to use the local proxy for builds done by the Nix daemon. This might be enabled +by experimental features, specifically [`configurable-impure-env`](https://nixos.org/manual/nix/unstable/contributing/experimental-features#xp-feature-configurable-impure-env), +in upcoming Nix versions. diff --git a/nixos/modules/services/development/athens.nix b/nixos/modules/services/development/athens.nix new file mode 100644 index 000000000000..34f8964a3bd5 --- /dev/null +++ b/nixos/modules/services/development/athens.nix @@ -0,0 +1,936 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.athens; + + athensConfig = flip recursiveUpdate cfg.extraConfig ( + { + GoBinary = "${cfg.goBinary}/bin/go"; + GoEnv = cfg.goEnv; + GoBinaryEnvVars = lib.mapAttrsToList (k: v: "${k}=${v}") cfg.goBinaryEnvVars; + GoGetWorkers = cfg.goGetWorkers; + GoGetDir = cfg.goGetDir; + ProtocolWorkers = cfg.protocolWorkers; + LogLevel = cfg.logLevel; + CloudRuntime = cfg.cloudRuntime; + EnablePprof = cfg.enablePprof; + PprofPort = ":${toString cfg.pprofPort}"; + FilterFile = cfg.filterFile; + RobotsFile = cfg.robotsFile; + Timeout = cfg.timeout; + StorageType = cfg.storageType; + TLSCertFile = cfg.tlsCertFile; + TLSKeyFile = cfg.tlsKeyFile; + Port = ":${toString cfg.port}"; + UnixSocket = cfg.unixSocket; + GlobalEndpoint = cfg.globalEndpoint; + BasicAuthUser = cfg.basicAuthUser; + BasicAuthPass = cfg.basicAuthPass; + ForceSSL = cfg.forceSSL; + ValidatorHook = cfg.validatorHook; + PathPrefix = cfg.pathPrefix; + NETRCPath = cfg.netrcPath; + GithubToken = cfg.githubToken; + HGRCPath = cfg.hgrcPath; + TraceExporter = cfg.traceExporter; + StatsExporter = cfg.statsExporter; + SumDBs = cfg.sumDBs; + NoSumPatterns = cfg.noSumPatterns; + DownloadMode = cfg.downloadMode; + NetworkMode = cfg.networkMode; + DownloadURL = cfg.downloadURL; + SingleFlightType = cfg.singleFlightType; + IndexType = cfg.indexType; + ShutdownTimeout = cfg.shutdownTimeout; + SingleFlight = { + Etcd = { + Endpoints = builtins.concatStringsSep "," cfg.singleFlight.etcd.endpoints; + }; + Redis = { + Endpoint = cfg.singleFlight.redis.endpoint; + Password = cfg.singleFlight.redis.password; + LockConfig = { + TTL = cfg.singleFlight.redis.lockConfig.ttl; + Timeout = cfg.singleFlight.redis.lockConfig.timeout; + MaxRetries = cfg.singleFlight.redis.lockConfig.maxRetries; + }; + }; + RedisSentinel = { + Endpoints = cfg.singleFlight.redisSentinel.endpoints; + MasterName = cfg.singleFlight.redisSentinel.masterName; + SentinelPassword = cfg.singleFlight.redisSentinel.sentinelPassword; + LockConfig = { + TTL = cfg.singleFlight.redisSentinel.lockConfig.ttl; + Timeout = cfg.singleFlight.redisSentinel.lockConfig.timeout; + MaxRetries = cfg.singleFlight.redisSentinel.lockConfig.maxRetries; + }; + }; + }; + Storage = { + CDN = { + Endpoint = cfg.storage.cdn.endpoint; + }; + Disk = { + RootPath = cfg.storage.disk.rootPath; + }; + GCP = { + ProjectID = cfg.storage.gcp.projectID; + Bucket = cfg.storage.gcp.bucket; + JSONKey = cfg.storage.gcp.jsonKey; + }; + Minio = { + Endpoint = cfg.storage.minio.endpoint; + Key = cfg.storage.minio.key; + Secret = cfg.storage.minio.secret; + EnableSSL = cfg.storage.minio.enableSSL; + Bucket = cfg.storage.minio.bucket; + region = cfg.storage.minio.region; + }; + Mongo = { + URL = cfg.storage.mongo.url; + DefaultDBName = cfg.storage.mongo.defaultDBName; + CertPath = cfg.storage.mongo.certPath; + Insecure = cfg.storage.mongo.insecure; + }; + S3 = { + Region = cfg.storage.s3.region; + Key = cfg.storage.s3.key; + Secret = cfg.storage.s3.secret; + Token = cfg.storage.s3.token; + Bucket = cfg.storage.s3.bucket; + ForcePathStyle = cfg.storage.s3.forcePathStyle; + UseDefaultConfiguration = cfg.storage.s3.useDefaultConfiguration; + CredentialsEndpoint = cfg.storage.s3.credentialsEndpoint; + AwsContainerCredentialsRelativeURI = cfg.storage.s3.awsContainerCredentialsRelativeURI; + Endpoint = cfg.storage.s3.endpoint; + }; + AzureBlob = { + AccountName = cfg.storage.azureblob.accountName; + AccountKey = cfg.storage.azureblob.accountKey; + ContainerName = cfg.storage.azureblob.containerName; + }; + External = { + URL = cfg.storage.external.url; + }; + }; + Index = { + MySQL = { + Protocol = cfg.index.mysql.protocol; + Host = cfg.index.mysql.host; + Port = cfg.index.mysql.port; + User = cfg.index.mysql.user; + Password = cfg.index.mysql.password; + Database = cfg.index.mysql.database; + Params = { + parseTime = cfg.index.mysql.params.parseTime; + timeout = cfg.index.mysql.params.timeout; + }; + }; + Postgres = { + Host = cfg.index.postgres.host; + Port = cfg.index.postgres.port; + User = cfg.index.postgres.user; + Password = cfg.index.postgres.password; + Database = cfg.index.postgres.database; + Params = { + connect_timeout = cfg.index.postgres.params.connect_timeout; + sslmode = cfg.index.postgres.params.sslmode; + }; + }; + }; + } + ); + + configFile = pkgs.runCommandLocal "config.toml" { } '' + ${pkgs.buildPackages.jq}/bin/jq 'del(..|nulls)' \ + < ${pkgs.writeText "config.json" (builtins.toJSON athensConfig)} | \ + ${pkgs.buildPackages.remarshal}/bin/remarshal -if json -of toml \ + > $out + ''; +in +{ + meta = { + maintainers = pkgs.athens.meta.maintainers; + doc = ./athens.md; + }; + + options.services.athens = { + enable = mkEnableOption (lib.mdDoc "Go module datastore and proxy"); + + package = mkOption { + default = pkgs.athens; + defaultText = literalExpression "pkgs.athens"; + example = "pkgs.athens"; + description = lib.mdDoc "Which athens derivation to use"; + type = types.package; + }; + + goBinary = mkOption { + type = types.package; + default = pkgs.go; + defaultText = literalExpression "pkgs.go"; + example = "pkgs.go_1_21"; + description = lib.mdDoc '' + The Go package used by Athens at runtime. + + Athens primarily runs two Go commands: + 1. `go mod download -json <module>@<version>` + 2. `go list -m -json <module>@latest` + ''; + }; + + goEnv = mkOption { + type = types.enum [ "development" "production" ]; + description = lib.mdDoc "Specifies the type of environment to run. One of 'development' or 'production'."; + default = "development"; + example = "production"; + }; + + goBinaryEnvVars = mkOption { + type = types.attrs; + description = lib.mdDoc "Environment variables to pass to the Go binary."; + example = '' + { "GOPROXY" = "direct", "GODEBUG" = "true" } + ''; + default = { }; + }; + + goGetWorkers = mkOption { + type = types.int; + description = lib.mdDoc "Number of workers concurrently downloading modules."; + default = 10; + example = 32; + }; + + goGetDir = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + Temporary directory that Athens will use to + fetch modules from VCS prior to persisting + them to a storage backend. + + If the value is empty, Athens will use the + default OS temp directory. + ''; + default = null; + example = "/tmp/athens"; + }; + + protocolWorkers = mkOption { + type = types.int; + description = lib.mdDoc "Number of workers concurrently serving protocol paths."; + default = 30; + }; + + logLevel = mkOption { + type = types.nullOr (types.enum [ "panic" "fatal" "error" "warning" "info" "debug" "trace" ]); + description = lib.mdDoc '' + Log level for Athens. + Supports all logrus log levels (https://github.com/Sirupsen/logrus#level-logging)". + ''; + default = "warning"; + example = "debug"; + }; + + cloudRuntime = mkOption { + type = types.enum [ "GCP" "none" ]; + description = lib.mdDoc '' + Specifies the Cloud Provider on which the Proxy/registry is running. + ''; + default = "none"; + example = "GCP"; + }; + + enablePprof = mkOption { + type = types.bool; + description = lib.mdDoc "Enable pprof endpoints."; + default = false; + }; + + pprofPort = mkOption { + type = types.port; + description = lib.mdDoc "Port number for pprof endpoints."; + default = 3301; + example = 443; + }; + + filterFile = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc ''Filename for the include exclude filter.''; + default = null; + example = literalExpression '' + pkgs.writeText "filterFile" ''' + - github.com/azure + + github.com/azure/azure-sdk-for-go + D golang.org/x/tools + ''' + ''; + }; + + robotsFile = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc ''Provides /robots.txt for net crawlers.''; + default = null; + example = literalExpression ''pkgs.writeText "robots.txt" "# my custom robots.txt ..."''; + }; + + timeout = mkOption { + type = types.int; + description = lib.mdDoc "Timeout for external network calls in seconds."; + default = 300; + example = 3; + }; + + storageType = mkOption { + type = types.enum [ "memory" "disk" "mongo" "gcp" "minio" "s3" "azureblob" "external" ]; + description = lib.mdDoc "Specifies the type of storage backend to use."; + default = "disk"; + }; + + tlsCertFile = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc "Path to the TLS certificate file."; + default = null; + example = "/etc/ssl/certs/athens.crt"; + }; + + tlsKeyFile = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc "Path to the TLS key file."; + default = null; + example = "/etc/ssl/certs/athens.key"; + }; + + port = mkOption { + type = types.port; + default = 3000; + description = lib.mdDoc '' + Port number Athens listens on. + ''; + example = 443; + }; + + unixSocket = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + Path to the unix socket file. + If set, Athens will listen on the unix socket instead of TCP socket. + ''; + default = null; + example = "/run/athens.sock"; + }; + + globalEndpoint = mkOption { + type = types.str; + description = lib.mdDoc '' + Endpoint for a package registry in case of a proxy cache miss. + ''; + default = ""; + example = "http://upstream-athens.example.com:3000"; + }; + + basicAuthUser = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + Username for basic auth. + ''; + default = null; + example = "user"; + }; + + basicAuthPass = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + Password for basic auth. Warning: this is stored in plain text in the config file. + ''; + default = null; + example = "swordfish"; + }; + + forceSSL = mkOption { + type = types.bool; + description = lib.mdDoc '' + Force SSL redirects for incoming requests. + ''; + default = false; + }; + + validatorHook = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + Endpoint to validate modules against. + + Not used if empty. + ''; + default = null; + example = "https://validation.example.com"; + }; + + pathPrefix = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + Sets basepath for all routes. + ''; + default = null; + example = "/athens"; + }; + + netrcPath = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + Path to the .netrc file. + ''; + default = null; + example = "/home/user/.netrc"; + }; + + githubToken = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + Creates .netrc file with the given token to be used for GitHub. + Warning: this is stored in plain text in the config file. + ''; + default = null; + example = "ghp_1234567890"; + }; + + hgrcPath = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + Path to the .hgrc file. + ''; + default = null; + example = "/home/user/.hgrc"; + }; + + traceExporter = mkOption { + type = types.nullOr (types.enum [ "jaeger" "datadog" ]); + description = lib.mdDoc '' + Trace exporter to use. + ''; + default = null; + }; + + traceExporterURL = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + URL endpoint that traces will be sent to. + ''; + default = null; + example = "http://localhost:14268"; + }; + + statsExporter = mkOption { + type = types.nullOr (types.enum [ "prometheus" ]); + description = lib.mdDoc "Stats exporter to use."; + default = null; + }; + + sumDBs = mkOption { + type = types.listOf types.str; + description = lib.mdDoc '' + List of fully qualified URLs that Athens will proxy + that the go command can use a checksum verifier. + ''; + default = [ "https://sum.golang.org" ]; + }; + + noSumPatterns = mkOption { + type = types.listOf types.str; + description = lib.mdDoc '' + List of patterns that Athens sum db proxy will return a 403 for. + ''; + default = [ ]; + example = [ "github.com/mycompany/*" ]; + }; + + downloadMode = mkOption { + type = types.oneOf [ (types.enum [ "sync" "async" "redirect" "async_redirect" "none" ]) (types.strMatching "^file:.*$|^custom:.*$") ]; + description = lib.mdDoc '' + Defines how Athens behaves when a module@version + is not found in storage. There are 7 options: + 1. "sync": download the module synchronously and + return the results to the client. + 2. "async": return 404, but asynchronously store the module + in the storage backend. + 3. "redirect": return a 301 redirect status to the client + with the base URL as the DownloadRedirectURL from below. + 4. "async_redirect": same as option number 3 but it will + asynchronously store the module to the backend. + 5. "none": return 404 if a module is not found and do nothing. + 6. "file:<path>": will point to an HCL file that specifies + any of the 5 options above based on different import paths. + 7. "custom:<base64-encoded-hcl>" is the same as option 6 + but the file is fully encoded in the option. This is + useful for using an environment variable in serverless + deployments. + ''; + default = "async_redirect"; + }; + + networkMode = mkOption { + type = types.enum [ "strict" "offline" "fallback" ]; + description = lib.mdDoc '' + Configures how Athens will return the results + of the /list endpoint as it can be assembled from both its own + storage and the upstream VCS. + + Note, that for better error messaging, this would also affect how other + endpoints behave. + + Modes: + 1. strict: merge VCS versions with storage versions, but fail if either of them fails. + 2. offline: only get storage versions, never reach out to VCS. + 3. fallback: only return storage versions, if VCS fails. Note this means that you may + see inconsistent results since fallback mode does a best effort of giving you what's + available at the time of requesting versions. + ''; + default = "strict"; + }; + + downloadURL = mkOption { + type = types.str; + description = lib.mdDoc "URL used if DownloadMode is set to redirect."; + default = "https://proxy.golang.org"; + }; + + singleFlightType = mkOption { + type = types.enum [ "memory" "etcd" "redis" "redis-sentinel" "gcp" "azureblob" ]; + description = lib.mdDoc '' + Determines what mechanism Athens uses to manage concurrency flowing into the Athens backend. + ''; + default = "memory"; + }; + + indexType = mkOption { + type = types.enum [ "none" "memory" "mysql" "postgres" ]; + description = lib.mdDoc '' + Type of index backend Athens will use. + ''; + default = "none"; + }; + + shutdownTimeout = mkOption { + type = types.int; + description = lib.mdDoc '' + Number of seconds to wait for the server to shutdown gracefully. + ''; + default = 60; + example = 1; + }; + + singleFlight = { + etcd = { + endpoints = mkOption { + type = types.listOf types.str; + description = lib.mdDoc "URLs that determine all distributed etcd servers."; + default = [ ]; + example = [ "localhost:2379" ]; + }; + }; + redis = { + endpoint = mkOption { + type = types.str; + description = lib.mdDoc "URL of the redis server."; + default = ""; + example = "localhost:6379"; + }; + password = mkOption { + type = types.str; + description = lib.mdDoc "Password for the redis server. Warning: this is stored in plain text in the config file."; + default = ""; + example = "swordfish"; + }; + + lockConfig = { + ttl = mkOption { + type = types.int; + description = lib.mdDoc "TTL for the lock in seconds."; + default = 900; + example = 1; + }; + timeout = mkOption { + type = types.int; + description = lib.mdDoc "Timeout for the lock in seconds."; + default = 15; + example = 1; + }; + maxRetries = mkOption { + type = types.int; + description = lib.mdDoc "Maximum number of retries for the lock."; + default = 10; + example = 1; + }; + }; + }; + + redisSentinel = { + endpoints = mkOption { + type = types.listOf types.str; + description = lib.mdDoc "URLs that determine all distributed redis servers."; + default = [ ]; + example = [ "localhost:26379" ]; + }; + masterName = mkOption { + type = types.str; + description = lib.mdDoc "Name of the sentinel master server."; + default = ""; + example = "redis-1"; + }; + sentinelPassword = mkOption { + type = types.str; + description = lib.mdDoc "Password for the sentinel server. Warning: this is stored in plain text in the config file."; + default = ""; + example = "swordfish"; + }; + + lockConfig = { + ttl = mkOption { + type = types.int; + description = lib.mdDoc "TTL for the lock in seconds."; + default = 900; + example = 1; + }; + timeout = mkOption { + type = types.int; + description = lib.mdDoc "Timeout for the lock in seconds."; + default = 15; + example = 1; + }; + maxRetries = mkOption { + type = types.int; + description = lib.mdDoc "Maximum number of retries for the lock."; + default = 10; + example = 1; + }; + }; + }; + }; + + storage = { + cdn = { + endpoint = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "hostname of the CDN server."; + example = "cdn.example.com"; + default = null; + }; + }; + + disk = { + rootPath = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc "Athens disk root folder."; + default = "/var/lib/athens"; + }; + }; + + gcp = { + projectID = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "GCP project ID."; + example = "my-project"; + default = null; + }; + bucket = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "GCP backend storage bucket."; + example = "my-bucket"; + default = null; + }; + jsonKey = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Base64 encoded GCP service account key. Warning: this is stored in plain text in the config file."; + default = null; + }; + }; + + minio = { + endpoint = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Endpoint of the minio storage backend."; + example = "minio.example.com:9001"; + default = null; + }; + key = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Access key id for the minio storage backend."; + example = "minio"; + default = null; + }; + secret = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Secret key for the minio storage backend. Warning: this is stored in plain text in the config file."; + example = "minio123"; + default = null; + }; + enableSSL = mkOption { + type = types.bool; + description = lib.mdDoc "Enable SSL for the minio storage backend."; + default = false; + }; + bucket = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Bucket name for the minio storage backend."; + example = "gomods"; + default = null; + }; + region = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Region for the minio storage backend."; + example = "us-east-1"; + default = null; + }; + }; + + mongo = { + url = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "URL of the mongo database."; + example = "mongodb://localhost:27017"; + default = null; + }; + defaultDBName = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Name of the mongo database."; + example = "athens"; + default = null; + }; + certPath = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc "Path to the certificate file for the mongo database."; + example = "/etc/ssl/mongo.pem"; + default = null; + }; + insecure = mkOption { + type = types.bool; + description = lib.mdDoc "Allow insecure connections to the mongo database."; + default = false; + }; + }; + + s3 = { + region = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Region of the S3 storage backend."; + example = "eu-west-3"; + default = null; + }; + key = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Access key id for the S3 storage backend."; + example = "minio"; + default = null; + }; + secret = mkOption { + type = types.str; + description = lib.mdDoc "Secret key for the S3 storage backend. Warning: this is stored in plain text in the config file."; + default = ""; + }; + token = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Token for the S3 storage backend. Warning: this is stored in plain text in the config file."; + default = null; + }; + bucket = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Bucket name for the S3 storage backend."; + example = "gomods"; + default = null; + }; + forcePathStyle = mkOption { + type = types.bool; + description = lib.mdDoc "Force path style for the S3 storage backend."; + default = false; + }; + useDefaultConfiguration = mkOption { + type = types.bool; + description = lib.mdDoc "Use default configuration for the S3 storage backend."; + default = false; + }; + credentialsEndpoint = mkOption { + type = types.str; + description = lib.mdDoc "Credentials endpoint for the S3 storage backend."; + default = ""; + }; + awsContainerCredentialsRelativeURI = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Container relative url (used by fargate)."; + default = null; + }; + endpoint = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Endpoint for the S3 storage backend."; + default = null; + }; + }; + + azureblob = { + accountName = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Account name for the Azure Blob storage backend."; + default = null; + }; + accountKey = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Account key for the Azure Blob storage backend. Warning: this is stored in plain text in the config file."; + default = null; + }; + containerName = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Container name for the Azure Blob storage backend."; + default = null; + }; + }; + + external = { + url = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "URL of the backend storage layer."; + example = "https://athens.example.com"; + default = null; + }; + }; + }; + + index = { + mysql = { + protocol = mkOption { + type = types.str; + description = lib.mdDoc "Protocol for the MySQL database."; + default = "tcp"; + }; + host = mkOption { + type = types.str; + description = lib.mdDoc "Host for the MySQL database."; + default = "localhost"; + }; + port = mkOption { + type = types.int; + description = lib.mdDoc "Port for the MySQL database."; + default = 3306; + }; + user = mkOption { + type = types.str; + description = lib.mdDoc "User for the MySQL database."; + default = "root"; + }; + password = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Password for the MySQL database. Warning: this is stored in plain text in the config file."; + default = null; + }; + database = mkOption { + type = types.str; + description = lib.mdDoc "Database name for the MySQL database."; + default = "athens"; + }; + params = { + parseTime = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Parse time for the MySQL database."; + default = "true"; + }; + timeout = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Timeout for the MySQL database."; + default = "30s"; + }; + }; + }; + + postgres = { + host = mkOption { + type = types.str; + description = lib.mdDoc "Host for the Postgres database."; + default = "localhost"; + }; + port = mkOption { + type = types.int; + description = lib.mdDoc "Port for the Postgres database."; + default = 5432; + }; + user = mkOption { + type = types.str; + description = lib.mdDoc "User for the Postgres database."; + default = "postgres"; + }; + password = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Password for the Postgres database. Warning: this is stored in plain text in the config file."; + default = null; + }; + database = mkOption { + type = types.str; + description = lib.mdDoc "Database name for the Postgres database."; + default = "athens"; + }; + params = { + connect_timeout = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "Connect timeout for the Postgres database."; + default = "30s"; + }; + sslmode = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc "SSL mode for the Postgres database."; + default = "disable"; + }; + }; + }; + }; + + extraConfig = mkOption { + type = types.attrs; + description = lib.mdDoc '' + Extra configuration options for the athens config file. + ''; + default = { }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.athens = { + description = "Athens Go module proxy"; + documentation = [ "https://docs.gomods.io" ]; + + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + + serviceConfig = { + Restart = "on-abnormal"; + Nice = 5; + ExecStart = ''${cfg.package}/bin/athens -config_file=${configFile}''; + + KillMode = "mixed"; + KillSignal = "SIGINT"; + TimeoutStopSec = cfg.shutdownTimeout; + + LimitNOFILE = 1048576; + LimitNPROC = 512; + + DynamicUser = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHome = "read-only"; + ProtectSystem = "full"; + + ReadWritePaths = mkIf (cfg.storage.disk.rootPath != null && (! hasPrefix "/var/lib/" cfg.storage.disk.rootPath)) [ cfg.storage.disk.rootPath ]; + StateDirectory = mkIf (hasPrefix "/var/lib/" cfg.storage.disk.rootPath) [ (removePrefix "/var/lib/" cfg.storage.disk.rootPath) ]; + + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + NoNewPrivileges = true; + }; + }; + + networking.firewall = { + allowedTCPPorts = optionals (cfg.unixSocket == null) [ cfg.port ] + ++ optionals cfg.enablePprof [ cfg.pprofPort ]; + }; + }; + +} diff --git a/nixos/modules/services/matrix/maubot.md b/nixos/modules/services/matrix/maubot.md new file mode 100644 index 000000000000..f6a05db56caf --- /dev/null +++ b/nixos/modules/services/matrix/maubot.md @@ -0,0 +1,103 @@ +# Maubot {#module-services-maubot} + +[Maubot](https://github.com/maubot/maubot) is a plugin-based bot +framework for Matrix. + +## Configuration {#module-services-maubot-configuration} + +1. Set [](#opt-services.maubot.enable) to `true`. The service will use + SQLite by default. +2. If you want to use PostgreSQL instead of SQLite, do this: + + ```nix + services.maubot.settings.database = "postgresql://maubot@localhost/maubot"; + ``` + + If the PostgreSQL connection requires a password, you will have to + add it later on step 8. +3. If you plan to expose your Maubot interface to the web, do something + like this: + ```nix + services.nginx.virtualHosts."matrix.example.org".locations = { + "/_matrix/maubot/" = { + proxyPass = "http://127.0.0.1:${toString config.services.maubot.settings.server.port}"; + proxyWebsockets = true; + }; + }; + services.maubot.settings.server.public_url = "matrix.example.org"; + # do the following only if you want to use something other than /_matrix/maubot... + services.maubot.settings.server.ui_base_path = "/another/base/path"; + ``` +4. Optionally, set `services.maubot.pythonPackages` to a list of python3 + packages to make available for Maubot plugins. +5. Optionally, set `services.maubot.plugins` to a list of Maubot + plugins (full list available at https://plugins.maubot.xyz/): + ```nix + services.maubot.plugins = with config.services.maubot.package.plugins; [ + reactbot + # This will only change the default config! After you create a + # plugin instance, the default config will be copied into that + # instance's config in Maubot's database, and further base config + # changes won't affect the running plugin. + (rss.override { + base_config = { + update_interval = 60; + max_backoff = 7200; + spam_sleep = 2; + command_prefix = "rss"; + admins = [ "@chayleaf:pavluk.org" ]; + }; + }) + ]; + # ...or... + services.maubot.plugins = config.services.maubot.package.plugins.allOfficialPlugins; + # ...or... + services.maubot.plugins = config.services.maubot.package.plugins.allPlugins; + # ...or... + services.maubot.plugins = with config.services.maubot.package.plugins; [ + (weather.override { + # you can pass base_config as a string + base_config = '' + default_location: New York + default_units: M + default_language: + show_link: true + show_image: false + ''; + }) + ]; + ``` +6. Start Maubot at least once before doing the following steps (it's + necessary to generate the initial config). +7. If your PostgreSQL connection requires a password, add + `database: postgresql://user:password@localhost/maubot` + to `/var/lib/maubot/config.yaml`. This overrides the Nix-provided + config. Even then, don't remove the `database` line from Nix config + so the module knows you use PostgreSQL! +8. To create a user account for logging into Maubot web UI and + configuring it, generate a password using the shell command + `mkpasswd -R 12 -m bcrypt`, and edit `/var/lib/maubot/config.yaml` + with the following: + + ```yaml + admins: + admin_username: $2b$12$g.oIStUeUCvI58ebYoVMtO/vb9QZJo81PsmVOomHiNCFbh0dJpZVa + ``` + + Where `admin_username` is your username, and `$2b...` is the bcrypted + password. +9. Optional: if you want to be able to register new users with the + Maubot CLI (`mbc`), and your homeserver is private, add your + homeserver's registration key to `/var/lib/maubot/config.yaml`: + + ```yaml + homeservers: + matrix.example.org: + url: https://matrix.example.org + secret: your-very-secret-key + ``` +10. Restart Maubot after editing `/var/lib/maubot/config.yaml`,and + Maubot will be available at + `https://matrix.example.org/_matrix/maubot`. If you want to use the + `mbc` CLI, it's available using the `maubot` package (`nix-shell -p + maubot`). diff --git a/nixos/modules/services/matrix/maubot.nix b/nixos/modules/services/matrix/maubot.nix new file mode 100644 index 000000000000..6cdb57fa72ef --- /dev/null +++ b/nixos/modules/services/matrix/maubot.nix @@ -0,0 +1,459 @@ +{ lib +, config +, pkgs +, ... +}: + +let + cfg = config.services.maubot; + + wrapper1 = + if cfg.plugins == [ ] + then cfg.package + else cfg.package.withPlugins (_: cfg.plugins); + + wrapper2 = + if cfg.pythonPackages == [ ] + then wrapper1 + else wrapper1.withPythonPackages (_: cfg.pythonPackages); + + settings = lib.recursiveUpdate cfg.settings { + plugin_directories.trash = + if cfg.settings.plugin_directories.trash == null + then "delete" + else cfg.settings.plugin_directories.trash; + server.unshared_secret = "generate"; + }; + + finalPackage = wrapper2.withBaseConfig settings; + + isPostgresql = db: builtins.isString db && lib.hasPrefix "postgresql://" db; + isLocalPostgresDB = db: isPostgresql db && builtins.any (x: lib.hasInfix x db) [ + "@127.0.0.1/" + "@::1/" + "@[::1]/" + "@localhost/" + ]; + parsePostgresDB = db: + let + noSchema = lib.removePrefix "postgresql://" db; + in { + username = builtins.head (lib.splitString "@" noSchema); + database = lib.last (lib.splitString "/" noSchema); + }; + + postgresDBs = [ + cfg.settings.database + cfg.settings.crypto_database + cfg.settings.plugin_databases.postgres + ]; + + localPostgresDBs = builtins.filter isLocalPostgresDB postgresDBs; + + parsedLocalPostgresDBs = map parsePostgresDB localPostgresDBs; + parsedPostgresDBs = map parsePostgresDB postgresDBs; + + hasLocalPostgresDB = localPostgresDBs != [ ]; +in +{ + options.services.maubot = with lib; { + enable = mkEnableOption (mdDoc "maubot"); + + package = lib.mkPackageOptionMD pkgs "maubot" { }; + + plugins = mkOption { + type = types.listOf types.package; + default = [ ]; + example = literalExpression '' + with config.services.maubot.package.plugins; [ + xyz.maubot.reactbot + xyz.maubot.rss + ]; + ''; + description = mdDoc '' + List of additional maubot plugins to make available. + ''; + }; + + pythonPackages = mkOption { + type = types.listOf types.package; + default = [ ]; + example = literalExpression '' + with pkgs.python3Packages; [ + aiohttp + ]; + ''; + description = mdDoc '' + List of additional Python packages to make available for maubot. + ''; + }; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/maubot"; + description = mdDoc '' + The directory where maubot stores its stateful data. + ''; + }; + + extraConfigFile = mkOption { + type = types.str; + default = "./config.yaml"; + defaultText = literalExpression ''"''${config.services.maubot.dataDir}/config.yaml"''; + description = mdDoc '' + A file for storing secrets. You can pass homeserver registration keys here. + If it already exists, **it must contain `server.unshared_secret`** which is used for signing API keys. + If `configMutable` is not set to true, **maubot user must have write access to this file**. + ''; + }; + + configMutable = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Whether maubot should write updated config into `extraConfigFile`. **This will make your Nix module settings have no effect besides the initial config, as extraConfigFile takes precedence over NixOS settings!** + ''; + }; + + settings = mkOption { + default = { }; + description = mdDoc '' + YAML settings for maubot. See the + [example configuration](https://github.com/maubot/maubot/blob/master/maubot/example-config.yaml) + for more info. + + Secrets should be passed in by using `extraConfigFile`. + ''; + type = with types; submodule { + options = { + database = mkOption { + type = str; + default = "sqlite:maubot.db"; + example = "postgresql://username:password@hostname/dbname"; + description = mdDoc '' + The full URI to the database. SQLite and Postgres are fully supported. + Other DBMSes supported by SQLAlchemy may or may not work. + ''; + }; + + crypto_database = mkOption { + type = str; + default = "default"; + example = "postgresql://username:password@hostname/dbname"; + description = mdDoc '' + Separate database URL for the crypto database. By default, the regular database is also used for crypto. + ''; + }; + + database_opts = mkOption { + type = types.attrs; + default = { }; + description = mdDoc '' + Additional arguments for asyncpg.create_pool() or sqlite3.connect() + ''; + }; + + plugin_directories = mkOption { + default = { }; + description = mdDoc "Plugin directory paths"; + type = submodule { + options = { + upload = mkOption { + type = types.str; + default = "./plugins"; + defaultText = literalExpression ''"''${config.services.maubot.dataDir}/plugins"''; + description = mdDoc '' + The directory where uploaded new plugins should be stored. + ''; + }; + load = mkOption { + type = types.listOf types.str; + default = [ "./plugins" ]; + defaultText = literalExpression ''[ "''${config.services.maubot.dataDir}/plugins" ]''; + description = mdDoc '' + The directories from which plugins should be loaded. Duplicate plugin IDs will be moved to the trash. + ''; + }; + trash = mkOption { + type = with types; nullOr str; + default = "./trash"; + defaultText = literalExpression ''"''${config.services.maubot.dataDir}/trash"''; + description = mdDoc '' + The directory where old plugin versions and conflicting plugins should be moved. Set to null to delete files immediately. + ''; + }; + }; + }; + }; + + plugin_databases = mkOption { + description = mdDoc "Plugin database settings"; + default = { }; + type = submodule { + options = { + sqlite = mkOption { + type = types.str; + default = "./plugins"; + defaultText = literalExpression ''"''${config.services.maubot.dataDir}/plugins"''; + description = mdDoc '' + The directory where SQLite plugin databases should be stored. + ''; + }; + + postgres = mkOption { + type = types.nullOr types.str; + default = if isPostgresql cfg.settings.database then "default" else null; + defaultText = literalExpression ''if isPostgresql config.services.maubot.settings.database then "default" else null''; + description = mdDoc '' + The connection URL for plugin database. See [example config](https://github.com/maubot/maubot/blob/master/maubot/example-config.yaml) for exact format. + ''; + }; + + postgres_max_conns_per_plugin = mkOption { + type = types.nullOr types.int; + default = 3; + description = mdDoc '' + Maximum number of connections per plugin instance. + ''; + }; + + postgres_opts = mkOption { + type = types.attrs; + default = { }; + description = mdDoc '' + Overrides for the default database_opts when using a non-default postgres connection URL. + ''; + }; + }; + }; + }; + + server = mkOption { + default = { }; + description = mdDoc "Listener config"; + type = submodule { + options = { + hostname = mkOption { + type = types.str; + default = "127.0.0.1"; + description = mdDoc '' + The IP to listen on + ''; + }; + port = mkOption { + type = types.port; + default = 29316; + description = mdDoc '' + The port to listen on + ''; + }; + public_url = mkOption { + type = types.str; + default = "http://${cfg.settings.server.hostname}:${toString cfg.settings.server.port}"; + defaultText = literalExpression ''"http://''${config.services.maubot.settings.server.hostname}:''${toString config.services.maubot.settings.server.port}"''; + description = mdDoc '' + Public base URL where the server is visible. + ''; + }; + ui_base_path = mkOption { + type = types.str; + default = "/_matrix/maubot"; + description = mdDoc '' + The base path for the UI. + ''; + }; + plugin_base_path = mkOption { + type = types.str; + default = "${config.services.maubot.settings.server.ui_base_path}/plugin/"; + defaultText = literalExpression '' + "''${config.services.maubot.settings.server.ui_base_path}/plugin/" + ''; + description = mdDoc '' + The base path for plugin endpoints. The instance ID will be appended directly. + ''; + }; + override_resource_path = mkOption { + type = types.nullOr types.str; + default = null; + description = mdDoc '' + Override path from where to load UI resources. + ''; + }; + }; + }; + }; + + homeservers = mkOption { + type = types.attrsOf (types.submodule { + options = { + url = mkOption { + type = types.str; + description = mdDoc '' + Client-server API URL + ''; + }; + }; + }); + default = { + "matrix.org" = { + url = "https://matrix-client.matrix.org"; + }; + }; + description = mdDoc '' + Known homeservers. This is required for the `mbc auth` command and also allows more convenient access from the management UI. + If you want to specify registration secrets, pass this via extraConfigFile instead. + ''; + }; + + admins = mkOption { + type = types.attrsOf types.str; + default = { root = ""; }; + description = mdDoc '' + List of administrator users. Plaintext passwords will be bcrypted on startup. Set empty password + to prevent normal login. Root is a special user that can't have a password and will always exist. + ''; + }; + + api_features = mkOption { + type = types.attrsOf bool; + default = { + login = true; + plugin = true; + plugin_upload = true; + instance = true; + instance_database = true; + client = true; + client_proxy = true; + client_auth = true; + dev_open = true; + log = true; + }; + description = mdDoc '' + API feature switches. + ''; + }; + + logging = mkOption { + type = types.attrs; + description = mdDoc '' + Python logging configuration. See [section 16.7.2 of the Python + documentation](https://docs.python.org/3.6/library/logging.config.html#configuration-dictionary-schema) + for more info. + ''; + default = { + version = 1; + formatters = { + colored = { + "()" = "maubot.lib.color_log.ColorFormatter"; + format = "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"; + }; + normal = { + format = "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"; + }; + }; + handlers = { + file = { + class = "logging.handlers.RotatingFileHandler"; + formatter = "normal"; + filename = "./maubot.log"; + maxBytes = 10485760; + backupCount = 10; + }; + console = { + class = "logging.StreamHandler"; + formatter = "colored"; + }; + }; + loggers = { + maubot = { + level = "DEBUG"; + }; + mau = { + level = "DEBUG"; + }; + aiohttp = { + level = "INFO"; + }; + }; + root = { + level = "DEBUG"; + handlers = [ "file" "console" ]; + }; + }; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + warnings = lib.optional (builtins.any (x: x.username != x.database) parsedLocalPostgresDBs) '' + The Maubot database username doesn't match the database name! This means the user won't be automatically + granted ownership of the database. Consider changing either the username or the database name. + ''; + assertions = [ + { + assertion = builtins.all (x: !lib.hasInfix ":" x.username) parsedPostgresDBs; + message = '' + Putting database passwords in your Nix config makes them world-readable. To securely put passwords + in your Maubot config, change /var/lib/maubot/config.yaml after running Maubot at least once as + described in the NixOS manual. + ''; + } + { + assertion = hasLocalPostgresDB -> config.services.postgresql.enable; + message = '' + Cannot deploy maubot with a configuration for a local postgresql database and a missing postgresql service. + ''; + } + ]; + + services.postgresql = lib.mkIf hasLocalPostgresDB { + enable = true; + ensureDatabases = map (x: x.database) parsedLocalPostgresDBs; + ensureUsers = lib.flip map parsedLocalPostgresDBs (x: { + name = x.username; + ensureDBOwnership = lib.mkIf (x.username == x.database) true; + }); + }; + + users.users.maubot = { + group = "maubot"; + home = cfg.dataDir; + # otherwise StateDirectory is enough + createHome = lib.mkIf (cfg.dataDir != "/var/lib/maubot") true; + isSystemUser = true; + }; + + users.groups.maubot = { }; + + systemd.services.maubot = rec { + description = "maubot - a plugin-based Matrix bot system written in Python"; + after = [ "network.target" ] ++ wants ++ lib.optional hasLocalPostgresDB "postgresql.service"; + # all plugins get automatically disabled if maubot starts before synapse + wants = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + if [ ! -f "${cfg.extraConfigFile}" ]; then + echo "server:" > "${cfg.extraConfigFile}" + echo " unshared_secret: $(head -c40 /dev/random | base32 | ${pkgs.gawk}/bin/awk '{print tolower($0)}')" > "${cfg.extraConfigFile}" + chmod 640 "${cfg.extraConfigFile}" + fi + ''; + + serviceConfig = { + ExecStart = "${finalPackage}/bin/maubot --config ${cfg.extraConfigFile}" + lib.optionalString (!cfg.configMutable) " --no-update"; + User = "maubot"; + Group = "maubot"; + Restart = "on-failure"; + RestartSec = "10s"; + StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/maubot") "maubot"; + WorkingDirectory = cfg.dataDir; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ chayleaf ]; + meta.doc = ./maubot.md; +} diff --git a/nixos/modules/services/misc/forgejo.md b/nixos/modules/services/misc/forgejo.md index 3df8bc20976a..14b21933e6b0 100644 --- a/nixos/modules/services/misc/forgejo.md +++ b/nixos/modules/services/misc/forgejo.md @@ -20,7 +20,7 @@ If you experience issues with your instance using `services.gitea`, ::: {.note} Migrating is, while not strictly necessary at this point, highly recommended. -Both modules and projects are likely to divide further with each release. +Both modules and projects are likely to diverge further with each release. Which might lead to an even more involved migration. ::: diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix index d44219008466..af9768c98ac3 100644 --- a/nixos/modules/services/misc/sourcehut/default.nix +++ b/nixos/modules/services/misc/sourcehut/default.nix @@ -1325,6 +1325,11 @@ in (import ./service.nix "paste" { inherit configIniOfService; port = 5011; + extraServices.pastesrht-api = { + serviceConfig.Restart = "always"; + serviceConfig.RestartSec = "5s"; + serviceConfig.ExecStart = "${pkgs.sourcehut.pastesrht}/bin/pastesrht-api -b ${cfg.listenAddress}:${toString (cfg.paste.port + 100)}"; + }; }) (import ./service.nix "todo" { diff --git a/nixos/modules/services/networking/x2goserver.nix b/nixos/modules/services/networking/x2goserver.nix index 1242229a0b60..f1eba9fafc1c 100644 --- a/nixos/modules/services/networking/x2goserver.nix +++ b/nixos/modules/services/networking/x2goserver.nix @@ -160,5 +160,8 @@ in { security.sudo.extraConfig = '' Defaults env_keep+=QT_GRAPHICSSYSTEM ''; + security.sudo-rs.extraConfig = '' + Defaults env_keep+=QT_GRAPHICSSYSTEM + ''; }; } diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index c3893f4b09b2..72a195d3a04e 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -145,7 +145,8 @@ in systemd.services.clamav-daemon = mkIf cfg.daemon.enable { description = "ClamAV daemon (clamd)"; - after = optional cfg.updater.enable "clamav-freshclam.service"; + after = optionals cfg.updater.enable [ "clamav-freshclam.service" ]; + wants = optionals cfg.updater.enable [ "clamav-freshclam.service" ]; wantedBy = [ "multi-user.target" ]; restartTriggers = [ clamdConfigFile ]; diff --git a/nixos/modules/system/boot/unl0kr.nix b/nixos/modules/system/boot/unl0kr.nix new file mode 100644 index 000000000000..8d9af37382e0 --- /dev/null +++ b/nixos/modules/system/boot/unl0kr.nix @@ -0,0 +1,89 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.boot.initrd.unl0kr; +in +{ + options.boot.initrd.unl0kr = { + enable = lib.mkEnableOption (lib.mdDoc "unl0kr in initrd") // { + description = lib.mdDoc '' + Whether to enable the unl0kr on-screen keyboard in initrd to unlock LUKS. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + meta.maintainers = with lib.maintainers; [ tomfitzhenry ]; + assertions = [ + { + assertion = cfg.enable -> config.boot.initrd.systemd.enable; + message = "boot.initrd.unl0kr is only supported with boot.initrd.systemd."; + } + ]; + + boot.initrd.systemd = { + storePaths = with pkgs; [ + "${pkgs.gnugrep}/bin/grep" + libinput + xkeyboard_config + "${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password" + "${pkgs.unl0kr}/bin/unl0kr" + ]; + services = { + unl0kr-ask-password = { + description = "Forward Password Requests to unl0kr"; + conflicts = [ + "emergency.service" + "initrd-switch-root.target" + "shutdown.target" + ]; + unitConfig.DefaultDependencies = false; + after = [ + "systemd-vconsole-setup.service" + "udev.service" + ]; + before = [ + "shutdown.target" + ]; + script = '' + # This script acts as a Password Agent: https://systemd.io/PASSWORD_AGENTS/ + + DIR=/run/systemd/ask-password/ + # If a user has multiple encrypted disks, the requests might come in different times, + # so make sure to answer as many requests as we can. Once boot succeeds, other + # password agents will be responsible for watching for requests. + while [ -d $DIR ] && [ "$(ls -A $DIR/ask.*)" ]; + do + for file in `ls $DIR/ask.*`; do + socket="$(cat "$file" | ${pkgs.gnugrep}/bin/grep "Socket=" | cut -d= -f2)" + ${pkgs.unl0kr}/bin/unl0kr | ${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password 1 "$socket" + done + done + ''; + }; + }; + + paths = { + unl0kr-ask-password = { + description = "Forward Password Requests to unl0kr"; + conflicts = [ + "emergency.service" + "initrd-switch-root.target" + "shutdown.target" + ]; + unitConfig.DefaultDependencies = false; + before = [ + "shutdown.target" + "paths.target" + "cryptsetup.target" + ]; + wantedBy = [ "sysinit.target" ]; + pathConfig = { + DirectoryNotEmpty = "/run/systemd/ask-password"; + MakeDirectory = true; + }; + }; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 1e11cc220805..480439c2a25e 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -813,6 +813,7 @@ in { systemd-initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix { systemdStage1 = true; }; systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {}; systemd-initrd-luks-tpm2 = handleTest ./systemd-initrd-luks-tpm2.nix {}; + systemd-initrd-luks-unl0kr = handleTest ./systemd-initrd-luks-unl0kr.nix {}; systemd-initrd-modprobe = handleTest ./systemd-initrd-modprobe.nix {}; systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; }; systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {}; diff --git a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix index e638f2e5b861..addc898bd760 100644 --- a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix +++ b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix @@ -41,10 +41,13 @@ in { }; secretFile = "/etc/nextcloud-secrets.json"; - extraOptions.redis = { - dbindex = 0; - timeout = 1.5; - # password handled via secretfile below + extraOptions = { + allow_local_remote_servers = true; + redis = { + dbindex = 0; + timeout = 1.5; + # password handled via secretfile below + }; }; configureRedis = true; }; @@ -62,6 +65,7 @@ in { services.postgresql = { enable = true; + package = pkgs.postgresql_14; }; systemd.services.postgresql.postStart = pkgs.lib.mkAfter '' password=$(cat ${passFile}) diff --git a/nixos/tests/nixops/default.nix b/nixos/tests/nixops/default.nix index b8f747b2a19f..f7a26f2461c4 100644 --- a/nixos/tests/nixops/default.nix +++ b/nixos/tests/nixops/default.nix @@ -1,4 +1,6 @@ -{ pkgs, ... }: +{ pkgs +, testers +, ... }: let inherit (pkgs) lib; @@ -19,7 +21,7 @@ let passthru.override = args': testsForPackage (args // args'); }; - testLegacyNetwork = { nixopsPkg, ... }: pkgs.nixosTest ({ + testLegacyNetwork = { nixopsPkg, ... }: testers.nixosTest ({ name = "nixops-legacy-network"; nodes = { deployer = { config, lib, nodes, pkgs, ... }: { diff --git a/nixos/tests/systemd-initrd-luks-unl0kr.nix b/nixos/tests/systemd-initrd-luks-unl0kr.nix new file mode 100644 index 000000000000..0658a098cfa2 --- /dev/null +++ b/nixos/tests/systemd-initrd-luks-unl0kr.nix @@ -0,0 +1,75 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: let + passphrase = "secret"; +in { + name = "systemd-initrd-luks-unl0kr"; + meta = with pkgs.lib.maintainers; { + maintainers = [ tomfitzhenry ]; + }; + + enableOCR = true; + + nodes.machine = { pkgs, ... }: { + virtualisation = { + emptyDiskImages = [ 512 512 ]; + useBootLoader = true; + mountHostNixStore = true; + useEFIBoot = true; + qemu.options = [ + "-vga virtio" + ]; + }; + boot.loader.systemd-boot.enable = true; + + boot.initrd.availableKernelModules = [ + "evdev" # for entering pw + "bochs" + ]; + + environment.systemPackages = with pkgs; [ cryptsetup ]; + boot.initrd = { + systemd = { + enable = true; + emergencyAccess = true; + }; + unl0kr.enable = true; + }; + + specialisation.boot-luks.configuration = { + boot.initrd.luks.devices = lib.mkVMOverride { + # We have two disks and only type one password - key reuse is in place + cryptroot.device = "/dev/vdb"; + cryptroot2.device = "/dev/vdc"; + }; + virtualisation.rootDevice = "/dev/mapper/cryptroot"; + virtualisation.fileSystems."/".autoFormat = true; + # test mounting device unlocked in initrd after switching root + virtualisation.fileSystems."/cryptroot2".device = "/dev/mapper/cryptroot2"; + }; + }; + + testScript = '' + # Create encrypted volume + machine.wait_for_unit("multi-user.target") + machine.succeed("echo -n ${passphrase} | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -") + machine.succeed("echo -n ${passphrase} | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -") + machine.succeed("echo -n ${passphrase} | cryptsetup luksOpen -q /dev/vdc cryptroot2") + machine.succeed("mkfs.ext4 /dev/mapper/cryptroot2") + + # Boot from the encrypted disk + machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf") + machine.succeed("sync") + machine.crash() + + # Boot and decrypt the disk + machine.start() + machine.wait_for_text("Password required for booting") + machine.screenshot("prompt") + machine.send_chars("${passphrase}") + machine.screenshot("pw") + machine.send_chars("\n") + machine.wait_for_unit("multi-user.target") + + assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount"), "/dev/mapper/cryptroot do not appear in mountpoints list" + assert "/dev/mapper/cryptroot2 on /cryptroot2 type ext4" in machine.succeed("mount") + ''; +}) diff --git a/nixos/tests/systemd-timesyncd.nix b/nixos/tests/systemd-timesyncd.nix index f38d06be1516..02f49f49b8a5 100644 --- a/nixos/tests/systemd-timesyncd.nix +++ b/nixos/tests/systemd-timesyncd.nix @@ -15,13 +15,14 @@ in { # create the path that should be migrated by our activation script when # upgrading to a newer nixos version system.stateVersion = "19.03"; - systemd.tmpfiles.rules = [ - "r /var/lib/systemd/timesync -" - "d /var/lib/systemd -" - "d /var/lib/private/systemd/timesync -" - "L /var/lib/systemd/timesync - - - - /var/lib/private/systemd/timesync" - "d /var/lib/private/systemd/timesync - systemd-timesync systemd-timesync -" - ]; + systemd.tmpfiles.settings.systemd-timesyncd-test = { + "/var/lib/systemd/timesync".R = { }; + "/var/lib/systemd/timesync".L.argument = "/var/lib/private/systemd/timesync"; + "/var/lib/private/systemd/timesync".d = { + user = "systemd-timesync"; + group = "systemd-timesync"; + }; + }; }); }; |