mirror of
https://github.com/vale981/phoebe
synced 2025-03-04 17:31:41 -05:00
Rails: Rework service execution so `nix-copy-closure' deployments work better
These changes allow service restarts to pick up a new environment (including the correct gemset) when restarted. Also, make migrations its own service that other services need to wait for.
This commit is contained in:
parent
66525b8df1
commit
3399464b17
7 changed files with 101 additions and 30 deletions
52
modules/services/web/rails/build-path.sh
Executable file
52
modules/services/web/rails/build-path.sh
Executable file
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
################################################################################
|
||||
# Build a PATH variable given a nix-store path.
|
||||
set -e
|
||||
set -u
|
||||
|
||||
################################################################################
|
||||
dirs=()
|
||||
|
||||
################################################################################
|
||||
if [ $# -ne 1 ]; then
|
||||
>&2 echo "ERROR: missing store path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
################################################################################
|
||||
function join() {
|
||||
local sep=$1; shift
|
||||
local IFS="$sep";
|
||||
echo "$*";
|
||||
}
|
||||
|
||||
################################################################################
|
||||
function maybe_add_dir() {
|
||||
local path=$1
|
||||
|
||||
if [ -n "$path" ] && [ -d "$path/bin" ]; then
|
||||
dirs+=( "$path/bin" )
|
||||
fi
|
||||
}
|
||||
|
||||
################################################################################
|
||||
function main() {
|
||||
local path=$1
|
||||
path=$(realpath "$path")
|
||||
|
||||
maybe_add_dir "$path"
|
||||
|
||||
for drv in $(nix-store --query --references "$path"); do
|
||||
maybe_add_dir "$drv"
|
||||
done
|
||||
|
||||
if [ "${#dirs[@]}" -gt 0 ]; then
|
||||
echo "export PATH=$(join : "${dirs[@]}"):$PATH"
|
||||
else
|
||||
echo "export PATH=$PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
################################################################################
|
||||
main "$1"
|
|
@ -72,7 +72,6 @@ let
|
|||
group = "rails-${app.name}";
|
||||
shell = "${pkgs.bash}/bin/bash";
|
||||
extraGroups = [ config.services.nginx.group ];
|
||||
packages = funcs.appPath app;
|
||||
};
|
||||
groups."rails-${app.name}" = {};
|
||||
};
|
||||
|
|
|
@ -12,11 +12,6 @@ rec {
|
|||
# Path to where the app is actually installed:
|
||||
appLink = app: "${app.home}/package";
|
||||
|
||||
##############################################################################
|
||||
# Packages to put in the application's PATH. FIXME:
|
||||
# propagatedBuildInputs won't always be set.
|
||||
appPath = app: [ app.package.rubyEnv ] ++ app.package.propagatedBuildInputs;
|
||||
|
||||
##############################################################################
|
||||
# All of the environment variables that a Rails app needs:
|
||||
appEnv = app: {
|
||||
|
|
|
@ -86,6 +86,13 @@ let
|
|||
default = false;
|
||||
description = "Is this the main Rails process?";
|
||||
};
|
||||
|
||||
isMigration = mkOption {
|
||||
internal = true;
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Is this the migration service?";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# Rails user shell profile.
|
||||
if [ -e "$HOME/.path" ]; then
|
||||
. "$HOME/.path"
|
||||
fi
|
||||
|
||||
if [ -e "$HOME/.env" ]; then
|
||||
. "$HOME/.env"
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
substituteAll ${./db-migrate.sh} $out/bin/db-migrate.sh
|
||||
substituteAll ${./build-path.sh} $out/bin/build-path.sh
|
||||
find $out/bin -type f -exec chmod 555 '{}' ';'
|
||||
'';
|
||||
|
||||
|
|
|
@ -23,39 +23,56 @@ let
|
|||
name = "main";
|
||||
schedule = null;
|
||||
isMain = true;
|
||||
isMigration = false;
|
||||
|
||||
script = ''
|
||||
puma -e ${app.railsEnv} -p ${toString app.port}
|
||||
'';
|
||||
};
|
||||
|
||||
##############################################################################
|
||||
# The database migration service:
|
||||
migrationService = app: {
|
||||
name = "migrations";
|
||||
schedule = null;
|
||||
isMain = false;
|
||||
isMigration = true;
|
||||
|
||||
script = ''
|
||||
${scripts.user}/bin/db-migrate.sh \
|
||||
-r ${funcs.appLink app}/share/${app.name} \
|
||||
-s ${app.home}/state
|
||||
'';
|
||||
};
|
||||
|
||||
##############################################################################
|
||||
# Generate a systemd service for a Ruby on Rails application:
|
||||
appService = app: service: {
|
||||
"rails-${app.name}-${service.name}" = {
|
||||
description = "${app.name} (Ruby on Rails) ${service.name}";
|
||||
path = funcs.appPath app;
|
||||
path = with pkgs; [ coreutils nix ];
|
||||
environment = funcs.appEnv app;
|
||||
|
||||
# Only start this service if it isn't scheduled by a timer.
|
||||
partOf = optional (service.schedule == null) "rails-${app.name}.target";
|
||||
wantedBy = optional (service.schedule == null) "rails-${app.name}.target";
|
||||
|
||||
wants =
|
||||
plib.keyService app.database.passwordFile ++
|
||||
plib.keyService app.sourcedFile ++
|
||||
app.afterServices;
|
||||
wants = plib.keyService app.database.passwordFile
|
||||
++ plib.keyService app.sourcedFile
|
||||
++ app.afterServices
|
||||
++ optional (!service.isMigration && app.database.migrate) "rails-${app.name}-migrations"
|
||||
++ optional (!service.isMain && !service.isMigration) "rails-${app.name}-main";
|
||||
|
||||
after =
|
||||
[ "network.target" ] ++
|
||||
optional localpg "postgresql.service" ++
|
||||
optional localpg "pg-accounts.service" ++
|
||||
optional (!service.isMain) "rails-${app.name}-main" ++
|
||||
plib.keyService app.database.passwordFile ++
|
||||
plib.keyService app.sourcedFile ++
|
||||
app.afterServices;
|
||||
after = [ "network.target" ]
|
||||
++ optional localpg "postgresql.service"
|
||||
++ optional localpg "postgres-account-manager.service"
|
||||
++ plib.keyService app.database.passwordFile
|
||||
++ plib.keyService app.sourcedFile
|
||||
++ app.afterServices
|
||||
++ optional (!service.isMigration && app.database.migrate) "rails-${app.name}-migrations"
|
||||
++ optional (!service.isMain && !service.isMigration) "rails-${app.name}-main";
|
||||
|
||||
preStart = optionalString service.isMain ''
|
||||
preStart = optionalString (service.isMain || service.isMigration) ''
|
||||
# Link the package into the application's home directory:
|
||||
if [ ! -e "${funcs.appLink app}" ] || [ "${toString app.deployedExternally}" -ne 1 ]; then
|
||||
ln -nfs "${app.package}" "${funcs.appLink app}"
|
||||
|
@ -73,6 +90,7 @@ let
|
|||
mkdir -p ${app.home}/home
|
||||
ln -nfs ${funcs.appLink app}/share/${app.name} ${app.home}/home/app
|
||||
ln -nfs ${plib.attrsToShellExports "rails-${app.name}-env" (funcs.appEnv app)} ${app.home}/home/.env
|
||||
echo 'eval $(${scripts.user}/bin/build-path.sh "${funcs.appLink app}")' > ${app.home}/home/.path
|
||||
cp ${./profile.sh} ${app.home}/home/.profile
|
||||
chmod 0700 ${app.home}/home/.profile
|
||||
|
||||
|
@ -85,25 +103,19 @@ let
|
|||
chown -R rails-${app.name}:rails-${app.name} ${app.home}
|
||||
chmod go+rx $(dirname "${app.home}") "${app.home}"
|
||||
chmod u+w ${app.home}/db/schema.rb
|
||||
|
||||
'' + optionalString (service.isMain && app.database.migrate) ''
|
||||
# Migrate the database:
|
||||
${pkgs.sudo}/bin/sudo --user=rails-${app.name} --login \
|
||||
${scripts.user}/bin/db-migrate.sh \
|
||||
-r ${funcs.appLink app}/share/${app.name} \
|
||||
-s ${app.home}/state
|
||||
'';
|
||||
|
||||
script = ''
|
||||
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
|
||||
eval $(${scripts.user}/bin/build-path.sh "${funcs.appLink app}")
|
||||
${service.script}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
WorkingDirectory = "-${funcs.appLink app}/share/${app.name}";
|
||||
Restart = "on-failure";
|
||||
Type = if service.isMigration then "oneshot" else "simple";
|
||||
Restart = if service.isMigration then "no" else "on-failure";
|
||||
TimeoutSec = "infinity"; # FIXME: what's a reasonable amount of time?
|
||||
Type = "simple";
|
||||
PermissionsStartOnly = true;
|
||||
User = "rails-${app.name}";
|
||||
Group = "rails-${app.name}";
|
||||
|
@ -128,7 +140,9 @@ let
|
|||
# systemd services.
|
||||
appServices = app:
|
||||
foldr (service: set: recursiveUpdate set (appService app service)) {}
|
||||
( [(mainService app)] ++ attrValues app.services );
|
||||
( singleton (mainService app)
|
||||
++ optional app.database.migrate (migrationService app)
|
||||
++ attrValues app.services );
|
||||
|
||||
##############################################################################
|
||||
# Collect all services and turn them into systemd timers:
|
||||
|
|
Loading…
Add table
Reference in a new issue