diff options
Diffstat (limited to 'hostnix/elmo/typetype.nix')
| -rw-r--r-- | hostnix/elmo/typetype.nix | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/hostnix/elmo/typetype.nix b/hostnix/elmo/typetype.nix new file mode 100644 index 0000000..e94f5f8 --- /dev/null +++ b/hostnix/elmo/typetype.nix @@ -0,0 +1,158 @@ +{ lib, pkgs, ... }: + +# Self-hosted TypeType instance: https://github.com/TypeType-Video/TypeType +# +# Upstream only ships container images, so this is a translation of their +# docker-compose.yml rather than a native service. Omitted from the upstream +# stack: typetype-downloader, garage, garage-config (the download/S3 +# subsystem) and typetype-secrets (replaced by /var/secrets, below). + +# TODO downloads: needs typetype-downloader + a Garage bucket bootstrapped by +# hand (scripts/bootstrap-garage.sh does layout assign / bucket create / key +# create), plus the typetype_downloader database. +# TODO SSO + +let + network = "typetype"; + + # The frontend image's nginx resolves these names over Docker's embedded DNS + # (resolver 127.0.0.11), so retain the original container names. + containers = [ + "typetype" + "typetype-server" + "typetype-token" + "typetype-postgres" + "typetype-dragonfly" + ]; + + # Pin by version tag and digest. + images = { + web = "ghcr.io/typetype-video/typetype:1.3.1@sha256:4da200fb96d858cfa3bc2a8cbb98a9682a560f40a055b9c407f3e173a28dcf82"; + server = "ghcr.io/typetype-video/typetype-server:1.3.1@sha256:f1ad7fd31e5c1cb994601f714df82e8207c3769d759df232e21a3876751a8faf"; + token = "ghcr.io/typetype-video/typetype-token:1.3.1@sha256:8dfcc6d84cc09c33d18add0ec807093c2182be10857a021a4c61ace9a3f561d5"; + }; + + secrets = "/var/secrets/typetype"; +in + +{ + systemd.tmpfiles.rules = [ + "d /var/lib/typetype 0750 root root -" + # Bind mounted rather than a Docker volume so backup.nix picks it up; 999 + # is the postgres uid inside the image. + "d /var/lib/typetype/postgres 0700 999 999 -" + "d ${secrets} 0750 root root -" + ]; + + systemd.services = + lib.genAttrs (map (c: "docker-${c}") containers) (_: { + after = [ "docker-network-typetype.service" ]; + requires = [ "docker-network-typetype.service" ]; + unitConfig.AssertPathExists = "${secrets}/env"; + }) + // { + # Initially create network. + docker-network-typetype = { + wantedBy = [ "multi-user.target" ]; + after = [ "docker.service" ]; + requires = [ "docker.service" ]; + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + docker network inspect ${network} >/dev/null 2>&1 || + docker network create ${network} + ''; + }; + }; + + virtualisation.oci-containers.containers = { + typetype = { + image = images.web; + networks = [ network ]; + dependsOn = [ + "typetype-server" + "typetype-token" + ]; + ports = [ "127.0.0.1:8082:80" ]; + }; + + typetype-server = { + image = images.server; + networks = [ network ]; + dependsOn = [ + "typetype-postgres" + "typetype-dragonfly" + "typetype-token" + ]; + # Sets DATABASE_PASSWORD. + environmentFiles = [ "${secrets}/env" ]; + environment = { + ALLOWED_ORIGINS = "https://tt.elmo.mou.fo"; + DATABASE_URL = "jdbc:postgresql://typetype-postgres:5432/typetype"; + DATABASE_USER = "typetype"; + DRAGONFLY_URL = "redis://typetype-dragonfly:6379"; + YOUTUBE_REMOTE_LOGIN_ENABLED = "false"; + YOUTUBE_REMOTE_LOGIN_SERVICE_URL = "http://typetype-token:8081"; + YOUTUBE_REMOTE_LOGIN_CALLBACK_BASE_URL = "http://typetype-server:8080"; + YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN_FILE = "/run/typetype-secrets/youtube_remote_login_internal_token"; + YOUTUBE_SESSION_ENCRYPTION_KEY_FILE = "/run/typetype-secrets/youtube_session_encryption_key"; + }; + volumes = [ "${secrets}:/run/typetype-secrets:ro" ]; + }; + + typetype-token = { + image = images.token; + networks = [ network ]; + environment = { + NODE_ENV = "production"; + YOUTUBE_REMOTE_LOGIN_ENABLED = "false"; + YOUTUBE_REMOTE_LOGIN_INTERNAL_TOKEN_FILE = "/run/typetype-secrets/youtube_remote_login_internal_token"; + }; + volumes = [ "${secrets}:/run/typetype-secrets:ro" ]; + # --ipc=host is upstream's; it only matters once remote login is enabled + # and the service starts driving a headless browser. + extraOptions = [ + "--init" + "--ipc=host" + ]; + }; + + typetype-postgres = { + image = "postgres:17"; + networks = [ network ]; + # Sets POSTGRES_PASSWORD. + environmentFiles = [ "${secrets}/env" ]; + environment = { + POSTGRES_DB = "typetype"; + POSTGRES_USER = "typetype"; + }; + volumes = [ "/var/lib/typetype/postgres:/var/lib/postgresql/data" ]; + }; + + typetype-dragonfly = { + image = "docker.dragonflydb.io/dragonflydb/dragonfly:v1.39.0"; + networks = [ network ]; + extraOptions = [ + "--ulimit" + "memlock=-1" + ]; + }; + }; + + # TODO allocate domain + services.nginx.virtualHosts."tt.elmo.mou.fo" = { + useACMEHost = "elmo.mou.fo"; + forceSSL = true; + locations."/" = { + proxyPass = "http://127.0.0.1:8082"; + proxyWebsockets = true; + }; + # Matches client_max_body_size in the frontend image's nginx.conf. + extraConfig = '' + client_max_body_size 2g; + ''; + }; +} |
