Browse Source

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.
pjones/monitoring
Peter J. Jones 5 months ago
parent
commit
193b82189e
Signed by: Peter Jones <pjones@devalot.com> GPG Key ID: 9DAFAA8D01941E49

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
1
+/result

+ 19
- 5
default.nix View File

@@ -1,7 +1,21 @@
1
-{ config, lib, pkgs, ...}:
1
+{ pkgs ? import <nixpkgs> { }
2
+, ...
3
+}:
2 4
 
3
-{
4
-  imports = [
5
-    ./modules
6
-  ];
5
+pkgs.stdenvNoCC.mkDerivation rec {
6
+  name = "phoebe-${version}";
7
+  version = "0.1";
8
+  src = ./.;
9
+
10
+  phases =
11
+   [ "unpackPhase"
12
+     "installPhase"
13
+     "fixupPhase"
14
+   ];
15
+
16
+  installPhase = ''
17
+    mkdir -p $out
18
+    cp -rp bin modules lib $out/
19
+    chmod 0555 $out/bin/*
20
+  '';
7 21
 }

+ 43
- 0
lib/keys.nix View File

@@ -0,0 +1,43 @@
1
+# Functions for working with NixOps keys.
2
+{ lib }:
3
+
4
+with lib;
5
+
6
+let
7
+  # Where NixOps stores keys:
8
+  keyDirectory = "/run/keys/";
9
+
10
+  # Generate a service name:
11
+  mkServiceName = path:
12
+    replaceStrings ["/"] ["-"]
13
+      (removePrefix keyDirectory path + "-key.service");
14
+
15
+  funcs = rec {
16
+
17
+    /* Test to see if a file path is a NixOps managed key.
18
+
19
+       Example:
20
+         isKeyFile "/run/keys/foo"
21
+         => true
22
+         isKeyFile "/etc/passwd"
23
+         => false
24
+    */
25
+    isKeyFile = path:
26
+      if path == null
27
+        then false
28
+        else hasPrefix keyDirectory path;
29
+
30
+    /* Returns an array containing a systemd service name that can be
31
+       used to add a 'wants' or 'after' entry for a NixOps key.
32
+
33
+       Example:
34
+         keyService "/run/keys/foo"
35
+         => ["foo.service"]
36
+         keyService "/etc/passwd"
37
+         => []
38
+    */
39
+    keyService = path: optional (isKeyFile path) (mkServiceName path);
40
+
41
+  };
42
+
43
+in funcs

+ 18
- 0
modules/default.nix View File

@@ -1,8 +1,26 @@
1 1
 { config, lib, pkgs, ...}:
2 2
 
3
+with lib;
4
+
5
+let
6
+  libFiles = [
7
+    ../lib/keys.nix
8
+  ];
9
+
10
+  loadLib = path: import path { inherit lib; };
11
+  libs = foldr (a: b: recursiveUpdate (loadLib a) b) {} libFiles;
12
+
13
+in
3 14
 {
4 15
   imports = [
5 16
     ./security
6 17
     ./services
7 18
   ];
19
+
20
+  options.phoebe.lib = mkOption {
21
+    type = types.attrs;
22
+    default = libs;
23
+    internal = true;
24
+    readOnly = true;
25
+  };
8 26
 }

+ 5
- 12
modules/services/databases/postgresql/default.nix View File

@@ -6,9 +6,10 @@ with lib;
6 6
 
7 7
 let
8 8
   cfg = config.phoebe.services.postgresql;
9
+  plib = config.phoebe.lib;
9 10
   superuser = config.services.postgresql.superUser;
10 11
   create-user = import ./create-user.nix { inherit config lib pkgs; };
11
-  afterservices = concatMap (a: a.afterServices) (attrValues cfg.accounts);
12
+  afterservices = concatMap (a: plib.keyService a.passwordFile) (attrValues cfg.accounts);
12 13
 
13 14
   # Per-account options:
14 15
   account = { name, ... }: {
@@ -30,18 +31,10 @@ let
30 31
           A file containing the password of this database user.
31 32
           You'll want to use something like NixOps to get the password
32 33
           file onto the target machine.
33
-        '';
34
-      };
35 34
 
36
-      afterServices = mkOption {
37
-        type = types.listOf types.str;
38
-        default = [ ];
39
-        example = [ "dbpassword.service" ];
40
-        description = ''
41
-          A list of services that need to run before this user account
42
-          can be created.  This is really useful if you are using
43
-          NixOps to deploy the password file and want to wait for the
44
-          key to appear in /run/keys.
35
+          If the file looks like it's a NixOps key then the account
36
+          creation script will automatically wait for the appropriate
37
+          key service to start.
45 38
         '';
46 39
       };
47 40
 

+ 21
- 4
modules/services/web/rails/default.nix View File

@@ -8,6 +8,7 @@ let
8 8
   ##############################################################################
9 9
   # Save some typing.
10 10
   cfg = config.phoebe.services.rails;
11
+  plib = config.phoebe.lib;
11 12
   scripts = import ./scripts.nix { inherit lib pkgs; };
12 13
   options = import ./options.nix { inherit config lib pkgs; };
13 14
 
@@ -70,10 +71,17 @@ let
70 71
       } // app.environment;
