mirror of
https://github.com/vale981/phoebe
synced 2025-03-04 17:31:41 -05:00
Automatically depending on NixOps key services, new Rails sourcedFile option
* Services that need password files will automatically depend on the appropriate NixOps key service as necessary. * New `sourcedFile` option for Rails applications to load a Bash script just before starting the Rails service. Useful for setting secret environment variables.
This commit is contained in:
parent
accdc1bf54
commit
193b82189e
7 changed files with 124 additions and 33 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/result
|
22
default.nix
22
default.nix
|
@ -1,7 +1,21 @@
|
||||||
{ config, lib, pkgs, ...}:
|
{ pkgs ? import <nixpkgs> { }
|
||||||
|
, ...
|
||||||
|
}:
|
||||||
|
|
||||||
{
|
pkgs.stdenvNoCC.mkDerivation rec {
|
||||||
imports = [
|
name = "phoebe-${version}";
|
||||||
./modules
|
version = "0.1";
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
phases =
|
||||||
|
[ "unpackPhase"
|
||||||
|
"installPhase"
|
||||||
|
"fixupPhase"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -rp bin modules lib $out/
|
||||||
|
chmod 0555 $out/bin/*
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|
43
lib/keys.nix
Normal file
43
lib/keys.nix
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Functions for working with NixOps keys.
|
||||||
|
{ lib }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
# Where NixOps stores keys:
|
||||||
|
keyDirectory = "/run/keys/";
|
||||||
|
|
||||||
|
# Generate a service name:
|
||||||
|
mkServiceName = path:
|
||||||
|
replaceStrings ["/"] ["-"]
|
||||||
|
(removePrefix keyDirectory path + "-key.service");
|
||||||
|
|
||||||
|
funcs = rec {
|
||||||
|
|
||||||
|
/* Test to see if a file path is a NixOps managed key.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
isKeyFile "/run/keys/foo"
|
||||||
|
=> true
|
||||||
|
isKeyFile "/etc/passwd"
|
||||||
|
=> false
|
||||||
|
*/
|
||||||
|
isKeyFile = path:
|
||||||
|
if path == null
|
||||||
|
then false
|
||||||
|
else hasPrefix keyDirectory path;
|
||||||
|
|
||||||
|
/* Returns an array containing a systemd service name that can be
|
||||||
|
used to add a 'wants' or 'after' entry for a NixOps key.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
keyService "/run/keys/foo"
|
||||||
|
=> ["foo.service"]
|
||||||
|
keyService "/etc/passwd"
|
||||||
|
=> []
|
||||||
|
*/
|
||||||
|
keyService = path: optional (isKeyFile path) (mkServiceName path);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
in funcs
|
|
@ -1,8 +1,26 @@
|
||||||
{ config, lib, pkgs, ...}:
|
{ config, lib, pkgs, ...}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
libFiles = [
|
||||||
|
../lib/keys.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
loadLib = path: import path { inherit lib; };
|
||||||
|
libs = foldr (a: b: recursiveUpdate (loadLib a) b) {} libFiles;
|
||||||
|
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./security
|
./security
|
||||||
./services
|
./services
|
||||||
];
|
];
|
||||||
|
|
||||||
|
options.phoebe.lib = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = libs;
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,10 @@ with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.phoebe.services.postgresql;
|
cfg = config.phoebe.services.postgresql;
|
||||||
|
plib = config.phoebe.lib;
|
||||||
superuser = config.services.postgresql.superUser;
|
superuser = config.services.postgresql.superUser;
|
||||||
create-user = import ./create-user.nix { inherit config lib pkgs; };
|
create-user = import ./create-user.nix { inherit config lib pkgs; };
|
||||||
afterservices = concatMap (a: a.afterServices) (attrValues cfg.accounts);
|
afterservices = concatMap (a: plib.keyService a.passwordFile) (attrValues cfg.accounts);
|
||||||
|
|
||||||
# Per-account options:
|
# Per-account options:
|
||||||
account = { name, ... }: {
|
account = { name, ... }: {
|
||||||
|
@ -30,18 +31,10 @@ let
|
||||||
A file containing the password of this database user.
|
A file containing the password of this database user.
|
||||||
You'll want to use something like NixOps to get the password
|
You'll want to use something like NixOps to get the password
|
||||||
file onto the target machine.
|
file onto the target machine.
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
afterServices = mkOption {
|
If the file looks like it's a NixOps key then the account
|
||||||
type = types.listOf types.str;
|
creation script will automatically wait for the appropriate
|
||||||
default = [ ];
|
key service to start.
|
||||||
example = [ "dbpassword.service" ];
|
|
||||||
description = ''
|
|
||||||
A list of services that need to run before this user account
|
|
||||||
can be created. This is really useful if you are using
|
|
||||||
NixOps to deploy the password file and want to wait for the
|
|
||||||
key to appear in /run/keys.
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ let
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Save some typing.
|
# Save some typing.
|
||||||
cfg = config.phoebe.services.rails;
|
cfg = config.phoebe.services.rails;
|
||||||
|
plib = config.phoebe.lib;
|
||||||
scripts = import ./scripts.nix { inherit lib pkgs; };
|
scripts = import ./scripts.nix { inherit lib pkgs; };
|
||||||
options = import ./options.nix { inherit config lib pkgs; };
|
options = import ./options.nix { inherit config lib pkgs; };
|
||||||
|
|
||||||
|
@ -70,10 +71,17 @@ let
|
||||||
} // app.environment;
|
} // app.environment;
|
||||||
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
wants = optional (app.database.passwordService != null) app.database.passwordService;
|
|
||||||
after = [ "network.target" ] ++
|
wants =
|
||||||
|
plib.keyService app.database.passwordFile ++
|
||||||
|
plib.keyService app.sourcedFile;
|
||||||
|
|
||||||
|
after =
|
||||||
|
[ "network.target" ] ++
|
||||||
optional localpg "postgresql.service" ++
|
optional localpg "postgresql.service" ++
|
||||||
optional (app.database.passwordService != null) app.database.passwordService;
|
optional localpg "pg-accounts.service" ++
|
||||||
|
plib.keyService app.database.passwordFile ++
|
||||||
|
plib.keyService app.sourcedFile;
|
||||||
|
|
||||||
preStart = ''
|
preStart = ''
|
||||||
# Prepare the config directory:
|
# Prepare the config directory:
|
||||||
|
@ -88,6 +96,11 @@ let
|
||||||
mkdir -p ${app.home}/home
|
mkdir -p ${app.home}/home
|
||||||
ln -nfs ${app.package}/share/${app.name} ${app.home}/home/${app.name}
|
ln -nfs ${app.package}/share/${app.name} ${app.home}/home/${app.name}
|
||||||
|
|
||||||
|
# Copy the sourcedFile if necessary:
|
||||||
|
${optionalString (app.sourcedFile != null) ''
|
||||||
|
cp ${app.sourcedFile} ${app.home}/state/sourcedFile.sh
|
||||||
|
''}
|
||||||
|
|
||||||
# Fix permissions:
|
# Fix permissions:
|
||||||
chown -R rails-${app.name}:rails-${app.name} ${app.home}
|
chown -R rails-${app.name}:rails-${app.name} ${app.home}
|
||||||
chmod go+rx $(dirname "${app.home}")
|
chmod go+rx $(dirname "${app.home}")
|
||||||
|
@ -101,6 +114,11 @@ let
|
||||||
-s ${app.home}/state
|
-s ${app.home}/state
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
|
||||||
|
${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}
|
||||||
|
'';
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
WorkingDirectory = "${app.package}/share/${app.name}";
|
WorkingDirectory = "${app.package}/share/${app.name}";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
|
@ -110,7 +128,6 @@ let
|
||||||
User = "rails-${app.name}";
|
User = "rails-${app.name}";
|
||||||
Group = "rails-${app.name}";
|
Group = "rails-${app.name}";
|
||||||
UMask = "0077";
|
UMask = "0077";
|
||||||
ExecStart = "${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,18 +31,6 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
passwordService = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
example = "db-password.service";
|
|
||||||
description = ''
|
|
||||||
A service to wait on before starting the Rails application.
|
|
||||||
This service should provide the password file for the
|
|
||||||
passwordFile option. Useful when deploying passwords with
|
|
||||||
NixOps.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
migrate = mkOption {
|
migrate = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
|
@ -112,6 +100,23 @@ let
|
||||||
default = { };
|
default = { };
|
||||||
description = "Environment variables.";
|
description = "Environment variables.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sourcedFile = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
example = "/run/keys/env.sh";
|
||||||
|
description = ''
|
||||||
|
Bash file to source immediately before running any service
|
||||||
|
command.
|
||||||
|
|
||||||
|
If the file is store under /run/keys the service will wait
|
||||||
|
for the file to become available.
|
||||||
|
|
||||||
|
This option can be used to set environment variables more
|
||||||
|
securely than using the environment option. However, you
|
||||||
|
should really use the Rails secrets system.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue