{ 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; ''; }; }