71 72
 
72 73
       wantedBy = [ "multi-user.target" ];
73
-      wants = optional (app.database.passwordService != null) app.database.passwordService;
74
-      after = [ "network.target" ] ++
74
+
75
+      wants =
76
+        plib.keyService app.database.passwordFile ++
77
+        plib.keyService app.sourcedFile;
78
+
79
+      after =
80
+        [ "network.target" ] ++
75 81
         optional localpg  "postgresql.service" ++
76
-        optional (app.database.passwordService != null) app.database.passwordService;
82
+        optional localpg  "pg-accounts.service" ++
83
+        plib.keyService app.database.passwordFile ++
84
+        plib.keyService app.sourcedFile;
77 85
 
78 86
       preStart = ''
79 87
         # Prepare the config directory:
@@ -88,6 +96,11 @@ let
88 96
         mkdir -p ${app.home}/home
89 97
         ln -nfs ${app.package}/share/${app.name} ${app.home}/home/${app.name}
90 98
 
99
+        # Copy the sourcedFile if necessary:
100
+        ${optionalString (app.sourcedFile != null) ''
101
+          cp ${app.sourcedFile} ${app.home}/state/sourcedFile.sh
102
+        ''}
103
+
91 104
         # Fix permissions:
92 105
         chown -R rails-${app.name}:rails-${app.name} ${app.home}
93 106
         chmod go+rx $(dirname "${app.home}")
@@ -101,6 +114,11 @@ let
101 114
             -s ${app.home}/state
102 115
       '';
103 116
 
117
+      script = ''
118
+        ${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
119
+        ${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}
120
+      '';
121
+
104 122
       serviceConfig = {
105 123
         WorkingDirectory = "${app.package}/share/${app.name}";
106 124
         Restart = "on-failure";
@@ -110,7 +128,6 @@ let
110 128
         User = "rails-${app.name}";
111 129
         Group = "rails-${app.name}";
112 130
         UMask = "0077";
113
-        ExecStart = "${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}";
114 131
       };
115 132
     };
116 133
   };

+ 17
- 12
modules/services/web/rails/options.nix View File

@@ -31,18 +31,6 @@ let
31 31
         '';
32 32
       };
33 33
 
34
-      passwordService = mkOption {
35
-        type = types.nullOr types.str;
36
-        default = null;
37
-        example = "db-password.service";
38
-        description = ''
39
-          A service to wait on before starting the Rails application.
40
-          This service should provide the password file for the
41
-          passwordFile option.  Useful when deploying passwords with
42
-          NixOps.
43
-        '';
44
-      };
45
-
46 34
       migrate = mkOption {
47 35
         type = types.bool;
48 36
         default = true;
@@ -112,6 +100,23 @@ let
112 100
         default = { };
113 101
         description = "Environment variables.";
114 102
       };
103
+
104
+      sourcedFile = mkOption {
105
+        type = types.nullOr types.path;
106
+        default = null;
107
+        example = "/run/keys/env.sh";
108
+        description = ''
109
+          Bash file to source immediately before running any service
110
+          command.
111
+
112
+          If the file is store under /run/keys the service will wait
113
+          for the file to become available.
114
+
115
+          This option can be used to set environment variables more
116
+          securely than using the environment option.  However, you
117
+          should really use the Rails secrets system.
118
+        '';
119
+      };
115 120
     };
116 121
 
117 122
     config = {

Loading…
Cancel
Save