···58585959- [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`.
60606161+- [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).
6262+6163- [Homer](https://homer-demo.netlify.app/), a very simple static homepage for your server. Available as [services.homer](options.html#opt-services.homer).
62646365- [Ghidra](https://ghidra-sre.org/), a software reverse engineering (SRE) suite of tools. Available as [programs.ghidra](options.html#opt-programs.ghidra).
···11+{
22+ config,
33+ lib,
44+ pkgs,
55+ ...
66+}:
77+let
88+99+ cfg = config.services.oncall;
1010+ settingsFormat = pkgs.formats.yaml { };
1111+ configFile = settingsFormat.generate "oncall_extra_settings.yaml" cfg.settings;
1212+1313+in
1414+{
1515+ options.services.oncall = {
1616+1717+ enable = lib.mkEnableOption "Oncall web app";
1818+1919+ package = lib.mkPackageOption pkgs "oncall" { };
2020+2121+ database.createLocally = lib.mkEnableOption "Create the database and database user locally." // {
2222+ default = true;
2323+ };
2424+2525+ settings = lib.mkOption {
2626+ type = lib.types.submodule {
2727+ freeformType = settingsFormat.type;
2828+ options = {
2929+ oncall_host = lib.mkOption {
3030+ type = lib.types.str;
3131+ default = "localhost";
3232+ description = "FQDN for the Oncall instance.";
3333+ };
3434+ db.conn = {
3535+ kwargs = {
3636+ user = lib.mkOption {
3737+ type = lib.types.str;
3838+ default = "oncall";
3939+ description = "Database user.";
4040+ };
4141+ host = lib.mkOption {
4242+ type = lib.types.str;
4343+ default = "localhost";
4444+ description = "Database host.";
4545+ };
4646+ database = lib.mkOption {
4747+ type = lib.types.str;
4848+ default = "oncall";
4949+ description = "Database name.";
5050+ };
5151+ };
5252+ str = lib.mkOption {
5353+ type = lib.types.str;
5454+ default = "%(scheme)s://%(user)s@%(host)s:%(port)s/%(database)s?charset=%(charset)s&unix_socket=/run/mysqld/mysqld.sock";
5555+ description = ''
5656+ Database connection scheme. The default specifies the
5757+ connection through a local socket.
5858+ '';
5959+ };
6060+ require_auth = lib.mkOption {
6161+ type = lib.types.bool;
6262+ default = true;
6363+ description = ''
6464+ Whether authentication is required to access the web app.
6565+ '';
6666+ };
6767+ };
6868+ };
6969+ };
7070+ default = { };
7171+ description = ''
7272+ Extra configuration options to append or override.
7373+ For available and default option values see
7474+ [upstream configuration file](https://github.com/linkedin/oncall/blob/master/configs/config.yaml)
7575+ and the administration part in the
7676+ [offical documentation](https://oncall.tools/docs/admin_guide.html).
7777+ '';
7878+ };
7979+8080+ secretFile = lib.mkOption {
8181+ type = lib.types.pathWith {
8282+ inStore = false;
8383+ absolute = true;
8484+ };
8585+ example = "/run/keys/oncall-dbpassword";
8686+ description = ''
8787+ A YAML file containing secrets such as database or user passwords.
8888+ Some variables that can be considered secrets are:
8989+9090+ - db.conn.kwargs.password:
9191+ Password used to authenticate to the database.
9292+9393+ - session.encrypt_key:
9494+ Key for encrypting/signing session cookies.
9595+ Change to random long values in production.
9696+9797+ - session.sign_key:
9898+ Key for encrypting/signing session cookies.
9999+ Change to random long values in production.
100100+ '';
101101+ };
102102+103103+ };
104104+105105+ config = lib.mkIf cfg.enable {
106106+107107+ # Disable debug, only needed for development
108108+ services.oncall.settings = lib.mkMerge [
109109+ ({
110110+ debug = lib.mkDefault false;
111111+ auth.debug = lib.mkDefault false;
112112+ })
113113+ ];
114114+115115+ services.uwsgi = {
116116+ enable = true;
117117+ plugins = [ "python3" ];
118118+ user = "oncall";
119119+ instance = {
120120+ type = "emperor";
121121+ vassals = {
122122+ oncall = {
123123+ type = "normal";
124124+ env = [
125125+ "PYTHONPATH=${pkgs.oncall.pythonPath}"
126126+ (
127127+ "ONCALL_EXTRA_CONFIG="
128128+ + (lib.concatStringsSep "," (
129129+ [ configFile ] ++ lib.optional (cfg.secretFile != null) cfg.secretFile
130130+ ))
131131+ )
132132+ "STATIC_ROOT=/var/lib/oncall"
133133+ ];
134134+ module = "oncall.app:get_wsgi_app()";
135135+ socket = "${config.services.uwsgi.runDir}/oncall.sock";
136136+ socketGroup = "nginx";
137137+ immediate-gid = "nginx";
138138+ chmod-socket = "770";
139139+ pyargv = "${pkgs.oncall}/share/configs/config.yaml";
140140+ buffer-size = 32768;
141141+ };
142142+ };
143143+ };
144144+ };
145145+146146+ services.nginx = {
147147+ enable = lib.mkDefault true;
148148+ virtualHosts."${cfg.settings.oncall_host}".locations = {
149149+ "/".extraConfig = "uwsgi_pass unix://${config.services.uwsgi.runDir}/oncall.sock;";
150150+ };
151151+ };
152152+153153+ services.mysql = lib.mkIf cfg.database.createLocally {
154154+ enable = true;
155155+ package = lib.mkDefault pkgs.mariadb;
156156+ ensureDatabases = [ cfg.settings.db.conn.kwargs.database ];
157157+ ensureUsers = [
158158+ {
159159+ name = cfg.settings.db.conn.kwargs.user;
160160+ ensurePermissions = {
161161+ "${cfg.settings.db.conn.kwargs.database}.*" = "ALL PRIVILEGES";
162162+ };
163163+ }
164164+ ];
165165+ };
166166+167167+ users.users.oncall = {
168168+ group = "nginx";
169169+ isSystemUser = true;
170170+ };
171171+172172+ systemd = {
173173+ services = {
174174+ uwsgi.serviceConfig.StateDirectory = "oncall";
175175+ oncall-setup-database = lib.mkIf cfg.database.createLocally {
176176+ description = "Set up Oncall database";
177177+ serviceConfig = {
178178+ Type = "oneshot";
179179+ RemainAfterExit = true;
180180+ };
181181+ requiredBy = [ "uwsgi.service" ];
182182+ after = [ "mysql.service" ];
183183+ script =
184184+ let
185185+ mysql = "${lib.getExe' config.services.mysql.package "mysql"}";
186186+ in
187187+ ''
188188+ if [ ! -f /var/lib/oncall/.dbexists ]; then
189189+ # Load database schema provided with package
190190+ ${mysql} ${cfg.settings.db.conn.kwargs.database} < ${cfg.package}/share/db/schema.v0.sql
191191+ ${mysql} ${cfg.settings.db.conn.kwargs.database} < ${cfg.package}/share/db/schema-update.v0-1602184489.sql
192192+ touch /var/lib/oncall/.dbexists
193193+ fi
194194+ '';
195195+ };
196196+ };
197197+ };
198198+199199+ };
200200+201201+ meta.maintainers = with lib.maintainers; [ onny ];
202202+203203+}
+1
nixos/tests/all-tests.nix
···618618 odoo = handleTest ./odoo.nix { };
619619 odoo17 = handleTest ./odoo.nix { package = pkgs.odoo17; };
620620 odoo16 = handleTest ./odoo.nix { package = pkgs.odoo16; };
621621+ oncall = runTest ./web-apps/oncall.nix;
621622 # 9pnet_virtio used to mount /nix partition doesn't support
622623 # hibernation. This test happens to work on x86_64-linux but
623624 # not on other platforms.