Unverified Commit c70e3c44 authored by phanirithvij's avatar phanirithvij
Browse files

nixos/goupile: init

parent cd499f48
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@
  designed to run on affordable, low-power devices. Available as [services.meshtasticd]
  (#opt-services.meshtasticd.enable).

- [Goupile](https://goupile.org/en), an open-source design tool for secure forms including Clinical Report Forms (eCRF). Available as [services.goupile](#opt-services.goupile.enable).

- [knot-resolver](https://www.knot-resolver.cz/) in version 6. Available as `services.knot-resolver`. A module for knot-resolver 5 was already available as `services.kresd`.

- [ImmichFrame](https://immichframe.dev/), display your photos from Immich as a digital photo frame. Available as `services.immichframe`.
+1 −0
Original line number Diff line number Diff line
@@ -1651,6 +1651,7 @@
  ./services/web-apps/goatcounter.nix
  ./services/web-apps/gotify-server.nix
  ./services/web-apps/gotosocial.nix
  ./services/web-apps/goupile.nix
  ./services/web-apps/grav.nix
  ./services/web-apps/grocy.nix
  ./services/web-apps/guacamole-client.nix
+147 −0
Original line number Diff line number Diff line
{
  lib,
  pkgs,
  config,
  ...
}:
let
  cfg = config.services.goupile;
  settingsFormat = pkgs.formats.ini { };
in
{
  options.services.goupile = {
    enable = lib.mkEnableOption "Goupile server";
    package = lib.mkPackageOption pkgs "goupile" { };

    enableSandbox = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = "Enable the sandbox option.";
    };

    settings = lib.mkOption {
      type = lib.types.submodule {
        freeformType = settingsFormat.type;
        options = {
          HTTP.Port = lib.mkOption {
            type = lib.types.port;
            default = 8889;
            description = "The port goupile runs on";
          };
          Data.RootDirectory = lib.mkOption {
            type = lib.types.str;
            default = "/var/lib/goupile";
            description = "Goupile's data directory.";
          };
        };
      };
      default = { }; # default will be lost for submodules if overriden
      example = lib.literalExpression ''
        {
          HTTP.Port = 8888;
        }
      '';
      description = ''
        The options for `systemd.services.goupile` in ini format.

        The configuration options available can be found here
        https://github.com/Koromix/rygel/blob/goupile/3.11.1/src/goupile/server/admin.cc#L41
      '';
    };

    configFile = lib.mkOption {
      type = lib.types.path;
      description = ''
        The configuration file to be passed to goupile server.

        By default the configuration file is created from `services.goupile.settings`.
      '';
    };

    hostName = lib.mkOption {
      type = lib.types.str;
      default = "goupile";
      description = "Nginx service name for goupile service.";
    };
  };
  config = lib.mkIf cfg.enable (
    lib.mkMerge [
      {
        services.nginx = {
          enable = lib.mkDefault true;
          virtualHosts.${cfg.hostName} = {
            locations."/".proxyPass = "http://${cfg.hostName}:${builtins.toString cfg.settings.HTTP.Port}";
          };
        };
      }
      {
        services.goupile.configFile = settingsFormat.generate "goupile.ini" cfg.settings;
      }
      {
        systemd.services.goupile = {
          wants = [ "network-online.target" ];
          after = [ "network-online.target" ];
          wantedBy = [ "multi-user.target" ];

          documentation = [ "https://goupile.org/en" ];
          description = "Goupile eCRF";

          serviceConfig = {
            ExecStart = ''
              ${lib.getExe cfg.package} \
                ${lib.optionalString cfg.enableSandbox "--sandbox"} \
                -C ${cfg.configFile}
            '';

            DynamicUser = true;

            RuntimeDirectory = "goupile";
            RuntimeDirectoryPreserve = "yes";
            StateDirectory = "goupile";
            UMask = 0077;
            WorkingDirectory = "%S/goupile";

            SystemCallArchitectures = "native";
            SystemCallFilter = [
              "~@privileged"
              "~@resources"
              "~@obsolete"
              "~@mount"
              "@system-service"
              "@file-system"
              "@basic-io"
              "@clock"
            ];

            ProtectHome = true;
            PrivateUsers = true;
            PrivateDevices = true;
            ProtectKernelLogs = true;
            ProtectControlGroups = true;
            ProtectKernelModules = true;

            CapabilityBoundingSet = [
              "CAP_SYS_PTRACE"
              "CAP_CHOWN"
              "CAP_DAC_OVERRIDE"
              "CAP_FOWNER"
              "CAP_KILL" # Required for child process management
              "CAP_NET_BIND_SERVICE"
              "CAP_SETGID"
              "CAP_SETUID"
              "CAP_SYS_CHROOT"
              "CAP_SYS_RESOURCE"
            ];

            Restart = "always";
            RestartSec = 20;
            TimeoutStopSec = 30;
            LimitNOFILE = 4096;
          };
        };
      }
    ]
  );

  meta.maintainers = lib.teams.ngi.members;
}
+1 −0
Original line number Diff line number Diff line
@@ -689,6 +689,7 @@ in
  gotenberg = runTest ./gotenberg.nix;
  gotify-server = runTest ./gotify-server.nix;
  gotosocial = runTest ./web-apps/gotosocial.nix;
  goupile = runTest ./web-apps/goupile;
  grafana = handleTest ./grafana { };
  graphite = runTest ./graphite.nix;
  grav = runTest ./web-apps/grav.nix;
+68 −0
Original line number Diff line number Diff line
{ lib, pkgs, ... }:
{
  name = "goupile";

  nodes.machine =
    {
      lib,
      pkgs,
      config,
      ...
    }:
    {
      services.goupile = {
        enable = true;
        enableSandbox = true;
        settings.HTTP.Port = 8889;
      };
      #systemd.services.goupile.environment.DEFAULT_SECCOMP_ACTION = "Log"; # Block|Log|Kill
      networking = {
        firewall.allowedTCPPorts = [ config.services.nginx.defaultHTTPListenPort ];
        hostName = "goupile";
        domain = "local";
      };
    };

  testScript =
    { nodes, ... }:
    let
      port = builtins.toString nodes.machine.services.goupile.settings.HTTP.Port;
    in
    # py
    ''
      start_all()

      machine.wait_for_unit("goupile.service")
      machine.wait_for_open_port(${port})

      machine.succeed("curl -q http://localhost:${port}")
      machine.succeed("curl -q http://goupile.local")
      machine.succeed("curl -q http://localhost")
    '';

  # Debug interactively with:
  # - nix run .#nixosTests.goupile.driverInteractive -L
  # - run_tests()
  # ssh -o User=root vsock%3 (can also do vsock/3, but % works with scp etc.)
  interactive.sshBackdoor.enable = true;

  interactive.nodes.machine =
    { config, ... }:
    let
      port = config.services.goupile.settings.HTTP.Port;
    in
    {
      virtualisation.forwardPorts = map (port: {
        from = "host";
        host.port = port;
        guest.port = port;
      }) [ port ];

      # forwarded ports need to be accessible
      networking.firewall.allowedTCPPorts = [ port ];

      virtualisation.graphics = false;
    };

  meta.maintainers = lib.teams.ngi.members;
}