diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4a847d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/result diff --git a/default.nix b/default.nix index 2739a28..1a5a322 100644 --- a/default.nix +++ b/default.nix @@ -1,7 +1,21 @@ -{ config, lib, pkgs, ...}: +{ pkgs ? import { } +, ... +}: -{ - imports = [ - ./modules - ]; +pkgs.stdenvNoCC.mkDerivation rec { + name = "phoebe-${version}"; + version = "0.1"; + src = ./.; + + phases = + [ "unpackPhase" + "installPhase" + "fixupPhase" + ]; + + installPhase = '' + mkdir -p $out + cp -rp bin modules lib $out/ + chmod 0555 $out/bin/* + ''; } diff --git a/lib/keys.nix b/lib/keys.nix new file mode 100644 index 0000000..361d3e2 --- /dev/null +++ b/lib/keys.nix @@ -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 diff --git a/modules/default.nix b/modules/default.nix index e44fc93..e84f11c 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,8 +1,26 @@ { 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 = [ ./security ./services ]; + + options.phoebe.lib = mkOption { + type = types.attrs; + default = libs; + internal = true; + readOnly = true; + }; } diff --git a/modules/services/databases/postgresql/default.nix b/modules/services/databases/postgresql/default.nix index 63c1d6b..a68db7a 100644 --- a/modules/services/databases/postgresql/default.nix +++ b/modules/services/databases/postgresql/default.nix @@ -6,9 +6,10 @@ with lib; let cfg = config.phoebe.services.postgresql; + plib = config.phoebe.lib; superuser = config.services.postgresql.superUser; 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: account = { name, ... }: { @@ -30,18 +31,10 @@ let A file containing the password of this database user. You'll want to use something like NixOps to get the password file onto the target machine. - ''; - }; - afterServices = mkOption { - type = types.listOf types.str; - default = [ ]; - 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. + If the file looks like it's a NixOps key then the account + creation script will automatically wait for the appropriate + key service to start. ''; }; diff --git a/modules/services/web/rails/default.nix b/modules/services/web/rails/default.nix index 990dc81..bcfc871 100644 --- a/modules/services/web/rails/default.nix +++ b/modules/services/web/rails/default.nix @@ -8,6 +8,7 @@ let ############################################################################## # Save some typing. cfg = config.phoebe.services.rails; + plib = config.phoebe.lib; scripts = import ./scripts.nix { inherit lib pkgs; }; options = import ./options.nix { inherit config lib pkgs; }; @@ -70,10 +71,17 @@ let } // app.environment; 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 (app.database.passwordService != null) app.database.passwordService; + optional localpg "pg-accounts.service" ++ + plib.keyService app.database.passwordFile ++ + plib.keyService app.sourcedFile; preStart = '' # Prepare the config directory: @@ -88,6 +96,11 @@ let mkdir -p ${app.home}/home 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: chown -R rails-${app.name}:rails-${app.name} ${app.home} chmod go+rx $(dirname "${app.home}") @@ -101,6 +114,11 @@ let -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 = { WorkingDirectory = "${app.package}/share/${app.name}"; Restart = "on-failure"; @@ -110,7 +128,6 @@ let User = "rails-${app.name}"; Group = "rails-${app.name}"; UMask = "0077"; - ExecStart = "${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}"; }; }; }; diff --git a/modules/services/web/rails/options.nix b/modules/services/web/rails/options.nix index 3e6de53..cb0eb82 100644 --- a/modules/services/web/rails/options.nix +++ b/modules/services/web/rails/options.nix @@ -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 { type = types.bool; default = true; @@ -112,6 +100,23 @@ let default = { }; 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 = {