diff options
| -rw-r--r-- | hosts/surtr/default.nix | 8 | ||||
| -rw-r--r-- | modules/etebase-server.nix | 228 |
2 files changed, 235 insertions, 1 deletions
diff --git a/hosts/surtr/default.nix b/hosts/surtr/default.nix index f6200cf3..75dd9847 100644 --- a/hosts/surtr/default.nix +++ b/hosts/surtr/default.nix | |||
| @@ -1,4 +1,7 @@ | |||
| 1 | { flake, pkgs, lib, ... }: | 1 | { flake, pkgs, lib, ... }: |
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 2 | { | 5 | { |
| 3 | imports = with flake.nixosModules.systemProfiles; [ | 6 | imports = with flake.nixosModules.systemProfiles; [ |
| 4 | tmpfs-root qemu-guest openssh rebuild-machines zfs | 7 | tmpfs-root qemu-guest openssh rebuild-machines zfs |
| @@ -68,7 +71,7 @@ | |||
| 68 | systemd.network = { | 71 | systemd.network = { |
| 69 | networks = { | 72 | networks = { |
| 70 | "40-ens3".networkConfig = { | 73 | "40-ens3".networkConfig = { |
| 71 | Domains = lib.mkForce "~."; | 74 | Domains = mkForce "~."; |
| 72 | DNS = [ "127.0.0.1:5353" "[::1]:5353" ]; | 75 | DNS = [ "127.0.0.1:5353" "[::1]:5353" ]; |
| 73 | # DNSSEC = true; | 76 | # DNSSEC = true; |
| 74 | # DNS = [ "46.38.225.230" "46.38.252.230" "2a03:4000:0:1::e1e6" "2a03:4000:8000::fce6" ]; | 77 | # DNS = [ "46.38.225.230" "46.38.252.230" "2a03:4000:0:1::e1e6" "2a03:4000:8000::fce6" ]; |
| @@ -126,6 +129,9 @@ | |||
| 126 | cmdport 0 | 129 | cmdport 0 |
| 127 | ''; | 130 | ''; |
| 128 | }; | 131 | }; |
| 132 | systemd.services.chronyd.serviceConfig = { | ||
| 133 | PrivateDevices = mkForce false; | ||
| 134 | }; | ||
| 129 | 135 | ||
| 130 | services.openssh = { | 136 | services.openssh = { |
| 131 | enable = true; | 137 | enable = true; |
diff --git a/modules/etebase-server.nix b/modules/etebase-server.nix new file mode 100644 index 00000000..341e7fa0 --- /dev/null +++ b/modules/etebase-server.nix | |||
| @@ -0,0 +1,228 @@ | |||
| 1 | { config, pkgs, lib, ... }: | ||
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 5 | let | ||
| 6 | cfg = config.services.etebase-server; | ||
| 7 | |||
| 8 | pythonEnv = pkgs.python3.withPackages (ps: with ps; | ||
| 9 | [ etebase-server daphne psycopg2 ]); | ||
| 10 | |||
| 11 | iniFmt = pkgs.formats.ini {}; | ||
| 12 | |||
| 13 | configIni = iniFmt.generate "etebase-server.ini" cfg.settings; | ||
| 14 | |||
| 15 | defaultUser = "etebase-server"; | ||
| 16 | in | ||
| 17 | { | ||
| 18 | disabledModules = [ "services/misc/etebase-server.nix" ]; | ||
| 19 | |||
| 20 | imports = [ | ||
| 21 | (mkRemovedOptionModule | ||
| 22 | [ "services" "etebase-server" "customIni" ] | ||
| 23 | "Set the option `services.etebase-server.settings' instead.") | ||
| 24 | (mkRemovedOptionModule | ||
| 25 | [ "services" "etebase-server" "database" ] | ||
| 26 | "Set the option `services.etebase-server.settings.database' instead.") | ||
| 27 | (mkRenamedOptionModule | ||
| 28 | [ "services" "etebase-server" "secretFile" ] | ||
| 29 | [ "services" "etebase-server" "settings" "secret_file" ]) | ||
| 30 | (mkRenamedOptionModule | ||
| 31 | [ "services" "etebase-server" "host" ] | ||
| 32 | [ "services" "etebase-server" "settings" "allowed_hosts" "allowed_host1" ]) | ||
| 33 | ]; | ||
| 34 | |||
| 35 | options = { | ||
| 36 | services.etebase-server = { | ||
| 37 | enable = mkOption { | ||
| 38 | type = types.bool; | ||
| 39 | default = false; | ||
| 40 | example = true; | ||
| 41 | description = lib.mdDoc '' | ||
| 42 | Whether to enable the Etebase server. | ||
| 43 | |||
| 44 | Once enabled you need to create an admin user by invoking the | ||
| 45 | shell command `etebase-server createsuperuser` with | ||
| 46 | the user specified by the `user` option or a superuser. | ||
| 47 | Then you can login and create accounts on your-etebase-server.com/admin | ||
| 48 | ''; | ||
| 49 | }; | ||
| 50 | |||
| 51 | dataDir = mkOption { | ||
| 52 | type = types.str; | ||
| 53 | default = "/var/lib/etebase-server"; | ||
| 54 | description = lib.mdDoc "Directory to store the Etebase server data."; | ||
| 55 | }; | ||
| 56 | |||
| 57 | port = mkOption { | ||
| 58 | type = with types; nullOr port; | ||
| 59 | default = 8001; | ||
| 60 | description = lib.mdDoc "Port to listen on."; | ||
| 61 | }; | ||
| 62 | |||
| 63 | openFirewall = mkOption { | ||
| 64 | type = types.bool; | ||
| 65 | default = false; | ||
| 66 | description = lib.mdDoc '' | ||
| 67 | Whether to open ports in the firewall for the server. | ||
| 68 | ''; | ||
| 69 | }; | ||
| 70 | |||
| 71 | unixSocket = mkOption { | ||
| 72 | type = with types; nullOr str; | ||
| 73 | default = null; | ||
| 74 | description = lib.mdDoc "The path to the socket to bind to."; | ||
| 75 | example = "/run/etebase-server/etebase-server.sock"; | ||
| 76 | }; | ||
| 77 | |||
| 78 | settings = mkOption { | ||
| 79 | type = lib.types.submodule { | ||
| 80 | freeformType = iniFmt.type; | ||
| 81 | |||
| 82 | options = { | ||
| 83 | global = { | ||
| 84 | debug = mkOption { | ||
| 85 | type = types.bool; | ||
| 86 | default = false; | ||
| 87 | description = lib.mdDoc '' | ||
| 88 | Whether to set django's DEBUG flag. | ||
| 89 | ''; | ||
| 90 | }; | ||
| 91 | secret_file = mkOption { | ||
| 92 | type = with types; nullOr str; | ||
| 93 | default = null; | ||
| 94 | description = lib.mdDoc '' | ||
| 95 | The path to a file containing the secret | ||
| 96 | used as django's SECRET_KEY. | ||
| 97 | ''; | ||
| 98 | }; | ||
| 99 | static_root = mkOption { | ||
| 100 | type = types.str; | ||
| 101 | default = "${cfg.dataDir}/static"; | ||
| 102 | defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/static"''; | ||
| 103 | description = lib.mdDoc "The directory for static files."; | ||
| 104 | }; | ||
| 105 | media_root = mkOption { | ||
| 106 | type = types.str; | ||
| 107 | default = "${cfg.dataDir}/media"; | ||
| 108 | defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/media"''; | ||
| 109 | description = lib.mdDoc "The media directory."; | ||
| 110 | }; | ||
| 111 | }; | ||
| 112 | allowed_hosts = { | ||
| 113 | allowed_host1 = mkOption { | ||
| 114 | type = types.str; | ||
| 115 | default = "0.0.0.0"; | ||
| 116 | example = "localhost"; | ||
| 117 | description = lib.mdDoc '' | ||
| 118 | The main host that is allowed access. | ||
| 119 | ''; | ||
| 120 | }; | ||
| 121 | }; | ||
| 122 | database = { | ||
| 123 | engine = mkOption { | ||
| 124 | type = types.enum [ "django.db.backends.sqlite3" "django.db.backends.postgresql" ]; | ||
| 125 | default = "django.db.backends.sqlite3"; | ||
| 126 | description = lib.mdDoc "The database engine to use."; | ||
| 127 | }; | ||
| 128 | name = mkOption { | ||
| 129 | type = types.str; | ||
| 130 | default = "${cfg.dataDir}/db.sqlite3"; | ||
| 131 | defaultText = literalExpression ''"''${config.services.etebase-server.dataDir}/db.sqlite3"''; | ||
| 132 | description = lib.mdDoc "The database name."; | ||
| 133 | }; | ||
| 134 | }; | ||
| 135 | }; | ||
| 136 | }; | ||
| 137 | default = {}; | ||
| 138 | description = lib.mdDoc '' | ||
| 139 | Configuration for `etebase-server`. Refer to | ||
| 140 | <https://github.com/etesync/server/blob/master/etebase-server.ini.example> | ||
| 141 | and <https://github.com/etesync/server/wiki> | ||
| 142 | for details on supported values. | ||
| 143 | ''; | ||
| 144 | example = { | ||
| 145 | global = { | ||
| 146 | debug = true; | ||
| 147 | media_root = "/path/to/media"; | ||
| 148 | }; | ||
| 149 | allowed_hosts = { | ||
| 150 | allowed_host2 = "localhost"; | ||
| 151 | }; | ||
| 152 | }; | ||
| 153 | }; | ||
| 154 | |||
| 155 | user = mkOption { | ||
| 156 | type = types.str; | ||
| 157 | default = defaultUser; | ||
| 158 | description = lib.mdDoc "User under which Etebase server runs."; | ||
| 159 | }; | ||
| 160 | }; | ||
| 161 | }; | ||
| 162 | |||
| 163 | config = mkIf cfg.enable { | ||
| 164 | |||
| 165 | environment.systemPackages = with pkgs; [ | ||
| 166 | (runCommand "etebase-server" { | ||
| 167 | nativeBuildInputs = [ makeWrapper ]; | ||
| 168 | } '' | ||
| 169 | makeWrapper ${pythonEnv}/bin/etebase-server \ | ||
| 170 | $out/bin/etebase-server \ | ||
| 171 | --chdir ${escapeShellArg cfg.dataDir} \ | ||
| 172 | --prefix ETEBASE_EASY_CONFIG_PATH : "${configIni}" | ||
| 173 | '') | ||
| 174 | ]; | ||
| 175 | |||
| 176 | systemd.tmpfiles.rules = [ | ||
| 177 | "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -" | ||
| 178 | ]; | ||
| 179 | |||
| 180 | systemd.services.etebase-server = { | ||
| 181 | description = "An Etebase (EteSync 2.0) server"; | ||
| 182 | after = [ "network.target" "systemd-tmpfiles-setup.service" ]; | ||
| 183 | wantedBy = [ "multi-user.target" ]; | ||
| 184 | path = [ pythonEnv ]; | ||
| 185 | serviceConfig = { | ||
| 186 | User = cfg.user; | ||
| 187 | Restart = "always"; | ||
| 188 | WorkingDirectory = cfg.dataDir; | ||
| 189 | }; | ||
| 190 | environment = { | ||
| 191 | ETEBASE_EASY_CONFIG_PATH = configIni; | ||
| 192 | }; | ||
| 193 | preStart = '' | ||
| 194 | # Auto-migrate on first run or if the package has changed | ||
| 195 | versionFile="${cfg.dataDir}/src-version" | ||
| 196 | if [[ $(cat "$versionFile" 2>/dev/null) != ${pkgs.etebase-server} ]]; then | ||
| 197 | etebase-server migrate --no-input | ||
| 198 | etebase-server collectstatic --no-input --clear | ||
| 199 | echo ${pkgs.etebase-server} > "$versionFile" | ||
| 200 | fi | ||
| 201 | ''; | ||
| 202 | script = | ||
| 203 | let | ||
| 204 | networking = if cfg.unixSocket != null | ||
| 205 | then "-u ${cfg.unixSocket}" | ||
| 206 | else "-b 0.0.0.0 -p ${toString cfg.port}"; | ||
| 207 | in '' | ||
| 208 | cd "${pythonEnv}/lib/etebase-server"; | ||
| 209 | daphne ${networking} \ | ||
| 210 | etebase_server.asgi:application | ||
| 211 | ''; | ||
| 212 | }; | ||
| 213 | |||
| 214 | users = optionalAttrs (cfg.user == defaultUser) { | ||
| 215 | users.${defaultUser} = { | ||
| 216 | isSystemUser = true; | ||
| 217 | group = defaultUser; | ||
| 218 | home = cfg.dataDir; | ||
| 219 | }; | ||
| 220 | |||
| 221 | groups.${defaultUser} = {}; | ||
| 222 | }; | ||
| 223 | |||
| 224 | networking.firewall = mkIf cfg.openFirewall { | ||
| 225 | allowedTCPPorts = [ cfg.port ]; | ||
| 226 | }; | ||
| 227 | }; | ||
| 228 | } | ||
