diff options
author | Franz Pletz <fpletz@fnordicwalking.de> | 2016-01-30 14:47:04 +0100 |
---|---|---|
committer | Franz Pletz <fpletz@fnordicwalking.de> | 2016-02-26 07:08:31 +0100 |
commit | bcfa59bf822dc696e963d7abccfdff2e58e70525 (patch) | |
tree | b57383fd52e6ac6112f100ca04040c23efa87fa3 /nixos | |
parent | 30891166be9156c65a50c52cd08f3c7a0f5492da (diff) | |
download | nixlib-bcfa59bf822dc696e963d7abccfdff2e58e70525.tar nixlib-bcfa59bf822dc696e963d7abccfdff2e58e70525.tar.gz nixlib-bcfa59bf822dc696e963d7abccfdff2e58e70525.tar.bz2 nixlib-bcfa59bf822dc696e963d7abccfdff2e58e70525.tar.lz nixlib-bcfa59bf822dc696e963d7abccfdff2e58e70525.tar.xz nixlib-bcfa59bf822dc696e963d7abccfdff2e58e70525.tar.zst nixlib-bcfa59bf822dc696e963d7abccfdff2e58e70525.zip |
gitlab: 8.0.5 -> 8.5.0, service improvements
Updates gitlab to the current stable version and fixes a lot of features that were broken, at least with the current version and our configuration. Quite a lot of sweat and tears has gone into testing nearly all features and reading/patching the Gitlab source as we're about to deploy gitlab for our whole company. Things to note: * The gitlab config is now written as a nix attribute set and will be converted to JSON. Gitlab uses YAML but JSON is a subset of YAML. The `extraConfig` opition is also an attribute set that will be merged with the default config. This way *all* Gitlab options are supported. * Some paths like uploads and configs are hardcoded in rails (at least after my study of the Gitlab source). This is why they are linked from the Gitlab root to /run/gitlab and then linked to the configurable `statePath`. * Backup & restore should work out of the box from another Gitlab instance. * gitlab-git-http-server has been replaced by gitlab-workhorse upstream. Push & pull over HTTPS works perfectly. Communication to gitlab is done over unix sockets. An HTTP server is required to proxy requests to gitlab-workhorse over another unix socket at `/run/gitlab/gitlab-workhorse.socket`. * The user & group running gitlab are now configurable. These can even be changed for live instances. * The initial email address & password of the root user can be configured. Fixes #8598.
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/modules/rename.nix | 3 | ||||
-rw-r--r-- | nixos/modules/services/misc/gitlab.nix | 365 |
2 files changed, 244 insertions, 124 deletions
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 6d5ee6fc84e9..85435884b199 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -28,6 +28,9 @@ with lib; (mkRenamedOptionModule [ "services" "subsonic" "host" ] [ "services" "subsonic" "listenAddress" ]) (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ]) + (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ]) + (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ]) + # Old Grub-related options. (mkRenamedOptionModule [ "boot" "initrd" "extraKernelModules" ] [ "boot" "initrd" "kernelModules" ]) (mkRenamedOptionModule [ "boot" "extraKernelParams" ] [ "boot" "kernelParams" ]) diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 949357ab20f4..074f1a040b8b 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -21,14 +21,15 @@ let username: ${cfg.databaseUsername} encoding: utf8 ''; + gitlabShellYml = '' - user: gitlab - gitlab_url: "http://${cfg.host}:${toString cfg.port}/" + user: ${cfg.user} + gitlab_url: "http://localhost:8080/" http_settings: self_signed_cert: false - repos_path: "${cfg.stateDir}/repositories" - secret_file: "${cfg.stateDir}/config/gitlab_shell_secret" - log_file: "${cfg.stateDir}/log/gitlab-shell.log" + repos_path: "${cfg.statePath}/repositories" + secret_file: "${cfg.statePath}/config/gitlab_shell_secret" + log_file: "${cfg.statePath}/log/gitlab-shell.log" redis: bin: ${pkgs.redis}/bin/redis-cli host: 127.0.0.1 @@ -37,33 +38,101 @@ let namespace: resque:gitlab ''; + gitlabConfig = { + production = flip recursiveUpdate cfg.extraConfig { + gitlab = { + host = cfg.host; + port = cfg.port; + https = cfg.https; + user = cfg.user; + email_enabled = true; + email_display_name = "GitLab"; + email_reply_to = "noreply@localhost"; + default_theme = 2; + default_projects_features = { + issues = true; + merge_requests = true; + wiki = false; + snippets = false; + builds = true; + }; + }; + artifacts = { + enabled = true; + }; + lfs = { + enabled = true; + }; + gravatar = { + enabled = true; + }; + cron_jobs = { + stuck_ci_builds_worker = { + cron = "0 0 * * *"; + }; + }; + gitlab_ci = { + builds_path = "${cfg.statePath}/builds"; + }; + ldap = { + enabled = false; + }; + omniauth = { + enabled = false; + }; + shared = { + path = "${cfg.statePath}/shared"; + }; + backup = { + path = "${cfg.backupPath}"; + }; + gitlab_shell = { + path = "${pkgs.gitlab-shell}"; + repos_path = "${cfg.statePath}/repositories"; + hooks_path = "${cfg.statePath}/shell/hooks"; + secret_file = "${cfg.statePath}/config/gitlab_shell_secret"; + upload_pack = true; + receive_pack = true; + }; + git = { + bin_path = "git"; + max_size = 20971520; # 20MB + timeout = 10; + }; + extra = {}; + }; + }; + + gitlabEnv = { + HOME = "${cfg.statePath}/home"; + GEM_HOME = gemHome; + BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile"; + UNICORN_PATH = "${cfg.statePath}/"; + GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/"; + GITLAB_STATE_PATH = "${cfg.statePath}"; + GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads"; + GITLAB_LOG_PATH = "${cfg.statePath}/log"; + GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}"; + GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml"; + GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret"; + GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks"; + RAILS_ENV = "production"; + }; + unicornConfig = builtins.readFile ./defaultUnicornConfig.rb; gitlab-runner = pkgs.stdenv.mkDerivation rec { name = "gitlab-runner"; - buildInputs = [ pkgs.gitlab pkgs.bundler pkgs.makeWrapper ]; + buildInputs = with pkgs; [ gitlab bundler makeWrapper ]; phases = "installPhase fixupPhase"; buildPhase = ""; installPhase = '' mkdir -p $out/bin - makeWrapper ${bundler}/bin/bundle $out/bin/gitlab-runner\ - --set RAKEOPT '"-f ${pkgs.gitlab}/share/gitlab/Rakefile"'\ - --set GEM_HOME '${gemHome}'\ - --set UNICORN_PATH "${cfg.stateDir}/"\ - --set GITLAB_PATH "${pkgs.gitlab}/share/gitlab/"\ - --set GITLAB_APPLICATION_LOG_PATH "${cfg.stateDir}/log/application.log"\ - --set GITLAB_SATELLITES_PATH "${cfg.stateDir}/satellites"\ - --set GITLAB_SHELL_PATH "${pkgs.gitlab-shell}"\ - --set GITLAB_REPOSITORIES_PATH "${cfg.stateDir}/repositories"\ - --set GITLAB_SHELL_HOOKS_PATH "${cfg.stateDir}/shell/hooks"\ - --set BUNDLE_GEMFILE "${pkgs.gitlab}/share/gitlab/Gemfile"\ - --set GITLAB_EMAIL_FROM "${cfg.emailFrom}"\ - --set GITLAB_SHELL_CONFIG_PATH "${cfg.stateDir}/shell/config.yml"\ - --set GITLAB_SHELL_SECRET_PATH "${cfg.stateDir}/config/gitlab_shell_secret"\ - --set GITLAB_HOST "${cfg.host}"\ - --set GITLAB_PORT "${toString cfg.port}"\ - --set GITLAB_BACKUP_PATH "${cfg.backupPath}"\ - --set RAILS_ENV "production" + makeWrapper ${bundler}/bin/bundle $out/bin/gitlab-runner \ + ${concatStrings (mapAttrsToList (name: value: "--set ${name} '\"${value}\"' ") gitlabEnv)} \ + --set GITLAB_CONFIG_PATH '"${cfg.statePath}/config"' \ + --set PATH '"${pkgs.nodejs}/bin:${pkgs.gzip}/bin:${config.services.postgresql.package}/bin:$PATH"' \ + --set RAKEOPT '"-f ${pkgs.gitlab}/share/gitlab/Rakefile"' ''; }; @@ -79,13 +148,7 @@ in { ''; }; - satelliteDir = mkOption { - type = types.str; - default = "/var/gitlab/git-satellites"; - description = "Gitlab directory to store checked out git trees requires for operation."; - }; - - stateDir = mkOption { + statePath = mkOption { type = types.str; default = "/var/gitlab/state"; description = "Gitlab state directory, logs are stored here."; @@ -93,7 +156,7 @@ in { backupPath = mkOption { type = types.str; - default = cfg.stateDir + "/backup"; + default = cfg.statePath + "/backup"; description = "Gitlab path for backups."; }; @@ -136,7 +199,60 @@ in { port = mkOption { type = types.int; default = 8080; - description = "Gitlab server listening port."; + description = '' + Gitlab server port for copy-paste URLs, e.g. 80 or 443 if you're + service over https. + ''; + }; + + https = mkOption { + type = types.bool; + default = false; + description = "Whether gitlab prints URLs with https as scheme."; + }; + + user = mkOption { + type = types.str; + default = "gitlab"; + description = "User to run gitlab and all related services."; + }; + + group = mkOption { + type = types.str; + default = "gitlab"; + description = "Group to run gitlab and all related services."; + }; + + initialRootEmail = mkOption { + type = types.str; + default = "admin@local.host"; + description = '' + Initial email address of the root account if this is a new install. + ''; + }; + + initialRootPassword = mkOption { + type = types.str; + default = "UseNixOS!"; + description = '' + Initial password of the root account if this is a new install. + ''; + }; + + extraConfig = mkOption { + type = types.attrs; + default = ""; + example = { + gitlab = { + default_projects_features = { + builds = false; + }; + }; + }; + description = '' + Extra options to be merged into config/gitlab.yml as nix + attribute set. + ''; }; }; }; @@ -159,39 +275,24 @@ in { services.postfix.enable = mkDefault true; users.extraUsers = [ - { name = "gitlab"; - group = "gitlab"; - home = "${cfg.stateDir}/home"; + { name = cfg.user; + group = cfg.group; + home = "${cfg.statePath}/home"; shell = "${pkgs.bash}/bin/bash"; uid = config.ids.uids.gitlab; - } ]; + } + ]; users.extraGroups = [ - { name = "gitlab"; + { name = cfg.group; gid = config.ids.gids.gitlab; - } ]; + } + ]; systemd.services.gitlab-sidekiq = { after = [ "network.target" "redis.service" ]; wantedBy = [ "multi-user.target" ]; - environment.HOME = "${cfg.stateDir}/home"; - environment.GEM_HOME = gemHome; - environment.UNICORN_PATH = "${cfg.stateDir}/"; - environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/"; - environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log"; - environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites"; - environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}"; - environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories"; - environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks"; - environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile"; - environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}"; - environment.GITLAB_SHELL_CONFIG_PATH = "${cfg.stateDir}/shell/config.yml"; - environment.GITLAB_SHELL_SECRET_PATH = "${cfg.stateDir}/config/gitlab_shell_secret"; - environment.GITLAB_HOST = "${cfg.host}"; - environment.GITLAB_PORT = "${toString cfg.port}"; - environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}"; - environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}"; - environment.RAILS_ENV = "production"; + environment = gitlabEnv; path = with pkgs; [ config.services.postgresql.package gitAndTools.git @@ -201,116 +302,132 @@ in { ]; serviceConfig = { Type = "simple"; - User = "gitlab"; - Group = "gitlab"; + User = cfg.user; + Group = cfg.group; TimeoutSec = "300"; WorkingDirectory = "${pkgs.gitlab}/share/gitlab"; - ExecStart="${bundler}/bin/bundle exec \"sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e production -P ${cfg.stateDir}/tmp/sidekiq.pid\""; + ExecStart="${bundler}/bin/bundle exec \"sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e production -P ${cfg.statePath}/tmp/sidekiq.pid\""; }; }; - systemd.services.gitlab-git-http-server = { + systemd.services.gitlab-workhorse = { after = [ "network.target" "gitlab.service" ]; wantedBy = [ "multi-user.target" ]; - environment.HOME = "${cfg.stateDir}/home"; + environment.HOME = gitlabEnv.HOME; + environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH; path = with pkgs; [ gitAndTools.git openssh ]; + preStart = '' + mkdir -p /run/gitlab + chown ${cfg.user}:${cfg.group} /run/gitlab + ''; serviceConfig = { + PermissionsStartOnly = true; # preStart must be run as root Type = "simple"; - User = "gitlab"; - Group = "gitlab"; + User = cfg.user; + Group = cfg.group; TimeoutSec = "300"; - ExecStart = "${pkgs.gitlab-git-http-server}/bin/gitlab-git-http-server -listenUmask 0 -listenNetwork unix -listenAddr ${cfg.stateDir}/tmp/sockets/gitlab-git-http-server.socket -authBackend http://localhost:8080 ${cfg.stateDir}/repositories"; + ExecStart = + "${pkgs.gitlab-workhorse}/bin/gitlab-workhorse " + + "-listenUmask 0 " + + "-listenNetwork unix " + + "-listenAddr /run/gitlab/gitlab-workhorse.socket " + + "-authSocket ${cfg.statePath}/tmp/sockets/gitlab.socket " + + "-documentRoot ${pkgs.gitlab}/share/gitlab/public"; }; }; systemd.services.gitlab = { after = [ "network.target" "postgresql.service" "redis.service" ]; wantedBy = [ "multi-user.target" ]; - environment.HOME = "${cfg.stateDir}/home"; - environment.GEM_HOME = gemHome; - environment.UNICORN_PATH = "${cfg.stateDir}/"; - environment.GITLAB_PATH = "${pkgs.gitlab}/share/gitlab/"; - environment.GITLAB_APPLICATION_LOG_PATH = "${cfg.stateDir}/log/application.log"; - environment.GITLAB_SATELLITES_PATH = "${cfg.stateDir}/satellites"; - environment.GITLAB_SHELL_PATH = "${pkgs.gitlab-shell}"; - environment.GITLAB_SHELL_CONFIG_PATH = "${cfg.stateDir}/shell/config.yml"; - environment.GITLAB_SHELL_SECRET_PATH = "${cfg.stateDir}/config/gitlab_shell_secret"; - environment.GITLAB_REPOSITORIES_PATH = "${cfg.stateDir}/repositories"; - environment.GITLAB_SHELL_HOOKS_PATH = "${cfg.stateDir}/shell/hooks"; - environment.BUNDLE_GEMFILE = "${pkgs.gitlab}/share/gitlab/Gemfile"; - environment.GITLAB_EMAIL_FROM = "${cfg.emailFrom}"; - environment.GITLAB_HOST = "${cfg.host}"; - environment.GITLAB_PORT = "${toString cfg.port}"; - environment.GITLAB_DATABASE_HOST = "${cfg.databaseHost}"; - environment.GITLAB_DATABASE_PASSWORD = "${cfg.databasePassword}"; - environment.RAILS_ENV = "production"; + environment = gitlabEnv; path = with pkgs; [ config.services.postgresql.package gitAndTools.git - ruby openssh nodejs + sudo ]; preStart = '' - # TODO: use env vars - mkdir -p ${cfg.stateDir} - mkdir -p ${cfg.stateDir}/log - mkdir -p ${cfg.stateDir}/satellites - mkdir -p ${cfg.stateDir}/repositories - mkdir -p ${cfg.stateDir}/shell/hooks - mkdir -p ${cfg.stateDir}/tmp/pids - mkdir -p ${cfg.stateDir}/tmp/sockets - rm -rf ${cfg.stateDir}/config - mkdir -p ${cfg.stateDir}/config - # TODO: What exactly is gitlab-shell doing with the secret? - tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 20 > ${cfg.stateDir}/config/gitlab_shell_secret - mkdir -p ${cfg.stateDir}/home/.ssh - touch ${cfg.stateDir}/home/.ssh/authorized_keys + mkdir -p ${cfg.backupPath} + mkdir -p ${cfg.statePath}/builds + mkdir -p ${cfg.statePath}/repositories + mkdir -p ${gitlabConfig.production.shared.path}/artifacts + mkdir -p ${gitlabConfig.production.shared.path}/lfs-objects + mkdir -p ${cfg.statePath}/log + mkdir -p ${cfg.statePath}/shell + mkdir -p ${cfg.statePath}/tmp/pids + mkdir -p ${cfg.statePath}/tmp/sockets + + rm -rf ${cfg.statePath}/config ${cfg.statePath}/shell/hooks + mkdir -p ${cfg.statePath}/config ${cfg.statePath}/shell - cp -rf ${pkgs.gitlab}/share/gitlab/config ${cfg.stateDir}/ - cp ${pkgs.gitlab}/share/gitlab/VERSION ${cfg.stateDir}/VERSION - - ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.stateDir}/config/database.yml - ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.stateDir}/config/unicorn.rb - - chown -R gitlab:gitlab ${cfg.stateDir}/ - chmod -R 755 ${cfg.stateDir}/ + # TODO: What exactly is gitlab-shell doing with the secret? + tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 20 > ${cfg.statePath}/config/gitlab_shell_secret + + # The uploads directory is hardcoded somewhere deep in rails. It is + # symlinked in the gitlab package to /run/gitlab/uploads to make it + # configurable + mkdir -p /run/gitlab + mkdir -p ${cfg.statePath}/uploads + ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads + chown -R ${cfg.user}:${cfg.group} /run/gitlab + + # Prepare home directory + mkdir -p ${gitlabEnv.HOME}/.ssh + touch ${gitlabEnv.HOME}/.ssh/authorized_keys + chown -R ${cfg.user}:${cfg.group} ${gitlabEnv.HOME}/ + chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME}/ + + cp -rf ${pkgs.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config + ln -sf ${cfg.statePath}/config /run/gitlab/config + cp ${pkgs.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION + + # JSON is a subset of YAML + ln -fs ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml + ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.statePath}/config/database.yml + ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.statePath}/config/unicorn.rb + + chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/ + chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/ + + # Install the shell required to push repositories + ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} "$GITLAB_SHELL_CONFIG_PATH" + ln -fs ${pkgs.gitlab-shell}/hooks "$GITLAB_SHELL_HOOKS_PATH" + ${pkgs.gitlab-shell}/bin/install if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then - if ! test -e "${cfg.stateDir}/db-created"; then - psql postgres -c "CREATE ROLE gitlab WITH LOGIN NOCREATEDB NOCREATEROLE NOCREATEUSER ENCRYPTED PASSWORD '${cfg.databasePassword}'" + if ! test -e "${cfg.statePath}/db-created"; then + psql postgres -c "CREATE ROLE gitlab WITH LOGIN CREATEDB NOCREATEROLE NOCREATEUSER ENCRYPTED PASSWORD '${cfg.databasePassword}'" ${config.services.postgresql.package}/bin/createdb --owner gitlab gitlab || true - touch "${cfg.stateDir}/db-created" + touch "${cfg.statePath}/db-created" - # force=yes disables the manual-interaction yes/no prompt - # which breaks without an stdin. - force=yes ${bundler}/bin/bundle exec rake -f ${pkgs.gitlab}/share/gitlab/Rakefile gitlab:setup RAILS_ENV=production + # The gitlab:setup task is horribly broken somehow, these two tasks will do the same for setting up the initial database + ${gitlab-runner}/bin/gitlab-runner exec rake db:migrate RAILS_ENV=production + ${gitlab-runner}/bin/gitlab-runner exec rake db:seed_fu RAILS_ENV=production \ + GITLAB_ROOT_PASSWORD="${cfg.initialRootPassword}" GITLAB_ROOT_EMAIL="${cfg.initialRootEmail}"; fi fi - ${bundler}/bin/bundle exec rake -f ${pkgs.gitlab}/share/gitlab/Rakefile db:migrate RAILS_ENV=production - # Install the shell required to push repositories - ln -fs ${pkgs.writeText "config.yml" gitlabShellYml} ${cfg.stateDir}/shell/config.yml - export GITLAB_SHELL_CONFIG_PATH=""${cfg.stateDir}/shell/config.yml - ${pkgs.gitlab-shell}/bin/install + # Always do the db migrations just to be sure the database is up-to-date + ${gitlab-runner}/bin/gitlab-runner exec rake db:migrate RAILS_ENV=production - # Change permissions in the last step because some of the - # intermediary scripts like to create directories as root. - chown -R gitlab:gitlab ${cfg.stateDir}/ - chmod -R 755 ${cfg.stateDir}/ + # Change permissions in the last step because some of the + # intermediary scripts like to create directories as root. + chown -R ${cfg.user}:${cfg.group} ${cfg.statePath} + chmod -R u+rwX,go-rwx+X ${cfg.statePath} ''; serviceConfig = { PermissionsStartOnly = true; # preStart must be run as root Type = "simple"; - User = "gitlab"; - Group = "gitlab"; + User = cfg.user; + Group = cfg.group; TimeoutSec = "300"; WorkingDirectory = "${pkgs.gitlab}/share/gitlab"; - ExecStart="${bundler}/bin/bundle exec \"unicorn -c ${cfg.stateDir}/config/unicorn.rb -E production\""; + ExecStart="${bundler}/bin/bundle exec \"unicorn -c ${cfg.statePath}/config/unicorn.rb -E production\""; }; }; |