From 1cb5b509f1a78ed7a38a327fb122abdf971c1222 Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Wed, 10 Oct 2018 21:04:08 -0400 Subject: redmine: refactor, cleanup, bug fix, and add functionality - added package option to specify which version of redmine - added themes option back in to allow specifying redmine themes - added plugins option back in to allow specifying redmine plugins - added database.socket option to allow mysql unix socket authentication - added port option to allow specifying the port rails runs on - cleaned up Gemfile so it is much less hacky - switched to ruby version 2.4 by default as suggested by documentation http://www.redmine.org/projects/redmine/wiki/redmineinstall#Installing-Redmine - fixed an annoyance (bug) in the service causing recursive symlinks - fixed ownership bug on log files generated by redmine - updates reflecting renames in nixos options - added a nixos test --- nixos/modules/services/misc/redmine.nix | 152 +++++++++++++++++++++++++++----- nixos/tests/redmine.nix | 40 +++++++++ 2 files changed, 171 insertions(+), 21 deletions(-) create mode 100644 nixos/tests/redmine.nix (limited to 'nixos') diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix index f763ba21d0b2..c25901708e01 100644 --- a/nixos/modules/services/misc/redmine.nix +++ b/nixos/modules/services/misc/redmine.nix @@ -5,7 +5,7 @@ with lib; let cfg = config.services.redmine; - bundle = "${pkgs.redmine}/share/redmine/bin/bundle"; + bundle = "${cfg.package}/share/redmine/bin/bundle"; databaseYml = pkgs.writeText "database.yml" '' production: @@ -15,6 +15,7 @@ let port: ${toString cfg.database.port} username: ${cfg.database.user} password: #dbpass# + ${optionalString (cfg.database.socket != null) "socket: ${cfg.database.socket}"} ''; configurationYml = pkgs.writeText "configuration.yml" '' @@ -29,6 +30,19 @@ let ${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 { @@ -40,6 +54,14 @@ in description = "Enable the Redmine service."; }; + package = mkOption { + type = types.package; + default = pkgs.redmine; + defaultText = "pkgs.redmine"; + description = "Which Redmine package to use."; + example = "pkgs.redmine.override { ruby = pkgs.ruby_2_3; }"; + }; + user = mkOption { type = types.str; default = "redmine"; @@ -52,6 +74,12 @@ in description = "Group under which Redmine is ran."; }; + port = mkOption { + type = types.int; + default = 3000; + description = "Port on which Redmine is ran."; + }; + stateDir = mkOption { type = types.str; default = "/var/lib/redmine"; @@ -66,6 +94,41 @@ in See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration ''; + example = literalExample '' + email_delivery: + delivery_method: smtp + smtp_settings: + address: mail.example.com + port: 25 + ''; + }; + + themes = mkOption { + type = types.attrsOf types.path; + default = {}; + description = "Set of themes."; + example = literalExample '' + { + dkuk-redmine_alex_skin = builtins.fetchurl { + url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip; + sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl"; + }; + } + ''; + }; + + plugins = mkOption { + type = types.attrsOf types.path; + default = {}; + description = "Set of plugins."; + example = literalExample '' + { + redmine_env_auth = builtins.fetchurl { + url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip; + sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak"; + }; + } + ''; }; database = { @@ -78,7 +141,7 @@ in host = mkOption { type = types.str; - default = "127.0.0.1"; + default = (if cfg.database.socket != null then "localhost" else "127.0.0.1"); description = "Database host address."; }; @@ -119,6 +182,13 @@ in . ''; }; + + socket = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/mysqld/mysqld.sock"; + description = "Path to the unix socket file to use for authentication."; + }; }; }; }; @@ -126,17 +196,20 @@ in config = mkIf cfg.enable { assertions = [ - { assertion = cfg.database.passwordFile != null || cfg.database.password != ""; - message = "either services.redmine.database.passwordFile or services.redmine.database.password must be set"; + { assertion = cfg.database.passwordFile != null || cfg.database.password != "" || cfg.database.socket != null; + message = "one of services.redmine.database.socket, services.redmine.database.passwordFile, or services.redmine.database.password must be set"; + } + { assertion = cfg.database.socket != null -> (cfg.database.type == "mysql2"); + message = "Socket authentication is only available for the mysql2 database type"; } ]; - environment.systemPackages = [ pkgs.redmine ]; + environment.systemPackages = [ cfg.package ]; systemd.services.redmine = { 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.HOME = "${cfg.package}/share/redmine"; environment.RAILS_ENV = "production"; environment.RAILS_CACHE = "${cfg.stateDir}/cache"; environment.REDMINE_LANG = "en"; @@ -151,43 +224,80 @@ in subversion ]; preStart = '' - # start with a fresh config directory every time - rm -rf ${cfg.stateDir}/config - cp -r ${pkgs.redmine}/share/redmine/config.dist ${cfg.stateDir}/config + # ensure cache directory exists for db:migrate command + mkdir -p ${cfg.stateDir}/cache - # create the basic state directory layout pkgs.redmine expects - mkdir -p /run/redmine + # create the basic directory layout the redmine package expects + mkdir -p /run/redmine/public for i in config files log plugins tmp; do mkdir -p ${cfg.stateDir}/$i - ln -fs ${cfg.stateDir}/$i /run/redmine/$i + ln -fs ${cfg.stateDir}/$i /run/redmine/ done - # ensure cache directory exists for db:migrate command - mkdir -p ${cfg.stateDir}/cache + for i in plugin_assets themes; do + mkdir -p ${cfg.stateDir}/public/$i + ln -fs ${cfg.stateDir}/public/$i /run/redmine/public/ + done + + + # start with a fresh config directory + # the config directory is copied instead of linked as some mutable data is stored in there + rm -rf ${cfg.stateDir}/config/* + cp -r ${cfg.package}/share/redmine/config.dist/* ${cfg.stateDir}/config/ # link in the application configuration ln -fs ${configurationYml} ${cfg.stateDir}/config/configuration.yml + + # link in all user specified themes + rm -rf ${cfg.stateDir}/public/themes/* + for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do + ln -fs $theme/* ${cfg.stateDir}/public/themes/ + done + + # link in redmine provided themes + ln -sf ${cfg.package}/share/redmine/public/themes.dist/* ${cfg.stateDir}/public/themes/ + + + # link in all user specified plugins + rm -rf ${cfg.stateDir}/plugins/* + for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do + ln -fs $plugin/* ${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-} + done + + + # ensure correct permissions for most files chmod -R ug+rwX,o-rwx+x ${cfg.stateDir}/ - # handle database.passwordFile + + # handle database.passwordFile & permissions 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 + # 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 + # 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 + + # execute redmine required commands prior to starting the application + # NOTE: su required in case using mysql socket authentication + /run/wrappers/bin/su -s ${pkgs.bash}/bin/bash -m -l redmine -c '${bundle} exec rake db:migrate' + /run/wrappers/bin/su -s ${pkgs.bash}/bin/bash -m -l redmine -c '${bundle} exec rake redmine:load_default_data' + + + # log files don't exist until after first command has been executed + # correct ownership of files generated by calling exec rake ... + chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}/log ''; serviceConfig = { @@ -196,13 +306,13 @@ in User = cfg.user; Group = cfg.group; TimeoutSec = "300"; - WorkingDirectory = "${pkgs.redmine}/share/redmine"; - ExecStart="${bundle} exec rails server webrick -e production -P ${cfg.stateDir}/redmine.pid"; + WorkingDirectory = "${cfg.package}/share/redmine"; + ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P ${cfg.stateDir}/redmine.pid"; }; }; - users.extraUsers = optionalAttrs (cfg.user == "redmine") (singleton + users.users = optionalAttrs (cfg.user == "redmine") (singleton { name = "redmine"; group = cfg.group; home = cfg.stateDir; @@ -210,7 +320,7 @@ in uid = config.ids.uids.redmine; }); - users.extraGroups = optionalAttrs (cfg.group == "redmine") (singleton + users.groups = optionalAttrs (cfg.group == "redmine") (singleton { name = "redmine"; gid = config.ids.gids.redmine; }); diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix new file mode 100644 index 000000000000..c1cb15f8288f --- /dev/null +++ b/nixos/tests/redmine.nix @@ -0,0 +1,40 @@ +import ./make-test.nix ({ pkgs, lib, ... }: +{ + name = "redmine"; + meta.maintainers = [ lib.maintainers.aanderse ]; + + machine = + { config, pkgs, ... }: + { services.mysql.enable = true; + services.mysql.package = pkgs.mariadb; + services.mysql.ensureDatabases = [ "redmine" ]; + services.mysql.ensureUsers = [ + { name = "redmine"; + ensurePermissions = { "redmine.*" = "ALL PRIVILEGES"; }; + } + ]; + + services.redmine.enable = true; + services.redmine.database.socket = "/run/mysqld/mysqld.sock"; + services.redmine.plugins = { + redmine_env_auth = builtins.fetchurl { + url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip; + sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak"; + }; + }; + services.redmine.themes = { + dkuk-redmine_alex_skin = builtins.fetchurl { + url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip; + sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl"; + }; + }; + }; + + testScript = '' + startAll; + + $machine->waitForUnit('redmine.service'); + $machine->waitForOpenPort('3000'); + $machine->succeed("curl --fail http://localhost:3000/"); + ''; +}) \ No newline at end of file -- cgit 1.4.1