summaryrefslogtreecommitdiff
path: root/invidious
diff options
context:
space:
mode:
Diffstat (limited to 'invidious')
-rw-r--r--invidious/README.md28
-rw-r--r--invidious/config/sql/annotations.sql12
-rw-r--r--invidious/config/sql/channel_videos.sql30
-rw-r--r--invidious/config/sql/channels.sql25
-rw-r--r--invidious/config/sql/nonces.sql22
-rw-r--r--invidious/config/sql/playlist_videos.sql19
-rw-r--r--invidious/config/sql/playlists.sql29
-rw-r--r--invidious/config/sql/session_ids.sql23
-rw-r--r--invidious/config/sql/users.sql29
-rw-r--r--invidious/config/sql/videos.sql23
-rw-r--r--invidious/docker-compose.yml81
-rw-r--r--invidious/docker/Dockerfile81
-rwxr-xr-xinvidious/docker/init-invidious-db.sh12
13 files changed, 414 insertions, 0 deletions
diff --git a/invidious/README.md b/invidious/README.md
new file mode 100644
index 0000000..fbfa29f
--- /dev/null
+++ b/invidious/README.md
@@ -0,0 +1,28 @@
+# Invidious
+
+Self-hosted YouTube frontend. No ads, no tracking.
+
+## Setup
+
+1. Generate required keys:
+ ```
+ docker compose run --rm invidious --generate-hmac-key
+ docker compose run --rm invidious --generate-api-key
+ ```
+
+2. Set the generated keys as environment variables and start:
+ ```
+ INVIDIOUS_HMAC_KEY=<generated> INVIDIOUS_COMPANION_KEY=<generated> docker compose up -d
+ ```
+
+ Or add them to an `.env` file (create one in this directory).
+
+## Access
+
+- Web UI: `http://<host>:3000`
+
+## What to Configure
+
+- **Keys** — `INVIDIOUS_HMAC_KEY` and `INVIDIOUS_COMPANION_KEY` must be set. Without them, Invidious will not work.
+- **Database password** — Defaults to `kemal`. Change via `POSTGRES_PASSWORD` env var for production.
+- **Database init** — `docker/init-invidious-db.sh` and `config/sql/` are used for first-time database setup. No changes needed unless you customize the schema.
diff --git a/invidious/config/sql/annotations.sql b/invidious/config/sql/annotations.sql
new file mode 100644
index 0000000..3705829
--- /dev/null
+++ b/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/invidious/config/sql/channel_videos.sql b/invidious/config/sql/channel_videos.sql
new file mode 100644
index 0000000..cd4e0ff
--- /dev/null
+++ b/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/invidious/config/sql/channels.sql b/invidious/config/sql/channels.sql
new file mode 100644
index 0000000..55772da
--- /dev/null
+++ b/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/invidious/config/sql/nonces.sql b/invidious/config/sql/nonces.sql
new file mode 100644
index 0000000..644ac32
--- /dev/null
+++ b/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/invidious/config/sql/playlist_videos.sql b/invidious/config/sql/playlist_videos.sql
new file mode 100644
index 0000000..4b48b46
--- /dev/null
+++ b/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/invidious/config/sql/playlists.sql b/invidious/config/sql/playlists.sql
new file mode 100644
index 0000000..83efce4
--- /dev/null
+++ b/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/invidious/config/sql/session_ids.sql b/invidious/config/sql/session_ids.sql
new file mode 100644
index 0000000..c493769
--- /dev/null
+++ b/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/invidious/config/sql/users.sql b/invidious/config/sql/users.sql
new file mode 100644
index 0000000..ad002ec
--- /dev/null
+++ b/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/invidious/config/sql/videos.sql b/invidious/config/sql/videos.sql
new file mode 100644
index 0000000..55da396
--- /dev/null
+++ b/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/invidious/docker-compose.yml b/invidious/docker-compose.yml
new file mode 100644
index 0000000..c261a38
--- /dev/null
+++ b/invidious/docker-compose.yml
@@ -0,0 +1,81 @@
+services:
+ invidious:
+ image: quay.io/invidious/invidious:latest
+ restart: unless-stopped
+ # Remove "127.0.0.1:" if used from an external IP
+ ports:
+ - "3000:3000"
+ environment:
+ # Please read the following file for a comprehensive list of all available
+ # configuration options and their associated syntax:
+ # https://github.com/iv-org/invidious/blob/master/config/config.example.yml
+ INVIDIOUS_CONFIG: |
+ db:
+ dbname: invidious
+ user: kemal
+ password: ${POSTGRES_PASSWORD:-kemal}
+ host: invidious-db
+ port: 5432
+ check_tables: true
+ invidious_companion:
+ - private_url: "http://companion:8282/companion"
+ # Use the key generated in the 2nd step
+ invidious_companion_key: "${INVIDIOUS_COMPANION_KEY}"
+ # Use the key generated in the 1st step
+ hmac_key: "${INVIDIOUS_HMAC_KEY}"
+ healthcheck:
+ test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1
+ interval: 30s
+ timeout: 5s
+ retries: 2
+ logging:
+ options:
+ max-size: "1G"
+ max-file: "4"
+ depends_on:
+ - invidious-db
+
+ companion:
+ image: quay.io/invidious/invidious-companion:latest
+ # Please read the following file for a comprehensive list of all available
+ # environment variables and their associated syntax:
+ # https://github.com/iv-org/invidious/blob/master/config/config.example.yml
+ environment:
+ # Use the key generated in the 2nd step
+ - SERVER_SECRET_KEY=${INVIDIOUS_COMPANION_KEY}
+ restart: unless-stopped
+ # Uncomment only if you have configured "public_url" for Invidious companion
+ # Or if you want to use Invidious companion as an API in your program.
+ # Remove "127.0.0.1:" if used from an external IP
+ #ports:
+ # - "127.0.0.1:8282:8282"
+ logging:
+ options:
+ max-size: "1G"
+ max-file: "4"
+ cap_drop:
+ - ALL
+ read_only: true
+ # cache for youtube library
+ volumes:
+ - companioncache:/var/tmp/youtubei.js:rw
+ security_opt:
+ - no-new-privileges:true
+
+ invidious-db:
+ image: docker.io/library/postgres:14
+ restart: unless-stopped
+ volumes:
+ - postgresdata:/var/lib/postgresql/data
+ - ./config/sql:/config/sql
+ - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
+ environment:
+ POSTGRES_DB: invidious
+ POSTGRES_USER: kemal
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-kemal}
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
+
+volumes:
+ postgresdata:
+ companioncache:
diff --git a/invidious/docker/Dockerfile b/invidious/docker/Dockerfile
new file mode 100644
index 0000000..f837dae
--- /dev/null
+++ b/invidious/docker/Dockerfile
@@ -0,0 +1,81 @@
+# https://github.com/openssl/openssl/releases/tag/openssl-3.6.2
+ARG OPENSSL_VERSION='3.6.2'
+ARG OPENSSL_SHA256='aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f'
+
+FROM 84codes/crystal:1.20.2-alpine AS dependabot-crystal
+
+# We compile openssl ourselves due to a memory leak in how crystal interacts
+# with openssl
+# Reference: https://github.com/iv-org/invidious/issues/1438#issuecomment-3087636228
+FROM dependabot-crystal AS openssl-builder
+RUN apk add --no-cache curl perl linux-headers
+
+WORKDIR /
+
+ARG OPENSSL_VERSION
+ARG OPENSSL_SHA256
+RUN curl -Ls "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz" --output openssl-${OPENSSL_VERSION}.tar.gz
+RUN echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c
+RUN tar -xzvf openssl-${OPENSSL_VERSION}.tar.gz
+
+RUN cd openssl-${OPENSSL_VERSION} && ./Configure --openssldir=/etc/ssl && make -j$(nproc)
+
+FROM dependabot-crystal AS builder
+
+RUN apk add --no-cache sqlite-static yaml-static
+RUN apk del openssl-dev openssl-libs-static
+
+ARG release
+
+WORKDIR /invidious
+COPY ./shard.yml ./shard.yml
+COPY ./shard.lock ./shard.lock
+RUN shards install --production
+
+COPY ./src/ ./src/
+# TODO: .git folder is required for building – this is destructive.
+# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION.
+COPY ./.git/ ./.git/
+
+# Required for fetching player dependencies
+COPY ./scripts/ ./scripts/
+COPY ./assets/ ./assets/
+COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
+
+RUN crystal spec --warnings all \
+ --link-flags "-lxml2 -llzma"
+
+ARG OPENSSL_VERSION
+COPY --from=openssl-builder /openssl-${OPENSSL_VERSION} /openssl-${OPENSSL_VERSION}
+
+RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
+ PKG_CONFIG_PATH=/openssl-${OPENSSL_VERSION} \
+ crystal build ./src/invidious.cr \
+ --release \
+ --static --warnings all \
+ --link-flags "-lxml2 -llzma"; \
+ else \
+ PKG_CONFIG_PATH=/openssl-${OPENSSL_VERSION} \
+ crystal build ./src/invidious.cr \
+ --static --warnings all \
+ --link-flags "-lxml2 -llzma"; \
+ fi
+
+FROM alpine:3.23
+RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
+WORKDIR /invidious
+RUN addgroup -g 1000 -S invidious && \
+ adduser -u 1000 -S invidious -G invidious
+COPY --chown=invidious ./config/config.* ./config/
+RUN mv -n config/config.example.yml config/config.yml
+RUN sed -i 's/host: \(127.0.0.1\|localhost\)/host: invidious-db/' config/config.yml
+COPY ./config/sql/ ./config/sql/
+COPY ./locales/ ./locales/
+COPY --from=builder /invidious/assets ./assets/
+COPY --from=builder /invidious/invidious .
+RUN chmod o+rX -R ./assets ./config ./locales
+
+EXPOSE 3000
+USER invidious
+ENTRYPOINT ["/sbin/tini", "--"]
+CMD [ "/invidious/invidious" ]
diff --git a/invidious/docker/init-invidious-db.sh b/invidious/docker/init-invidious-db.sh
new file mode 100755
index 0000000..22b4cc5
--- /dev/null
+++ b/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