From f64ee1322f5e8a8e1d6008768db3fe4736460ac1 Mon Sep 17 00:00:00 2001 From: sid Date: Sun, 17 May 2026 17:55:00 +0200 Subject: [PATCH] overleaf: init --- constants.nix | 5 + hosts/rx4/services/default.nix | 1 + hosts/rx4/services/overleaf-oci.nix | 18 ++ hosts/sid/services/nginx.nix | 5 + modules/nixos/default.nix | 1 + modules/nixos/overleaf-oci/default.nix | 257 ++++++++++++++++++ .../overleaf-oci/mongodb-init-replica-set.js | 3 + 7 files changed, 290 insertions(+) create mode 100644 hosts/rx4/services/overleaf-oci.nix create mode 100644 modules/nixos/overleaf-oci/default.nix create mode 100644 modules/nixos/overleaf-oci/mongodb-init-replica-set.js diff --git a/constants.nix b/constants.nix index 3dc7ded..5dfa26e 100644 --- a/constants.nix +++ b/constants.nix @@ -37,6 +37,11 @@ rec { fqdn = "ai." + domain; port = 8083; }; + overleaf-oci = rec { + subdomain = "of"; + fqdn = subdomain + "." + domain; + port = 8081; + }; rss-bridge = rec { subdomain = "rss-bridge"; fqdn = subdomain + "." + domain; diff --git a/hosts/rx4/services/default.nix b/hosts/rx4/services/default.nix index a61584e..df12843 100644 --- a/hosts/rx4/services/default.nix +++ b/hosts/rx4/services/default.nix @@ -16,6 +16,7 @@ ./miniflux.nix ./netdata.nix ./nginx.nix + ./overleaf-oci.nix ./open-webui-oci.nix ./print-server.nix ./rsshub-oci.nix diff --git a/hosts/rx4/services/overleaf-oci.nix b/hosts/rx4/services/overleaf-oci.nix new file mode 100644 index 0000000..575012f --- /dev/null +++ b/hosts/rx4/services/overleaf-oci.nix @@ -0,0 +1,18 @@ +{ outputs, constants, ... }: + +let + inherit (constants.services.overleaf-oci) port subdomain; +in +{ + imports = [ outputs.nixosModules.overleaf-oci ]; + + services.overleaf-oci = { + enable = true; + inherit port; + reverseProxy = { + enable = true; + inherit subdomain; + forceSSL = false; + }; + }; +} diff --git a/hosts/sid/services/nginx.nix b/hosts/sid/services/nginx.nix index d1e6227..1c2f432 100644 --- a/hosts/sid/services/nginx.nix +++ b/hosts/sid/services/nginx.nix @@ -77,6 +77,11 @@ in error_log /var/log/nginx/open-webui-error.log debug; ''; }; + virtualHosts."${constants.services.overleaf-oci.fqdn}" = mkVirtualHost { + inherit ssl; + address = constants.hosts.rx4.ip; + port = constants.services.overleaf-oci.port; + }; virtualHosts."${constants.services.rsshub-oci.fqdn}" = mkVirtualHost { inherit ssl; address = constants.hosts.rx4.ip; diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 540f4ee..1028fdf 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -6,6 +6,7 @@ forgejo-runner = import ./forgejo-runner; gnome = import ./gnome; monero = import ./monero; + overleaf-oci = import ./overleaf-oci; pki = import ./pki; rsshub-oci = import ./rsshub-oci; tailscale = import ./tailscale; diff --git a/modules/nixos/overleaf-oci/default.nix b/modules/nixos/overleaf-oci/default.nix new file mode 100644 index 0000000..6be248b --- /dev/null +++ b/modules/nixos/overleaf-oci/default.nix @@ -0,0 +1,257 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.overleaf-oci; + domain = config.networking.domain; + subdomain = cfg.reverseProxy.subdomain; + fqdn = if (cfg.reverseProxy.enable && subdomain != "") then "${subdomain}.${domain}" else domain; + + mongodbInitReplicaSet = ./mongodb-init-replica-set.js; + + images = { + # https://hub.docker.com/r/sharelatex/sharelatex + sharelatex = pkgs.dockerTools.pullImage { + imageName = "sharelatex/sharelatex"; + imageDigest = "sha256:fa5e5a049721b0973aa6ddabd15cb04da2d492f38c685fda0805dab97d4f3c8a"; + hash = "sha256-2Om6b+xPcBFObMKAlZ+qsvPw1xd8B/8ODyI5dsWoaQA="; + finalImageName = "sharelatex/sharelatex"; + finalImageTag = "6.1.2"; + }; + # https://hub.docker.com/_/mongo + mongo = pkgs.dockerTools.pullImage { + imageName = "mongo"; + imageDigest = "sha256:ce32a4b67580c982e2020b07d4ffafdbd9ce474088434294d89c6bd9257f7788"; + hash = "sha256-NtMSeH5hYD69uF7gZWPppxoI9esyPTfxaw50yh0ChpY="; + finalImageName = "mongo"; + finalImageTag = "8.3.1"; + }; + # https://hub.docker.com/_/redis + redis = pkgs.dockerTools.pullImage { + imageName = "redis"; + imageDigest = "sha256:0c341492924cad6f5483f9133e43bd6c51ecdecbcadfac5b51657393b6a7936c"; + hash = "sha256-nU5+UdvA+29sbr7dAD2jpKYACxYb3BfStlOE/25ET+w="; + finalImageName = "redis"; + finalImageTag = "8.6.3"; + }; + }; + + defaultEnv = { + EMAIL_CONFIRMATION_DISABLED = "true"; + ENABLED_LINKED_FILE_TYPES = "project_file,project_output_file"; + ENABLE_CONVERSIONS = "true"; + OVERLEAF_APP_NAME = "Overleaf Community Edition"; + OVERLEAF_MONGO_URL = "mongodb://mongo/sharelatex"; + OVERLEAF_REDIS_HOST = "redis"; + REDIS_HOST = "redis"; + }; + + inherit (lib) + mkEnableOption + mkIf + mkOption + mkOverride + optional + types + ; + inherit (lib.utils) + mkReverseProxyOption + mkVirtualHost + ; +in +{ + options.services.overleaf-oci = { + enable = mkEnableOption "Overleaf service stack with Podman"; + port = mkOption { + type = types.port; + default = 80; + description = "The port Overleaf will listen on."; + }; + environment = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Extra environment variables for Overleaf."; + }; + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Environment file for secrets."; + }; + reverseProxy = mkReverseProxyOption "Overleaf" "overleaf"; + }; + + config = mkIf cfg.enable { + virtualisation.podman = { + enable = true; + autoPrune.enable = true; + dockerCompat = true; + }; + + services.nginx.virtualHosts = mkIf cfg.reverseProxy.enable { + "${fqdn}" = mkVirtualHost { + inherit (cfg) port; + ssl = cfg.reverseProxy.forceSSL; + }; + }; + + networking.firewall.interfaces = + let + matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; + in + { + "${matchAll}".allowedUDPPorts = [ 53 ]; + }; + + virtualisation.oci-containers.backend = "podman"; + + virtualisation.oci-containers.containers = { + overleaf-mongo = { + image = with images.mongo; imageName + ":" + imageTag; + imageFile = images.mongo; + environment = { + MONGO_INITDB_DATABASE = "sharelatex"; + }; + volumes = [ + "overleaf_mongo-data:/data/db:rw" + "${mongodbInitReplicaSet}:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js:rw" + ]; + cmd = [ + "--replSet" + "overleaf" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=mongo" + "--network=overleaf_default" + "--add-host=mongo:127.0.0.1" + "--health-cmd=echo 'db.stats().ok' | mongosh localhost:27017/test --quiet" + "--health-interval=10s" + "--health-retries=5" + "--health-timeout=10s" + ]; + }; + + overleaf-redis = { + image = with images.redis; imageName + ":" + imageTag; + imageFile = images.redis; + volumes = [ "overleaf_redis-data:/data:rw" ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=redis" + "--network=overleaf_default" + ]; + }; + + overleaf-sharelatex = { + image = with images.sharelatex; imageName + ":" + imageTag; + imageFile = images.sharelatex; + log-driver = "journald"; + environment = defaultEnv // cfg.environment; + environmentFiles = optional (cfg.environmentFile != null) cfg.environmentFile; + volumes = [ "overleaf_data:/var/lib/overleaf:rw" ]; + ports = [ "${toString cfg.port}:80/tcp" ]; + extraOptions = [ + "--network-alias=sharelatex" + "--network=overleaf_default" + ]; + }; + }; + + systemd.services = + let + commonServiceCfg = { + serviceConfig.Restart = mkOverride 90 "always"; + partOf = [ "podman-compose-overleaf-root.target" ]; + wantedBy = [ "podman-compose-overleaf-root.target" ]; + }; + in + { + podman-overleaf-mongo = commonServiceCfg // { + after = [ + "podman-network-overleaf_default.service" + "podman-volume-overleaf_mongo-data.service" + ]; + requires = [ + "podman-network-overleaf_default.service" + "podman-volume-overleaf_mongo-data.service" + ]; + }; + + podman-overleaf-redis = commonServiceCfg // { + after = [ + "podman-network-overleaf_default.service" + "podman-volume-overleaf_redis-data.service" + ]; + requires = [ + "podman-network-overleaf_default.service" + "podman-volume-overleaf_redis-data.service" + ]; + }; + + podman-overleaf-sharelatex = commonServiceCfg // { + after = [ + "podman-network-overleaf_default.service" + "podman-overleaf-mongo.service" + "podman-overleaf-redis.service" + ]; + requires = [ + "podman-network-overleaf_default.service" + "podman-overleaf-mongo.service" + "podman-overleaf-redis.service" + ]; + serviceConfig.ExecStartPre = [ + "${pkgs.bash}/bin/bash -c 'until ${pkgs.podman}/bin/podman healthcheck run overleaf-mongo; do sleep 2; done'" + ]; + }; + + podman-network-overleaf_default = { + path = [ pkgs.podman ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "podman network rm -f overleaf_default"; + }; + script = '' + podman network inspect overleaf_default || podman network create overleaf_default + ''; + partOf = [ "podman-compose-overleaf-root.target" ]; + wantedBy = [ "podman-compose-overleaf-root.target" ]; + }; + + podman-volume-overleaf_redis-data = { + path = [ pkgs.podman ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + podman volume inspect overleaf_redis-data || podman volume create overleaf_redis-data + ''; + partOf = [ "podman-compose-overleaf-root.target" ]; + wantedBy = [ "podman-compose-overleaf-root.target" ]; + }; + + podman-volume-overleaf_mongo-data = { + path = [ pkgs.podman ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + podman volume inspect overleaf_mongo-data || podman volume create overleaf_mongo-data + ''; + partOf = [ "podman-compose-overleaf-root.target" ]; + wantedBy = [ "podman-compose-overleaf-root.target" ]; + }; + }; + + systemd.targets."podman-compose-overleaf-root" = { + unitConfig.Description = "Root target for Overleaf services."; + wantedBy = [ "multi-user.target" ]; + }; + }; +} diff --git a/modules/nixos/overleaf-oci/mongodb-init-replica-set.js b/modules/nixos/overleaf-oci/mongodb-init-replica-set.js new file mode 100644 index 0000000..30af660 --- /dev/null +++ b/modules/nixos/overleaf-oci/mongodb-init-replica-set.js @@ -0,0 +1,3 @@ +/* eslint-disable no-undef */ + +rs.initiate({ _id: 'overleaf', members: [{ _id: 0, host: 'mongo:27017' }] }) -- 2.51.2