summary refs log tree commit diff
path: root/nixos/modules/services/misc/redmine.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/misc/redmine.nix')
-rw-r--r--nixos/modules/services/misc/redmine.nix294
1 files changed, 152 insertions, 142 deletions
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 9a9424449f8..f763ba21d0b 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -1,121 +1,124 @@
 { config, lib, pkgs, ... }:
 
-# TODO: support non-postgresql
-
 with lib;
 
 let
   cfg = config.services.redmine;
 
-  ruby = pkgs.ruby;
+  bundle = "${pkgs.redmine}/share/redmine/bin/bundle";
 
-  databaseYml = ''
+  databaseYml = pkgs.writeText "database.yml" ''
     production:
-      adapter: postgresql
-      database: ${cfg.databaseName}
-      host: ${cfg.databaseHost}
-      password: ${cfg.databasePassword}
-      username: ${cfg.databaseUsername}
-      encoding: utf8
+      adapter: ${cfg.database.type}
+      database: ${cfg.database.name}
+      host: ${cfg.database.host}
+      port: ${toString cfg.database.port}
+      username: ${cfg.database.user}
+      password: #dbpass#
   '';
 
-  configurationYml = ''
+  configurationYml = pkgs.writeText "configuration.yml" ''
     default:
-      # Absolute path to the directory where attachments are stored.
-      # The default is the 'files' directory in your Redmine instance.
-      # Your Redmine instance needs to have write permission on this
-      # directory.
-      # Examples:
-      # attachments_storage_path: /var/redmine/files
-      # attachments_storage_path: D:/redmine/files
-      attachments_storage_path: ${cfg.stateDir}/files
-
-      # Absolute path to the SCM commands errors (stderr) log file.
-      # The default is to log in the 'log' directory of your Redmine instance.
-      # Example:
-      # scm_stderr_log_file: /var/log/redmine_scm_stderr.log
-      scm_stderr_log_file: ${cfg.stateDir}/redmine_scm_stderr.log
-
-      ${cfg.extraConfig}
+      scm_subversion_command: ${pkgs.subversion}/bin/svn
+      scm_mercurial_command: ${pkgs.mercurial}/bin/hg
+      scm_git_command: ${pkgs.gitAndTools.git}/bin/git
+      scm_cvs_command: ${pkgs.cvs}/bin/cvs
+      scm_bazaar_command: ${pkgs.bazaar}/bin/bzr
+      scm_darcs_command: ${pkgs.darcs}/bin/darcs
+
+    ${cfg.extraConfig}
   '';
 
