From 9f61b06592e2abd33f13148945abab5e40fa93fe Mon Sep 17 00:00:00 2001 From: knight Date: Tue, 20 Jan 2026 12:52:19 -0500 Subject: [PATCH] Update invidious stack to use companion approach - Replace inv_sig_helper with invidious-companion for better YouTube API handling - Add healthcheck for main container - Add resource limits for all containers - Add SQL init scripts for fresh database setup - Update README with invidious secrets documentation Co-Authored-By: Claude Opus 4.5 --- README.md | 8 +++ stacks/invidious/.env.template | 3 +- stacks/invidious/config/sql/annotations.sql | 12 ++++ .../invidious/config/sql/channel_videos.sql | 30 ++++++++ stacks/invidious/config/sql/channels.sql | 25 +++++++ stacks/invidious/config/sql/nonces.sql | 22 ++++++ .../invidious/config/sql/playlist_videos.sql | 19 +++++ stacks/invidious/config/sql/playlists.sql | 29 ++++++++ stacks/invidious/config/sql/session_ids.sql | 23 +++++++ stacks/invidious/config/sql/users.sql | 29 ++++++++ stacks/invidious/config/sql/videos.sql | 23 +++++++ stacks/invidious/docker-compose.yml | 69 +++++++++++++++---- stacks/invidious/docker/init-invidious-db.sh | 12 ++++ 13 files changed, 289 insertions(+), 15 deletions(-) create mode 100644 stacks/invidious/config/sql/annotations.sql create mode 100644 stacks/invidious/config/sql/channel_videos.sql create mode 100644 stacks/invidious/config/sql/channels.sql create mode 100644 stacks/invidious/config/sql/nonces.sql create mode 100644 stacks/invidious/config/sql/playlist_videos.sql create mode 100644 stacks/invidious/config/sql/playlists.sql create mode 100644 stacks/invidious/config/sql/session_ids.sql create mode 100644 stacks/invidious/config/sql/users.sql create mode 100644 stacks/invidious/config/sql/videos.sql create mode 100755 stacks/invidious/docker/init-invidious-db.sh diff --git a/README.md b/README.md index 65cdb79..a7d61f4 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ stacks/ ├── perilous/ # Blog/website ├── ramz/ # Go web app ├── bookclub/ # Form mailer +├── invidious/ # YouTube frontend ├── watchtower/ # Auto container updates ├── dockge/ # Container management UI └── smokeping/ # Network monitoring @@ -96,6 +97,13 @@ Set these in Gitea → Repository → Settings → Actions → Secrets: |--------|-------------| | `PERILOUS_CODE_SERVER_PASSWORD` | Code-server password | +### Invidious +| Secret | Description | +|--------|-------------| +| `INVIDIOUS_DB_PASSWORD` | PostgreSQL password | +| `INVIDIOUS_HMAC_KEY` | Generate: `openssl rand -hex 16` | +| `INVIDIOUS_COMPANION_KEY` | Generate: `openssl rand -hex 16` (must match companion) | + ## Runner Setup The workflow requires a self-hosted runner on the prod server: diff --git a/stacks/invidious/.env.template b/stacks/invidious/.env.template index 86a1f3d..d15c19e 100644 --- a/stacks/invidious/.env.template +++ b/stacks/invidious/.env.template @@ -1,5 +1,4 @@ DOMAIN=${DOMAIN} INVIDIOUS_DB_PASSWORD=${INVIDIOUS_DB_PASSWORD} -INVIDIOUS_VISITOR_DATA=${INVIDIOUS_VISITOR_DATA} -INVIDIOUS_PO_TOKEN=${INVIDIOUS_PO_TOKEN} INVIDIOUS_HMAC_KEY=${INVIDIOUS_HMAC_KEY} +INVIDIOUS_COMPANION_KEY=${INVIDIOUS_COMPANION_KEY} diff --git a/stacks/invidious/config/sql/annotations.sql b/stacks/invidious/config/sql/annotations.sql new file mode 100644 index 0000000..3705829 --- /dev/null +++ b/stacks/invidious/config/sql/annotations.sql @@ -0,0 +1,12 @@ +-- Table: public.annotations + +-- DROP TABLE public.annotations; + +CREATE TABLE IF NOT EXISTS public.annotations +( + id text NOT NULL, + annotations xml, + CONSTRAINT annotations_id_key UNIQUE (id) +); + +GRANT ALL ON TABLE public.annotations TO current_user; diff --git a/stacks/invidious/config/sql/channel_videos.sql b/stacks/invidious/config/sql/channel_videos.sql new file mode 100644 index 0000000..cd4e0ff --- /dev/null +++ b/stacks/invidious/config/sql/channel_videos.sql @@ -0,0 +1,30 @@ +-- Table: public.channel_videos + +-- DROP TABLE public.channel_videos; + +CREATE TABLE IF NOT EXISTS public.channel_videos +( + id text NOT NULL, + title text, + published timestamp with time zone, + updated timestamp with time zone, + ucid text, + author text, + length_seconds integer, + live_now boolean, + premiere_timestamp timestamp with time zone, + views bigint, + CONSTRAINT channel_videos_id_key UNIQUE (id) +); + +GRANT ALL ON TABLE public.channel_videos TO current_user; + +-- Index: public.channel_videos_ucid_idx + +-- DROP INDEX public.channel_videos_ucid_idx; + +CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx + ON public.channel_videos + USING btree + (ucid COLLATE pg_catalog."default"); + diff --git a/stacks/invidious/config/sql/channels.sql b/stacks/invidious/config/sql/channels.sql new file mode 100644 index 0000000..55772da --- /dev/null +++ b/stacks/invidious/config/sql/channels.sql @@ -0,0 +1,25 @@ +-- Table: public.channels + +-- DROP TABLE public.channels; + +CREATE TABLE IF NOT EXISTS public.channels +( + id text NOT NULL, + author text, + updated timestamp with time zone, + deleted boolean, + subscribed timestamp with time zone, + CONSTRAINT channels_id_key UNIQUE (id) +); + +GRANT ALL ON TABLE public.channels TO current_user; + +-- Index: public.channels_id_idx + +-- DROP INDEX public.channels_id_idx; + +CREATE INDEX IF NOT EXISTS channels_id_idx + ON public.channels + USING btree + (id COLLATE pg_catalog."default"); + diff --git a/stacks/invidious/config/sql/nonces.sql b/stacks/invidious/config/sql/nonces.sql new file mode 100644 index 0000000..644ac32 --- /dev/null +++ b/stacks/invidious/config/sql/nonces.sql @@ -0,0 +1,22 @@ +-- Table: public.nonces + +-- DROP TABLE public.nonces; + +CREATE TABLE IF NOT EXISTS public.nonces +( + nonce text, + expire timestamp with time zone, + CONSTRAINT nonces_id_key UNIQUE (nonce) +); + +GRANT ALL ON TABLE public.nonces TO current_user; + +-- Index: public.nonces_nonce_idx + +-- DROP INDEX public.nonces_nonce_idx; + +CREATE INDEX IF NOT EXISTS nonces_nonce_idx + ON public.nonces + USING btree + (nonce COLLATE pg_catalog."default"); + diff --git a/stacks/invidious/config/sql/playlist_videos.sql b/stacks/invidious/config/sql/playlist_videos.sql new file mode 100644 index 0000000..4b48b46 --- /dev/null +++ b/stacks/invidious/config/sql/playlist_videos.sql @@ -0,0 +1,19 @@ +-- Table: public.playlist_videos + +-- DROP TABLE public.playlist_videos; + +CREATE TABLE IF NOT EXISTS public.playlist_videos +( + title text, + id text, + author text, + ucid text, + length_seconds integer, + published timestamptz, + plid text references playlists(id), + index int8, + live_now boolean, + PRIMARY KEY (index,plid) +); + +GRANT ALL ON TABLE public.playlist_videos TO current_user; diff --git a/stacks/invidious/config/sql/playlists.sql b/stacks/invidious/config/sql/playlists.sql new file mode 100644 index 0000000..83efce4 --- /dev/null +++ b/stacks/invidious/config/sql/playlists.sql @@ -0,0 +1,29 @@ +-- Type: public.privacy + +-- DROP TYPE public.privacy; + +CREATE TYPE public.privacy AS ENUM +( + 'Public', + 'Unlisted', + 'Private' +); + +-- Table: public.playlists + +-- DROP TABLE public.playlists; + +CREATE TABLE IF NOT EXISTS public.playlists +( + title text, + id text primary key, + author text, + description text, + video_count integer, + created timestamptz, + updated timestamptz, + privacy privacy, + index int8[] +); + +GRANT ALL ON public.playlists TO current_user; diff --git a/stacks/invidious/config/sql/session_ids.sql b/stacks/invidious/config/sql/session_ids.sql new file mode 100644 index 0000000..c493769 --- /dev/null +++ b/stacks/invidious/config/sql/session_ids.sql @@ -0,0 +1,23 @@ +-- Table: public.session_ids + +-- DROP TABLE public.session_ids; + +CREATE TABLE IF NOT EXISTS public.session_ids +( + id text NOT NULL, + email text, + issued timestamp with time zone, + CONSTRAINT session_ids_pkey PRIMARY KEY (id) +); + +GRANT ALL ON TABLE public.session_ids TO current_user; + +-- Index: public.session_ids_id_idx + +-- DROP INDEX public.session_ids_id_idx; + +CREATE INDEX IF NOT EXISTS session_ids_id_idx + ON public.session_ids + USING btree + (id COLLATE pg_catalog."default"); + diff --git a/stacks/invidious/config/sql/users.sql b/stacks/invidious/config/sql/users.sql new file mode 100644 index 0000000..ad002ec --- /dev/null +++ b/stacks/invidious/config/sql/users.sql @@ -0,0 +1,29 @@ +-- Table: public.users + +-- DROP TABLE public.users; + +CREATE TABLE IF NOT EXISTS public.users +( + updated timestamp with time zone, + notifications text[], + subscriptions text[], + email text NOT NULL, + preferences text, + password text, + token text, + watched text[], + feed_needs_update boolean, + CONSTRAINT users_email_key UNIQUE (email) +); + +GRANT ALL ON TABLE public.users TO current_user; + +-- Index: public.email_unique_idx + +-- DROP INDEX public.email_unique_idx; + +CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx + ON public.users + USING btree + (lower(email) COLLATE pg_catalog."default"); + diff --git a/stacks/invidious/config/sql/videos.sql b/stacks/invidious/config/sql/videos.sql new file mode 100644 index 0000000..55da396 --- /dev/null +++ b/stacks/invidious/config/sql/videos.sql @@ -0,0 +1,23 @@ +-- Table: public.videos + +-- DROP TABLE public.videos; + +CREATE UNLOGGED TABLE IF NOT EXISTS public.videos +( + id text NOT NULL, + info text, + updated timestamp with time zone, + CONSTRAINT videos_pkey PRIMARY KEY (id) +); + +GRANT ALL ON TABLE public.videos TO current_user; + +-- Index: public.id_idx + +-- DROP INDEX public.id_idx; + +CREATE UNIQUE INDEX IF NOT EXISTS id_idx + ON public.videos + USING btree + (id COLLATE pg_catalog."default"); + diff --git a/stacks/invidious/docker-compose.yml b/stacks/invidious/docker-compose.yml index d70c175..3d98c26 100644 --- a/stacks/invidious/docker-compose.yml +++ b/stacks/invidious/docker-compose.yml @@ -3,8 +3,6 @@ services: image: quay.io/invidious/invidious:latest container_name: invidious restart: unless-stopped - ports: - - "3001:3000" environment: INVIDIOUS_CONFIG: | db: @@ -14,45 +12,81 @@ services: host: invidious-db port: 5432 check_tables: true - signature_server: inv_sig_helper:12999 - visitor_data: ${INVIDIOUS_VISITOR_DATA} - po_token: ${INVIDIOUS_PO_TOKEN} + invidious_companion: + - private_url: "http://invidious-companion:8282/companion" + public_url: "https://inv.${DOMAIN}" + invidious_companion_key: "${INVIDIOUS_COMPANION_KEY}" + external_port: 443 + domain: inv.${DOMAIN} + https_only: true hmac_key: "${INVIDIOUS_HMAC_KEY}" + hsts: false + log_level: Info + healthcheck: + test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1 + interval: 30s + timeout: 5s + retries: 2 logging: options: max-size: "1G" max-file: "4" depends_on: - invidious-db + deploy: + resources: + limits: + cpus: '1' + memory: 2G labels: - "traefik.enable=true" - "traefik.http.services.invidious.loadbalancer.server.port=3000" - "traefik.http.routers.invidious.entrypoints=https" - - "traefik.http.routers.invidious.rule=Host(`invid.${DOMAIN}`, `i.${DOMAIN}`)" + - "traefik.http.routers.invidious.rule=Host(`inv.${DOMAIN}`)" - "traefik.http.routers.invidious.tls.certresolver=http" networks: - web - default - inv_sig_helper: - image: quay.io/invidious/inv-sig-helper:latest - container_name: inv-sig-helper - command: ["--tcp", "0.0.0.0:12999"] - environment: - - RUST_LOG=info + invidious-companion: + image: quay.io/invidious/invidious-companion:latest + container_name: invidious-companion restart: unless-stopped + environment: + - SERVER_SECRET_KEY=${INVIDIOUS_COMPANION_KEY} + - SERVER_BASE_URL=https://inv.${DOMAIN}/companion + logging: + options: + max-size: "1G" + max-file: "4" cap_drop: - ALL read_only: true + volumes: + - companion-cache:/var/tmp/youtubei.js:rw security_opt: - no-new-privileges:true + deploy: + resources: + limits: + cpus: '0.5' + memory: 1G + labels: + - "traefik.enable=true" + - "traefik.http.services.invidious-companion.loadbalancer.server.port=8282" + - "traefik.http.routers.invidious-companion.entrypoints=https" + - "traefik.http.routers.invidious-companion.rule=Host(`inv.${DOMAIN}`) && PathPrefix(`/companion`)" + - "traefik.http.routers.invidious-companion.tls.certresolver=http" + networks: + - web + - default invidious-db: image: postgres:14 container_name: invidious-db restart: unless-stopped volumes: - - ./postgres:/var/lib/postgresql/data + - postgres-data:/var/lib/postgresql/data - ./config/sql:/config/sql - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh environment: @@ -61,6 +95,15 @@ services: POSTGRES_PASSWORD: ${INVIDIOUS_DB_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + deploy: + resources: + limits: + cpus: '0.5' + memory: 1G + +volumes: + postgres-data: + companion-cache: networks: web: diff --git a/stacks/invidious/docker/init-invidious-db.sh b/stacks/invidious/docker/init-invidious-db.sh new file mode 100755 index 0000000..22b4cc5 --- /dev/null +++ b/stacks/invidious/docker/init-invidious-db.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -eou pipefail + +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/channels.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/videos.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/channel_videos.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/users.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/session_ids.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/nonces.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/annotations.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/playlists.sql +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/playlist_videos.sql