lol

nixos/archtika: init at 1.0.1 (#365218)

authored by

Sandro and committed by
GitHub
32d2826f d1c535f6

+378
+6
maintainers/maintainer-list.nix
··· 23443 23443 github = "thilobillerbeck"; 23444 23444 githubId = 7442383; 23445 23445 }; 23446 + thiloho = { 23447 + name = "Thilo Hohlt"; 23448 + email = "thilo.hohlt@tutanota.com"; 23449 + github = "thiloho"; 23450 + githubId = 123883702; 23451 + }; 23446 23452 thled = { 23447 23453 name = "Thomas Le Duc"; 23448 23454 email = "dev@tleduc.de";
+2
nixos/doc/manual/release-notes/rl-2505.section.md
··· 63 63 64 64 - [Bonsai](https://git.sr.ht/~stacyharper/bonsai), a general-purpose event mapper/state machine primarily used to create complex key shortcuts, and as part of the [SXMO](https://sxmo.org/) desktop environment. Available as [services.bonsaid](#opt-services.bonsaid.enable). 65 65 66 + - [archtika](https://github.com/archtika/archtika), a FLOSS, modern, performant, lightweight and self‑hosted CMS. Available as [services.archtika](#opt-services.archtika.enable). 67 + 66 68 - [scanservjs](https://github.com/sbs20/scanservjs/), a web UI for SANE scanners. Available at [services.scanservjs](#opt-services.scanservjs.enable). 67 69 68 70 - [Kimai](https://www.kimai.org/), a web-based multi-user time-tracking application. Available as [services.kimai](options.html#opt-services.kimai).
+1
nixos/modules/module-list.nix
··· 1459 1459 ./services/web-apps/agorakit.nix 1460 1460 ./services/web-apps/alps.nix 1461 1461 ./services/web-apps/anuko-time-tracker.nix 1462 + ./services/web-apps/archtika.nix 1462 1463 ./services/web-apps/artalk.nix 1463 1464 ./services/web-apps/audiobookshelf.nix 1464 1465 ./services/web-apps/bluemap.nix
+307
nixos/modules/services/web-apps/archtika.nix
··· 1 + { 2 + config, 3 + lib, 4 + pkgs, 5 + ... 6 + }: 7 + 8 + let 9 + inherit (lib) 10 + mkEnableOption 11 + mkOption 12 + mkIf 13 + mkPackageOption 14 + types 15 + ; 16 + cfg = config.services.archtika; 17 + in 18 + { 19 + options.services.archtika = { 20 + enable = mkEnableOption "Whether to enable the archtika service"; 21 + 22 + package = mkPackageOption pkgs "archtika" { }; 23 + 24 + user = mkOption { 25 + type = types.str; 26 + default = "archtika"; 27 + description = "User account under which archtika runs."; 28 + }; 29 + 30 + group = mkOption { 31 + type = types.str; 32 + default = "archtika"; 33 + description = "Group under which archtika runs."; 34 + }; 35 + 36 + databaseName = mkOption { 37 + type = types.str; 38 + default = "archtika"; 39 + description = "Name of the PostgreSQL database for archtika."; 40 + }; 41 + 42 + apiPort = mkOption { 43 + type = types.port; 44 + default = 5000; 45 + description = "Port on which the API runs."; 46 + }; 47 + 48 + apiAdminPort = mkOption { 49 + type = types.port; 50 + default = 7500; 51 + description = "Port on which the API admin server runs."; 52 + }; 53 + 54 + webAppPort = mkOption { 55 + type = types.port; 56 + default = 10000; 57 + description = "Port on which the web application runs."; 58 + }; 59 + 60 + domain = mkOption { 61 + type = types.str; 62 + description = "Domain to use for the application."; 63 + }; 64 + 65 + settings = mkOption { 66 + description = "Settings for the running archtika application."; 67 + type = types.submodule { 68 + options = { 69 + disableRegistration = mkOption { 70 + type = types.bool; 71 + default = false; 72 + description = "By default any user can create an account. That behavior can be disabled with this option."; 73 + }; 74 + maxUserWebsites = mkOption { 75 + type = types.ints.positive; 76 + default = 2; 77 + description = "Maximum number of websites allowed per user by default."; 78 + }; 79 + maxWebsiteStorageSize = mkOption { 80 + type = types.ints.positive; 81 + default = 50; 82 + description = "Maximum amount of disk space in MB allowed per user website by default."; 83 + }; 84 + }; 85 + }; 86 + }; 87 + }; 88 + 89 + config = mkIf cfg.enable ( 90 + let 91 + baseHardenedSystemdOptions = { 92 + CapabilityBoundingSet = ""; 93 + LockPersonality = true; 94 + NoNewPrivileges = true; 95 + PrivateDevices = true; 96 + PrivateTmp = true; 97 + ProtectClock = true; 98 + ProtectControlGroups = true; 99 + ProtectHome = true; 100 + ProtectHostname = true; 101 + ProtectKernelLogs = true; 102 + ProtectKernelModules = true; 103 + ProtectKernelTunables = true; 104 + ProtectSystem = "strict"; 105 + RemoveIPC = true; 106 + RestrictNamespaces = true; 107 + RestrictRealtime = true; 108 + RestrictSUIDSGID = true; 109 + SystemCallArchitectures = "native"; 110 + SystemCallFilter = [ 111 + "@system-service" 112 + "~@privileged" 113 + "~@resources" 114 + ]; 115 + ReadWritePaths = [ "/var/www/archtika-websites" ]; 116 + }; 117 + in 118 + { 119 + users.users.${cfg.user} = { 120 + isSystemUser = true; 121 + group = cfg.group; 122 + }; 123 + 124 + users.groups.${cfg.group} = { 125 + members = [ 126 + "nginx" 127 + "postgres" 128 + ]; 129 + }; 130 + 131 + systemd.tmpfiles.settings."10-archtika" = { 132 + "/var/www" = { 133 + d = { 134 + mode = "0755"; 135 + user = "root"; 136 + group = "root"; 137 + }; 138 + }; 139 + "/var/www/archtika-websites" = { 140 + d = { 141 + mode = "0770"; 142 + user = cfg.user; 143 + group = cfg.group; 144 + }; 145 + }; 146 + }; 147 + 148 + systemd.services.archtika-api = { 149 + description = "archtika API service"; 150 + wantedBy = [ "multi-user.target" ]; 151 + after = [ 152 + "network.target" 153 + "postgresql.service" 154 + ]; 155 + 156 + path = [ config.services.postgresql.package ]; 157 + 158 + serviceConfig = baseHardenedSystemdOptions // { 159 + User = cfg.user; 160 + Group = cfg.group; 161 + Restart = "always"; 162 + WorkingDirectory = "${cfg.package}/rest-api"; 163 + RestrictAddressFamilies = [ 164 + "AF_INET" 165 + "AF_INET6" 166 + "AF_UNIX" 167 + ]; 168 + }; 169 + 170 + script = 171 + let 172 + dbUrl = user: "postgres://${user}@/${cfg.databaseName}?host=/var/run/postgresql"; 173 + in 174 + '' 175 + JWT_SECRET=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c64) 176 + 177 + psql ${dbUrl "postgres"} \ 178 + -c "ALTER DATABASE ${cfg.databaseName} SET \"app.jwt_secret\" TO '$JWT_SECRET'" \ 179 + -c "ALTER DATABASE ${cfg.databaseName} SET \"app.website_max_storage_size\" TO ${toString cfg.settings.maxWebsiteStorageSize}" \ 180 + -c "ALTER DATABASE ${cfg.databaseName} SET \"app.website_max_number_user\" TO ${toString cfg.settings.maxUserWebsites}" 181 + 182 + ${lib.getExe pkgs.dbmate} --url "${dbUrl "postgres"}&sslmode=disable" --migrations-dir ${cfg.package}/rest-api/db/migrations up 183 + 184 + PGRST_SERVER_CORS_ALLOWED_ORIGINS="https://${cfg.domain}" \ 185 + PGRST_ADMIN_SERVER_PORT=${toString cfg.apiAdminPort} \ 186 + PGRST_SERVER_PORT=${toString cfg.apiPort} \ 187 + PGRST_DB_SCHEMAS="api" \ 188 + PGRST_DB_ANON_ROLE="anon" \ 189 + PGRST_OPENAPI_MODE="ignore-privileges" \ 190 + PGRST_DB_URI=${dbUrl "authenticator"} \ 191 + PGRST_JWT_SECRET="$JWT_SECRET" \ 192 + ${lib.getExe pkgs.postgrest} 193 + ''; 194 + }; 195 + 196 + systemd.services.archtika-web = { 197 + description = "archtika Web App service"; 198 + wantedBy = [ "multi-user.target" ]; 199 + after = [ "network.target" ]; 200 + 201 + serviceConfig = baseHardenedSystemdOptions // { 202 + User = cfg.user; 203 + Group = cfg.group; 204 + Restart = "always"; 205 + WorkingDirectory = "${cfg.package}/web-app"; 206 + RestrictAddressFamilies = [ 207 + "AF_INET" 208 + "AF_INET6" 209 + ]; 210 + }; 211 + 212 + environment = { 213 + REGISTRATION_IS_DISABLED = toString cfg.settings.disableRegistration; 214 + BODY_SIZE_LIMIT = "10M"; 215 + ORIGIN = "https://${cfg.domain}"; 216 + PORT = toString cfg.webAppPort; 217 + }; 218 + 219 + script = "${lib.getExe pkgs.nodejs} ${cfg.package}/web-app"; 220 + }; 221 + 222 + services.postgresql = { 223 + enable = true; 224 + ensureDatabases = [ cfg.databaseName ]; 225 + extensions = ps: with ps; [ pgjwt ]; 226 + authentication = lib.mkOverride 11 '' 227 + local postgres postgres trust 228 + local ${cfg.databaseName} all trust 229 + ''; 230 + }; 231 + 232 + systemd.services.postgresql = { 233 + path = with pkgs; [ 234 + gnutar 235 + gzip 236 + ]; 237 + serviceConfig = { 238 + ReadWritePaths = [ "/var/www/archtika-websites" ]; 239 + SystemCallFilter = [ "@system-service" ]; 240 + }; 241 + }; 242 + 243 + services.nginx = { 244 + enable = true; 245 + recommendedProxySettings = true; 246 + recommendedTlsSettings = true; 247 + recommendedZstdSettings = true; 248 + recommendedOptimisation = true; 249 + 250 + appendHttpConfig = '' 251 + map $http_cookie $archtika_auth_header { 252 + default ""; 253 + "~*session_token=([^;]+)" "Bearer $1"; 254 + } 255 + ''; 256 + 257 + virtualHosts = { 258 + "${cfg.domain}" = { 259 + useACMEHost = cfg.domain; 260 + forceSSL = true; 261 + locations = { 262 + "/" = { 263 + proxyPass = "http://127.0.0.1:${toString cfg.webAppPort}"; 264 + }; 265 + "/previews/" = { 266 + alias = "/var/www/archtika-websites/previews/"; 267 + index = "index.html"; 268 + tryFiles = "$uri $uri/ $uri.html =404"; 269 + }; 270 + "/api/rpc/export_articles_zip" = { 271 + proxyPass = "http://127.0.0.1:${toString cfg.apiPort}/rpc/export_articles_zip"; 272 + extraConfig = '' 273 + default_type application/json; 274 + proxy_set_header Authorization $archtika_auth_header; 275 + ''; 276 + }; 277 + "/api/" = { 278 + proxyPass = "http://127.0.0.1:${toString cfg.apiPort}/"; 279 + extraConfig = '' 280 + default_type application/json; 281 + ''; 282 + }; 283 + "/api/rpc/register" = mkIf cfg.settings.disableRegistration { 284 + extraConfig = '' 285 + deny all; 286 + ''; 287 + }; 288 + }; 289 + }; 290 + "~^(?<subdomain>.+)\\.${cfg.domain}$" = { 291 + useACMEHost = cfg.domain; 292 + forceSSL = true; 293 + locations = { 294 + "/" = { 295 + root = "/var/www/archtika-websites/$subdomain"; 296 + index = "index.html"; 297 + tryFiles = "$uri $uri/ $uri.html =404"; 298 + }; 299 + }; 300 + }; 301 + }; 302 + }; 303 + } 304 + ); 305 + 306 + meta.maintainers = [ lib.maintainers.thiloho ]; 307 + }
+62
pkgs/by-name/ar/archtika/package.nix
··· 1 + { 2 + lib, 3 + stdenv, 4 + buildNpmPackage, 5 + importNpmLock, 6 + symlinkJoin, 7 + fetchFromGitHub, 8 + nix-update-script, 9 + }: 10 + 11 + let 12 + version = "1.2.0"; 13 + 14 + src = fetchFromGitHub { 15 + owner = "archtika"; 16 + repo = "archtika"; 17 + tag = "v${version}"; 18 + hash = "sha256-ba9da7LqCE/e2lhRVHD7GOhwOj1fNTBbN/pARPMzIg4="; 19 + }; 20 + 21 + web = buildNpmPackage { 22 + name = "web-app"; 23 + src = "${src}/web-app"; 24 + npmDepsHash = "sha256-RTyo7K/Hr1hBGtcBKynrziUInl91JqZl84NkJg16ufA="; 25 + npmFlags = [ "--legacy-peer-deps" ]; 26 + installPhase = '' 27 + mkdir -p $out/web-app 28 + cp package.json $out/web-app 29 + cp -r node_modules $out/web-app 30 + cp -r build/* $out/web-app 31 + cp -r template-styles $out/web-app 32 + ''; 33 + }; 34 + 35 + api = stdenv.mkDerivation { 36 + name = "api"; 37 + src = "${src}/rest-api"; 38 + installPhase = '' 39 + mkdir -p $out/rest-api/db/migrations 40 + cp -r db/migrations/* $out/rest-api/db/migrations 41 + ''; 42 + }; 43 + in 44 + symlinkJoin { 45 + pname = "archtika"; 46 + inherit version; 47 + 48 + paths = [ 49 + web 50 + api 51 + ]; 52 + 53 + passthru.updateScript = nix-update-script { }; 54 + 55 + meta = { 56 + description = "Modern, performant and lightweight CMS"; 57 + homepage = "https://archtika.com"; 58 + license = lib.licenses.gpl3; 59 + maintainers = [ lib.maintainers.thiloho ]; 60 + platforms = lib.platforms.unix; 61 + }; 62 + }