-  unpackTheme = unpack "theme";
-  unpackPlugin = unpack "plugin";
-  unpack = id: (name: source:
-    pkgs.stdenv.mkDerivation {
-      name = "redmine-${id}-${name}";
-      buildInputs = [ pkgs.unzip ];
-      buildCommand = ''
-        mkdir -p $out
-        cd $out
-        unpackFile ${source}
-      '';
-    });
-
-in {
+in
 
+{
   options = {
     services.redmine = {
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = ''
-          Enable the redmine service.
-        '';
+        description = "Enable the Redmine service.";
       };
 
-      stateDir = mkOption {
+      user = mkOption {
         type = types.str;
-        default = "/var/redmine";
-        description = "The state directory, logs and plugins are stored here";
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = "Extra configuration in configuration.yml";
-      };
-
-      themes = mkOption {
-        type = types.attrsOf types.path;
-        default = {};
-        description = "Set of themes";
+        default = "redmine";
+        description = "User under which Redmine is ran.";
       };
 
-      plugins = mkOption {
-        type = types.attrsOf types.path;
-        default = {};
-        description = "Set of plugins";
+      group = mkOption {
+        type = types.str;
+        default = "redmine";
+        description = "Group under which Redmine is ran.";
       };
 
-      #databaseType = mkOption {
-      #  type = types.str;
-      #  default = "postgresql";
-      #  description = "Type of database";
-      #};
-
-      databaseHost = mkOption {
+      stateDir = mkOption {
         type = types.str;
-        default = "127.0.0.1";
-        description = "Database hostname";
+        default = "/var/lib/redmine";
+        description = "The state directory, logs and plugins are stored here.";
       };
 
-      databasePassword = mkOption {
-        type = types.str;
+      extraConfig = mkOption {
+        type = types.lines;
         default = "";
-        description = "Database user password";
-      };
+        description = ''
+          Extra configuration in configuration.yml.
 
-      databaseName = mkOption {
-        type = types.str;
-        default = "redmine";
-        description = "Database name";
+          See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration
+        '';
       };
 
-      databaseUsername = mkOption {
-        type = types.str;
-        default = "redmine";
-        description = "Database user";
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql2" "postgresql" ];
+          example = "postgresql";
+          default = "mysql2";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 3306;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "redmine";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "redmine";
+          description = "Database user.";
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            The password corresponding to <option>database.user</option>.
+            Warning: this is stored in cleartext in the Nix store!
+            Use <option>database.passwordFile</option> instead.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/redmine-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
       };
     };
   };
@@ -123,99 +126,106 @@ in {
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.databasePassword != "";
-        message = "services.redmine.databasePassword must be set";
+      { assertion = cfg.database.passwordFile != null || cfg.database.password != "";
+        message = "either services.redmine.database.passwordFile or services.redmine.database.password must be set";
       }
     ];
 
-    users.users = [
-      { name = "redmine";
-        group = "redmine";
-        uid = config.ids.uids.redmine;
-      } ];
-
-    users.groups = [
-      { name = "redmine";
-        gid = config.ids.gids.redmine;
-      } ];
+    environment.systemPackages = [ pkgs.redmine ];
 
     systemd.services.redmine = {
-      after = [ "network.target" "postgresql.service" ];
+      after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ];
       wantedBy = [ "multi-user.target" ];
+      environment.HOME = "${pkgs.redmine}/share/redmine";
       environment.RAILS_ENV = "production";
-      environment.RAILS_ETC = "${cfg.stateDir}/config";
-      environment.RAILS_LOG = "${cfg.stateDir}/log";
-      environment.RAILS_VAR = "${cfg.stateDir}/var";
       environment.RAILS_CACHE = "${cfg.stateDir}/cache";
-      environment.RAILS_PLUGINS = "${cfg.stateDir}/plugins";
-      environment.RAILS_PUBLIC = "${cfg.stateDir}/public";
-      environment.RAILS_TMP = "${cfg.stateDir}/tmp";
-      environment.SCHEMA = "${cfg.stateDir}/cache/schema.db";
-      environment.HOME = "${pkgs.redmine}/share/redmine";
       environment.REDMINE_LANG = "en";
-      environment.GEM_HOME = "${pkgs.redmine}/share/redmine/vendor/bundle/ruby/1.9.1";
-      environment.GEM_PATH = "${pkgs.bundler}/${pkgs.bundler.ruby.gemPath}";
+      environment.SCHEMA = "${cfg.stateDir}/cache/schema.db";
       path = with pkgs; [
         imagemagickBig
-        subversion
-        mercurial
-        cvs
-        config.services.postgresql.package
         bazaar
+        cvs
+        darcs
         gitAndTools.git
-        # once we build binaries for darc enable it
-        #darcs
+        mercurial
+        subversion
       ];
       preStart = ''
-        # TODO: use env vars
-        for i in plugins public/plugin_assets db files log config cache var/files tmp; do
+        # start with a fresh config directory every time
+        rm -rf ${cfg.stateDir}/config
+        cp -r ${pkgs.redmine}/share/redmine/config.dist ${cfg.stateDir}/config
+
+        # create the basic state directory layout pkgs.redmine expects
+        mkdir -p /run/redmine
+
+        for i in config files log plugins tmp; do
           mkdir -p ${cfg.stateDir}/$i
+          ln -fs ${cfg.stateDir}/$i /run/redmine/$i
         done
 
-        chown -R redmine:redmine ${cfg.stateDir}
-        chmod -R 755 ${cfg.stateDir}
+        # ensure cache directory exists for db:migrate command
+        mkdir -p ${cfg.stateDir}/cache
 
-        rm -rf ${cfg.stateDir}/public/*
-        cp -R ${pkgs.redmine}/share/redmine/public/* ${cfg.stateDir}/public/
-        for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do
-          ln -fs $theme/* ${cfg.stateDir}/public/themes/
-        done
+        # link in the application configuration
+        ln -fs ${configurationYml} ${cfg.stateDir}/config/configuration.yml
 
-        rm -rf ${cfg.stateDir}/plugins/*
-        for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do
-          ln -fs $plugin/* ${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-}
-        done
+        chmod -R ug+rwX,o-rwx+x ${cfg.stateDir}/
 
-        ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.stateDir}/config/database.yml
-        ln -fs ${pkgs.writeText "configuration.yml" configurationYml} ${cfg.stateDir}/config/configuration.yml
+        # handle database.passwordFile
+        DBPASS=$(head -n1 ${cfg.database.passwordFile})
+        cp -f ${databaseYml} ${cfg.stateDir}/config/database.yml
+        sed -e "s,#dbpass#,$DBPASS,g" -i ${cfg.stateDir}/config/database.yml
+        chmod 440 ${cfg.stateDir}/config/database.yml
 
-        if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
-          if ! test -e "${cfg.stateDir}/db-created"; then
-            psql postgres -c "CREATE ROLE redmine WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.databasePassword}'"
-            ${config.services.postgresql.package}/bin/createdb --owner redmine redmine || true
-            touch "${cfg.stateDir}/db-created"
-          fi
+        # generate a secret token if required
+        if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then
+          ${bundle} exec rake generate_secret_token
+          chmod 440 ${cfg.stateDir}/config/initializers/secret_token.rb
         fi
 
-        cd ${pkgs.redmine}/share/redmine/
-        ${ruby}/bin/rake db:migrate
-        ${ruby}/bin/rake redmine:plugins:migrate
-        ${ruby}/bin/rake redmine:load_default_data
-        ${ruby}/bin/rake generate_secret_token
+        # ensure everything is owned by ${cfg.user}
+        chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+
+        ${bundle} exec rake db:migrate
+        ${bundle} exec rake redmine:load_default_data
       '';
 
       serviceConfig = {
         PermissionsStartOnly = true; # preStart must be run as root
         Type = "simple";
-        User = "redmine";
-        Group = "redmine";
+        User = cfg.user;
+        Group = cfg.group;
         TimeoutSec = "300";
         WorkingDirectory = "${pkgs.redmine}/share/redmine";
-        ExecStart="${ruby}/bin/ruby ${pkgs.redmine}/share/redmine/script/rails server webrick -e production -P ${cfg.stateDir}/redmine.pid";
+        ExecStart="${bundle} exec rails server webrick -e production -P ${cfg.stateDir}/redmine.pid";
       };
 
     };
 
+    users.extraUsers = optionalAttrs (cfg.user == "redmine") (singleton
+      { name = "redmine";
+        group = cfg.group;
+        home = cfg.stateDir;
+        createHome = true;
+        uid = config.ids.uids.redmine;
+      });
+
+    users.extraGroups = optionalAttrs (cfg.group == "redmine") (singleton
+      { name = "redmine";
+        gid = config.ids.gids.redmine;
+      });
+
+    warnings = optional (cfg.database.password != "")
+      ''config.services.redmine.database.password will be stored as plaintext
+      in the Nix store. Use database.passwordFile instead.'';
+
+    # Create database passwordFile default when password is configured.
+    services.redmine.database.passwordFile =
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "redmine-database-password";
+        text = cfg.database.password;
+      })));
+
   };
 
 }