diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 453e0ed450bd..d4576463ed1e 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -62,6 +62,8 @@ - [vwifi](https://github.com/Raizo62/vwifi), a Wi-Fi simulator daemon leveraging the `mac80211_hwsim` and `vhost_vsock` kernel modules for efficient simulation of multi-node Wi-Fi networks. Available as {option}`services.vwifi`. +- [Oncall](https://oncall.tools), a web-based calendar tool designed for scheduling and managing on-call shifts. Available as [services.oncall](options.html#opt-services.oncall). + - [Homer](https://homer-demo.netlify.app/), a very simple static homepage for your server. Available as [services.homer](options.html#opt-services.homer). - [Ghidra](https://ghidra-sre.org/), a software reverse engineering (SRE) suite of tools. Available as [programs.ghidra](options.html#opt-programs.ghidra). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 073f8ebede1a..d4f09fef8364 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1608,6 +1608,7 @@ ./services/web-apps/nostr-rs-relay.nix ./services/web-apps/ocis.nix ./services/web-apps/olivetin.nix + ./services/web-apps/oncall.nix ./services/web-apps/onlyoffice.nix ./services/web-apps/open-web-calendar.nix ./services/web-apps/openvscode-server.nix diff --git a/nixos/modules/services/web-apps/oncall.nix b/nixos/modules/services/web-apps/oncall.nix new file mode 100644 index 000000000000..cd842606f278 --- /dev/null +++ b/nixos/modules/services/web-apps/oncall.nix @@ -0,0 +1,203 @@ +{ + config, + lib, + pkgs, + ... +}: +let + + cfg = config.services.oncall; + settingsFormat = pkgs.formats.yaml { }; + configFile = settingsFormat.generate "oncall_extra_settings.yaml" cfg.settings; + +in +{ + options.services.oncall = { + + enable = lib.mkEnableOption "Oncall web app"; + + package = lib.mkPackageOption pkgs "oncall" { }; + + database.createLocally = lib.mkEnableOption "Create the database and database user locally." // { + default = true; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = settingsFormat.type; + options = { + oncall_host = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = "FQDN for the Oncall instance."; + }; + db.conn = { + kwargs = { + user = lib.mkOption { + type = lib.types.str; + default = "oncall"; + description = "Database user."; + }; + host = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = "Database host."; + }; + database = lib.mkOption { + type = lib.types.str; + default = "oncall"; + description = "Database name."; + }; + }; + str = lib.mkOption { + type = lib.types.str; + default = "%(scheme)s://%(user)s@%(host)s:%(port)s/%(database)s?charset=%(charset)s&unix_socket=/run/mysqld/mysqld.sock"; + description = '' + Database connection scheme. The default specifies the + connection through a local socket. + ''; + }; + require_auth = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether authentication is required to access the web app. + ''; + }; + }; + }; + }; + default = { }; + description = '' + Extra configuration options to append or override. + For available and default option values see + [upstream configuration file](https://github.com/linkedin/oncall/blob/master/configs/config.yaml) + and the administration part in the + [offical documentation](https://oncall.tools/docs/admin_guide.html). + ''; + }; + + secretFile = lib.mkOption { + type = lib.types.pathWith { + inStore = false; + absolute = true; + }; + example = "/run/keys/oncall-dbpassword"; + description = '' + A YAML file containing secrets such as database or user passwords. + Some variables that can be considered secrets are: + + - db.conn.kwargs.password: + Password used to authenticate to the database. + + - session.encrypt_key: + Key for encrypting/signing session cookies. + Change to random long values in production. + + - session.sign_key: + Key for encrypting/signing session cookies. + Change to random long values in production. + ''; + }; + + }; + + config = lib.mkIf cfg.enable { + + # Disable debug, only needed for development + services.oncall.settings = lib.mkMerge [ + ({ + debug = lib.mkDefault false; + auth.debug = lib.mkDefault false; + }) + ]; + + services.uwsgi = { + enable = true; + plugins = [ "python3" ]; + user = "oncall"; + instance = { + type = "emperor"; + vassals = { + oncall = { + type = "normal"; + env = [ + "PYTHONPATH=${pkgs.oncall.pythonPath}" + ( + "ONCALL_EXTRA_CONFIG=" + + (lib.concatStringsSep "," ( + [ configFile ] ++ lib.optional (cfg.secretFile != null) cfg.secretFile + )) + ) + "STATIC_ROOT=/var/lib/oncall" + ]; + module = "oncall.app:get_wsgi_app()"; + socket = "${config.services.uwsgi.runDir}/oncall.sock"; + socketGroup = "nginx"; + immediate-gid = "nginx"; + chmod-socket = "770"; + pyargv = "${pkgs.oncall}/share/configs/config.yaml"; + buffer-size = 32768; + }; + }; + }; + }; + + services.nginx = { + enable = lib.mkDefault true; + virtualHosts."${cfg.settings.oncall_host}".locations = { + "/".extraConfig = "uwsgi_pass unix://${config.services.uwsgi.runDir}/oncall.sock;"; + }; + }; + + services.mysql = lib.mkIf cfg.database.createLocally { + enable = true; + package = lib.mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.settings.db.conn.kwargs.database ]; + ensureUsers = [ + { + name = cfg.settings.db.conn.kwargs.user; + ensurePermissions = { + "${cfg.settings.db.conn.kwargs.database}.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + users.users.oncall = { + group = "nginx"; + isSystemUser = true; + }; + + systemd = { + services = { + uwsgi.serviceConfig.StateDirectory = "oncall"; + oncall-setup-database = lib.mkIf cfg.database.createLocally { + description = "Set up Oncall database"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + requiredBy = [ "uwsgi.service" ]; + after = [ "mysql.service" ]; + script = + let + mysql = "${lib.getExe' config.services.mysql.package "mysql"}"; + in + '' + if [ ! -f /var/lib/oncall/.dbexists ]; then + # Load database schema provided with package + ${mysql} ${cfg.settings.db.conn.kwargs.database} < ${cfg.package}/share/db/schema.v0.sql + ${mysql} ${cfg.settings.db.conn.kwargs.database} < ${cfg.package}/share/db/schema-update.v0-1602184489.sql + touch /var/lib/oncall/.dbexists + fi + ''; + }; + }; + }; + + }; + + meta.maintainers = with lib.maintainers; [ onny ]; + +}