335 lines
10 KiB
Nix
335 lines
10 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
|
|
with lib;
|
|
let
|
|
# Piped domains
|
|
domain = "piped.cloonar.com";
|
|
apiDomain = "pipedapi.cloonar.com";
|
|
|
|
# Port configuration
|
|
backendPort = 8082;
|
|
proxyPort = 8081;
|
|
bgHelperPort = 3000;
|
|
|
|
# Database configuration
|
|
dbName = "piped";
|
|
dbUser = "piped";
|
|
|
|
# Piped backend configuration file
|
|
backendConfig = pkgs.writeText "config.properties" ''
|
|
# Database configuration
|
|
# 10.88.0.1 is the default Podman bridge gateway IP
|
|
hibernate.connection.url=jdbc:postgresql://10.89.0.1:5432/${dbName}
|
|
hibernate.connection.driver_class=org.postgresql.Driver
|
|
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
|
hibernate.connection.username=${dbUser}
|
|
hibernate.connection.password=PLACEHOLDER_DB_PASSWORD
|
|
|
|
# Server configuration
|
|
PORT=${toString backendPort}
|
|
HTTP_WORKERS=2
|
|
# Proxy configuration
|
|
PROXY_PART=https://${apiDomain}/proxy
|
|
|
|
# API URL
|
|
API_URL=https://${apiDomain}
|
|
|
|
# Frontend URL
|
|
FRONTEND_URL=https://${domain}
|
|
|
|
# Disable registration (private instance)
|
|
DISABLE_REGISTRATION=false
|
|
|
|
# ReCaptcha (disabled for private instance)
|
|
CAPTCHA_ENABLED=false
|
|
|
|
# Matrix support (optional)
|
|
MATRIX_SERVER=
|
|
|
|
# Sentry (disabled)
|
|
SENTRY_DSN=
|
|
|
|
# Compromised password check (optional)
|
|
COMPROMISED_PASSWORD_CHECK=true
|
|
|
|
# Feed retention (days)
|
|
FEED_RETENTION=30
|
|
|
|
# Background helper for iOS compatibility (generates PoTokens)
|
|
BG_HELPER_URL=http://piped-bg-helper:${toString bgHelperPort}
|
|
'';
|
|
|
|
# Piped frontend configuration
|
|
# Note: Piped requires the config in a specific format as a JSON file served at /config/config.json
|
|
frontendConfig = pkgs.writeText "config.json" (builtins.toJSON {
|
|
apiUrl = "https://${apiDomain}";
|
|
disableRegistration = false;
|
|
instanceName = "Piped (Private)";
|
|
});
|
|
in
|
|
{
|
|
sops.secrets = {
|
|
# Database password for postgres user (used by piped-db-init)
|
|
piped-db-password-postgres = {
|
|
key = "piped-db-password";
|
|
owner = "postgres";
|
|
};
|
|
# Database password for piped user (used by piped-config-generate)
|
|
piped-db-password-piped = {
|
|
key = "piped-db-password";
|
|
owner = "piped";
|
|
};
|
|
};
|
|
|
|
# Create system user for Piped
|
|
users.users.piped = {
|
|
isSystemUser = true;
|
|
group = "piped";
|
|
home = "/var/lib/piped";
|
|
createHome = true;
|
|
};
|
|
users.groups.piped = { };
|
|
|
|
# Note: piped user doesn't need special group membership for Podman
|
|
|
|
# Create piped config directory structure
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/piped 0700 piped piped - -"
|
|
"d /var/lib/piped/config 0700 piped piped - -"
|
|
];
|
|
|
|
# PostgreSQL database setup
|
|
services.postgresql = {
|
|
enable = true;
|
|
ensureDatabases = [ dbName ];
|
|
ensureUsers = [{
|
|
name = dbUser;
|
|
ensureDBOwnership = true;
|
|
}];
|
|
# Allow connections from Podman containers
|
|
settings = {
|
|
listen_addresses = mkForce "*";
|
|
};
|
|
authentication = pkgs.lib.mkOverride 10 ''
|
|
# Allow local connections
|
|
local all all trust
|
|
# Allow connections from localhost
|
|
host all all 127.0.0.1/32 trust
|
|
host all all ::1/128 trust
|
|
# Allow connections from Podman network (typically 10.88.0.0/16)
|
|
host ${dbName} ${dbUser} 10.88.0.0/16 scram-sha-256
|
|
host ${dbName} ${dbUser} 10.89.0.0/16 scram-sha-256
|
|
'';
|
|
};
|
|
|
|
# PostgreSQL backup
|
|
services.postgresqlBackup.databases = [ dbName ];
|
|
|
|
# Allow Podman containers to connect to PostgreSQL
|
|
networking.firewall.interfaces."podman1".allowedTCPPorts = [ 5432 ];
|
|
networking.firewall.interfaces."podman1".allowedUDPPorts = [ 53 5432 ];
|
|
|
|
# Setup database password (runs before containers start)
|
|
systemd.services.piped-db-init = {
|
|
description = "Initialize Piped database password";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "postgresql.service" ];
|
|
requires = [ "postgresql.service" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
User = "postgres";
|
|
Group = "postgres";
|
|
};
|
|
script = ''
|
|
password=$(cat ${config.sops.secrets.piped-db-password-postgres.path})
|
|
${config.services.postgresql.package}/bin/psql -c "ALTER USER ${dbUser} WITH PASSWORD '$password';"
|
|
'';
|
|
};
|
|
|
|
# Create Piped backend config with actual password
|
|
systemd.services.piped-config-generate = {
|
|
description = "Generate Piped backend configuration";
|
|
wantedBy = [ "multi-user.target" ];
|
|
before = [ "podman-piped-backend.service" ];
|
|
after = [ "piped-db-init.service" ];
|
|
requires = [ "piped-db-init.service" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
User = "piped";
|
|
Group = "piped";
|
|
};
|
|
script = ''
|
|
mkdir -p /var/lib/piped/config
|
|
password=$(cat ${config.sops.secrets.piped-db-password-piped.path})
|
|
sed "s|PLACEHOLDER_DB_PASSWORD|$password|" ${backendConfig} > /var/lib/piped/config/config.properties
|
|
chmod 600 /var/lib/piped/config/config.properties
|
|
'';
|
|
};
|
|
|
|
# Use Podman for OCI containers
|
|
virtualisation.oci-containers.backend = "podman";
|
|
|
|
# Create Piped network for container-to-container communication
|
|
systemd.services.init-piped-network = {
|
|
description = "Create Podman network for Piped services";
|
|
wantedBy = [ "multi-user.target" ];
|
|
before = [
|
|
"podman-piped-backend.service"
|
|
"podman-piped-bg-helper.service"
|
|
"podman-piped-proxy.service"
|
|
];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
};
|
|
script = ''
|
|
${pkgs.podman}/bin/podman network exists piped-net || \
|
|
${pkgs.podman}/bin/podman network create --interface-name=podman1 --subnet=10.89.0.0/24 piped-net
|
|
'';
|
|
};
|
|
|
|
# Piped Backend Podman container (using official upstream image)
|
|
virtualisation.oci-containers.containers.piped-backend = {
|
|
image = "1337kavin/piped:latest";
|
|
ports = [ "127.0.0.1:${toString backendPort}:${toString backendPort}" ];
|
|
volumes = [
|
|
"/var/lib/piped/config/config.properties:/app/config.properties:ro"
|
|
];
|
|
extraOptions = [
|
|
"--pull=newer"
|
|
"--network=podman" # Default bridge for PostgreSQL access at 10.88.0.1
|
|
"--network=piped-net" # Custom network for DNS resolution to bg-helper
|
|
];
|
|
};
|
|
|
|
# Ensure config is generated before backend container starts
|
|
systemd.services."podman-piped-backend" = {
|
|
after = mkAfter [ "piped-config-generate.service" ];
|
|
requires = mkAfter [ "piped-config-generate.service" ];
|
|
};
|
|
|
|
# Piped Background Helper (generates PoTokens for iOS compatibility)
|
|
virtualisation.oci-containers.containers.piped-bg-helper = {
|
|
image = "1337kavin/bg-helper-server:latest";
|
|
ports = [ "127.0.0.1:${toString bgHelperPort}:3000" ];
|
|
extraOptions = [
|
|
"--pull=newer"
|
|
"--network=piped-net"
|
|
];
|
|
};
|
|
|
|
# Piped Proxy Podman container
|
|
virtualisation.oci-containers.containers.piped-proxy = {
|
|
image = "1337kavin/piped-proxy:latest";
|
|
ports = [ "127.0.0.1:${toString proxyPort}:8080" ];
|
|
environment = {
|
|
UDS = "0"; # Disable Unix domain sockets
|
|
};
|
|
extraOptions = [
|
|
"--pull=newer"
|
|
"--network=piped-net"
|
|
];
|
|
};
|
|
|
|
# Nginx configuration
|
|
services.nginx.virtualHosts.${domain} = {
|
|
forceSSL = true;
|
|
enableACME = true;
|
|
acmeRoot = null;
|
|
|
|
# Serve Piped frontend static files
|
|
root = "${pkgs.piped}";
|
|
|
|
# Frontend (root)
|
|
locations."/" = {
|
|
extraConfig = ''
|
|
try_files $uri $uri/ /index.html;
|
|
'';
|
|
};
|
|
|
|
# Serve custom frontend config
|
|
locations."= /config/config.json" = {
|
|
alias = frontendConfig;
|
|
extraConfig = ''
|
|
add_header Content-Type application/json;
|
|
add_header Access-Control-Allow-Origin *;
|
|
'';
|
|
};
|
|
};
|
|
|
|
# API and Proxy domain
|
|
services.nginx.virtualHosts.${apiDomain} = {
|
|
forceSSL = true;
|
|
enableACME = true;
|
|
acmeRoot = null;
|
|
|
|
# Backend API (served at root)
|
|
locations."/" = {
|
|
proxyPass = "http://127.0.0.1:${toString backendPort}/";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
# Hide CORS headers from backend to avoid duplicates
|
|
proxy_hide_header Access-Control-Allow-Origin;
|
|
proxy_hide_header Access-Control-Allow-Methods;
|
|
proxy_hide_header Access-Control-Allow-Headers;
|
|
proxy_hide_header Access-Control-Expose-Headers;
|
|
proxy_hide_header Access-Control-Allow-Credentials;
|
|
|
|
# CORS headers for iOS API requests
|
|
add_header Access-Control-Allow-Origin * always;
|
|
add_header Access-Control-Allow-Methods "GET, POST, HEAD, OPTIONS" always;
|
|
add_header Access-Control-Allow-Headers "Range, Content-Type, Authorization" always;
|
|
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
|
|
|
|
# Handle preflight requests
|
|
if ($request_method = OPTIONS) {
|
|
return 204;
|
|
}
|
|
|
|
# Increase timeouts for long-running requests
|
|
proxy_connect_timeout 600s;
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
'';
|
|
};
|
|
|
|
# YouTube Proxy
|
|
locations."/proxy/" = {
|
|
proxyPass = "http://127.0.0.1:${toString proxyPort}/";
|
|
extraConfig = ''
|
|
# Hide CORS headers from proxy to avoid duplicates
|
|
proxy_hide_header Access-Control-Allow-Origin;
|
|
proxy_hide_header Access-Control-Allow-Methods;
|
|
proxy_hide_header Access-Control-Allow-Headers;
|
|
proxy_hide_header Access-Control-Expose-Headers;
|
|
proxy_hide_header Access-Control-Allow-Credentials;
|
|
|
|
# CORS headers for iOS HLS video streaming
|
|
add_header Access-Control-Allow-Origin * always;
|
|
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
|
add_header Access-Control-Allow-Headers "Range, Content-Type" always;
|
|
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
|
|
|
|
# Handle preflight requests
|
|
if ($request_method = OPTIONS) {
|
|
return 204;
|
|
}
|
|
|
|
proxy_buffering on;
|
|
|
|
# Increase buffer sizes for video streaming
|
|
proxy_buffer_size 128k;
|
|
proxy_buffers 256 16k;
|
|
proxy_busy_buffers_size 256k;
|
|
|
|
# Increase timeouts for video streaming
|
|
proxy_connect_timeout 600s;
|
|
proxy_send_timeout 600s;
|
|
proxy_read_timeout 600s;
|
|
'';
|
|
};
|
|
};
|
|
}
|