Compare commits
7 Commits
608cc9253d
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 194ed3b87f | |||
| 0cadd5d8d4 | |||
| 32a4d728ef | |||
| 0d9b0302a2 | |||
| d44a8c7276 | |||
| f19577fbcc | |||
| 9f61b06592 |
@@ -27,15 +27,32 @@ jobs:
|
||||
STACKS=$(git diff --name-only HEAD~1 HEAD 2>/dev/null | grep '^stacks/' | cut -d'/' -f2 | sort -u || echo "")
|
||||
|
||||
if [ -z "$STACKS" ]; then
|
||||
echo "No stacks changed, deploying all..."
|
||||
echo "No stacks changed, checking all stacks..."
|
||||
STACKS=$(ls stacks/)
|
||||
fi
|
||||
|
||||
echo "Deploying: $STACKS"
|
||||
echo "Evaluating stacks: $STACKS"
|
||||
echo ""
|
||||
|
||||
for stack in $STACKS; do
|
||||
COMPOSE_FILE="stacks/$stack/docker-compose.yml"
|
||||
|
||||
# Check stack-type label
|
||||
STACK_TYPE=$(grep -o 'stack-type=[^"]*' "$COMPOSE_FILE" 2>/dev/null | head -1 | cut -d= -f2)
|
||||
|
||||
if [ -z "$STACK_TYPE" ]; then
|
||||
echo "⚠️ SKIP $stack - no stack-type label found"
|
||||
continue
|
||||
fi
|
||||
|
||||
# On prod, only deploy 'prod' and 'public' stacks
|
||||
if [ "$STACK_TYPE" != "prod" ] && [ "$STACK_TYPE" != "public" ]; then
|
||||
echo "⏭️ SKIP $stack - stack-type=$STACK_TYPE (not for prod)"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "Deploying $stack..."
|
||||
echo "Deploying $stack (stack-type=$STACK_TYPE)..."
|
||||
echo "=========================================="
|
||||
|
||||
STACK_DIR="${{ env.STACKS_DIR }}/$stack"
|
||||
@@ -44,9 +61,10 @@ jobs:
|
||||
# Copy files
|
||||
sudo cp -r stacks/$stack/* "$STACK_DIR/"
|
||||
|
||||
# Create .env from template if exists
|
||||
if [ -f "$STACK_DIR/.env.template" ]; then
|
||||
sudo envsubst < "$STACK_DIR/.env.template" > "$STACK_DIR/.env"
|
||||
# Create .env from template if .env doesn't exist
|
||||
if [ -f "$STACK_DIR/.env.template" ] && [ ! -f "$STACK_DIR/.env" ]; then
|
||||
echo "Creating .env from template..."
|
||||
sudo sh -c "DOMAIN=$DOMAIN envsubst < '$STACK_DIR/.env.template' > '$STACK_DIR/.env'"
|
||||
fi
|
||||
|
||||
# Deploy
|
||||
@@ -59,11 +77,11 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Show running containers
|
||||
run: sudo docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | head -20
|
||||
run: sudo docker ps --format "table {{.Names}}\t{{.Status}}" | head -30
|
||||
|
||||
deploy-dev:
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
runs-on: ubuntu-dev
|
||||
runs-on: ubuntu-dev:host
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -77,15 +95,32 @@ jobs:
|
||||
STACKS=$(git diff --name-only HEAD~1 HEAD 2>/dev/null | grep '^stacks/' | cut -d'/' -f2 | sort -u || echo "")
|
||||
|
||||
if [ -z "$STACKS" ]; then
|
||||
echo "No stacks changed, deploying all..."
|
||||
echo "No stacks changed, checking all stacks..."
|
||||
STACKS=$(ls stacks/)
|
||||
fi
|
||||
|
||||
echo "Deploying: $STACKS"
|
||||
echo "Evaluating stacks: $STACKS"
|
||||
echo ""
|
||||
|
||||
for stack in $STACKS; do
|
||||
COMPOSE_FILE="stacks/$stack/docker-compose.yml"
|
||||
|
||||
# Check stack-type label
|
||||
STACK_TYPE=$(grep -o 'stack-type=[^"]*' "$COMPOSE_FILE" 2>/dev/null | head -1 | cut -d= -f2)
|
||||
|
||||
if [ -z "$STACK_TYPE" ]; then
|
||||
echo "⚠️ SKIP $stack - no stack-type label found"
|
||||
continue
|
||||
fi
|
||||
|
||||
# On dev, only deploy 'dev-only' stacks
|
||||
if [ "$STACK_TYPE" != "dev-only" ]; then
|
||||
echo "⏭️ SKIP $stack - stack-type=$STACK_TYPE (not for dev)"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "Deploying $stack..."
|
||||
echo "Deploying $stack (stack-type=$STACK_TYPE)..."
|
||||
echo "=========================================="
|
||||
|
||||
STACK_DIR="${{ env.STACKS_DIR }}/$stack"
|
||||
@@ -94,9 +129,10 @@ jobs:
|
||||
# Copy files
|
||||
sudo cp -r stacks/$stack/* "$STACK_DIR/"
|
||||
|
||||
# Create .env from template if exists
|
||||
if [ -f "$STACK_DIR/.env.template" ]; then
|
||||
sudo envsubst < "$STACK_DIR/.env.template" > "$STACK_DIR/.env"
|
||||
# Create .env from template if .env doesn't exist
|
||||
if [ -f "$STACK_DIR/.env.template" ] && [ ! -f "$STACK_DIR/.env" ]; then
|
||||
echo "Creating .env from template..."
|
||||
sudo sh -c "DOMAIN=$DOMAIN envsubst < '$STACK_DIR/.env.template' > '$STACK_DIR/.env'"
|
||||
fi
|
||||
|
||||
# Deploy
|
||||
@@ -109,4 +145,4 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Show running containers
|
||||
run: sudo docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | head -20
|
||||
run: sudo docker ps --format "table {{.Names}}\t{{.Status}}" | head -30
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -6,6 +6,7 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.form-mailer.rule=Host(`bookclub.${DOMAIN}`)"
|
||||
- "traefik.http.routers.form-mailer.entrypoints=https"
|
||||
|
||||
@@ -14,6 +14,7 @@ services:
|
||||
networks:
|
||||
- web
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.dockge.rule=Host(`dockge.${DOMAIN}`)"
|
||||
- "traefik.http.routers.dockge.entrypoints=https"
|
||||
@@ -23,3 +24,4 @@ services:
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ services:
|
||||
networks:
|
||||
- web
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.filebrowser.entrypoints=https"
|
||||
- "traefik.http.routers.filebrowser.rule=Host(`files.${DOMAIN}`)"
|
||||
|
||||
@@ -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}
|
||||
|
||||
12
stacks/invidious/config/sql/annotations.sql
Normal file
12
stacks/invidious/config/sql/annotations.sql
Normal file
@@ -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;
|
||||
30
stacks/invidious/config/sql/channel_videos.sql
Normal file
30
stacks/invidious/config/sql/channel_videos.sql
Normal file
@@ -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");
|
||||
|
||||
25
stacks/invidious/config/sql/channels.sql
Normal file
25
stacks/invidious/config/sql/channels.sql
Normal file
@@ -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");
|
||||
|
||||
22
stacks/invidious/config/sql/nonces.sql
Normal file
22
stacks/invidious/config/sql/nonces.sql
Normal file
@@ -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");
|
||||
|
||||
19
stacks/invidious/config/sql/playlist_videos.sql
Normal file
19
stacks/invidious/config/sql/playlist_videos.sql
Normal file
@@ -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;
|
||||
29
stacks/invidious/config/sql/playlists.sql
Normal file
29
stacks/invidious/config/sql/playlists.sql
Normal file
@@ -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;
|
||||
23
stacks/invidious/config/sql/session_ids.sql
Normal file
23
stacks/invidious/config/sql/session_ids.sql
Normal file
@@ -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");
|
||||
|
||||
29
stacks/invidious/config/sql/users.sql
Normal file
29
stacks/invidious/config/sql/users.sql
Normal file
@@ -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");
|
||||
|
||||
23
stacks/invidious/config/sql/videos.sql
Normal file
23
stacks/invidious/config/sql/videos.sql
Normal file
@@ -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");
|
||||
|
||||
@@ -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:
|
||||
|
||||
12
stacks/invidious/docker/init-invidious-db.sh
Executable file
12
stacks/invidious/docker/init-invidious-db.sh
Executable file
@@ -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
|
||||
@@ -9,6 +9,8 @@ services:
|
||||
- meshmonitor-data:/data
|
||||
env_file:
|
||||
- .env
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
volumes:
|
||||
meshmonitor-data:
|
||||
driver: local
|
||||
|
||||
@@ -5,3 +5,5 @@ services:
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8585:8080"
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
|
||||
@@ -12,3 +12,5 @@ services:
|
||||
- /etc/ansible/hosts:/etc/ansible/hosts:ro
|
||||
- ./.ssh:/var/core/mlLogWatcher/.ssh:ro
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
|
||||
@@ -8,6 +8,8 @@ services:
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
environment:
|
||||
FRONTEND_PORT: "5001"
|
||||
ports:
|
||||
|
||||
@@ -18,3 +18,5 @@ services:
|
||||
- /dev/dri:/dev/dri #optional
|
||||
shm_size: "1gb"
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
|
||||
@@ -18,6 +18,7 @@ services:
|
||||
depends_on:
|
||||
- syncthing
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.http.routers.todo-obbytodo.entrypoints=https"
|
||||
|
||||
@@ -19,6 +19,7 @@ services:
|
||||
networks:
|
||||
- web
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.syncthing.entrypoints=https"
|
||||
- "traefik.http.routers.syncthing.rule=Host(`syncthing.${DOMAIN}`)"
|
||||
|
||||
@@ -8,6 +8,8 @@ services:
|
||||
image: szurubooru/server:latest
|
||||
depends_on:
|
||||
- sql
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
environment:
|
||||
## These should be the names of the dependent containers listed below,
|
||||
## or FQDNs/IP addresses if these services are running outside of Docker
|
||||
|
||||
12
stacks/tlc/.env.template
Normal file
12
stacks/tlc/.env.template
Normal file
@@ -0,0 +1,12 @@
|
||||
# Elasticsearch configuration
|
||||
ELASTIC_URL=${ELASTIC_URL}
|
||||
ELASTIC_INDEX=${ELASTIC_INDEX:-tlc-egre}
|
||||
ELASTIC_API_KEY=${ELASTIC_API_KEY}
|
||||
ELASTIC_VERIFY_CERTS=${ELASTIC_VERIFY_CERTS:-0}
|
||||
|
||||
# Qdrant configuration
|
||||
QDRANT_URL=${QDRANT_URL}
|
||||
QDRANT_COLLECTION=${QDRANT_COLLECTION:-tlc-captions-full}
|
||||
QDRANT_VECTOR_NAME=${QDRANT_VECTOR_NAME:-}
|
||||
QDRANT_VECTOR_SIZE=${QDRANT_VECTOR_SIZE:-1024}
|
||||
QDRANT_EMBED_MODEL=${QDRANT_EMBED_MODEL:-BAAI/bge-large-en-v1.5}
|
||||
74
stacks/tlc/docker-compose.yml
Normal file
74
stacks/tlc/docker-compose.yml
Normal file
@@ -0,0 +1,74 @@
|
||||
services:
|
||||
# RSS Bridge - Converts YouTube channels to RSS feeds
|
||||
rss-bridge:
|
||||
image: rssbridge/rss-bridge:latest
|
||||
container_name: tlc-rss-bridge
|
||||
hostname: rss-bridge
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "com.ghost.tel/stack-type=dev-only"
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
ports:
|
||||
- "3001:80"
|
||||
networks:
|
||||
- web
|
||||
|
||||
# Feed Master - Aggregates multiple RSS feeds into unified feed
|
||||
feed-master:
|
||||
image: umputun/feed-master:latest
|
||||
container_name: tlc-feed-master
|
||||
hostname: feed-master
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- rss-bridge
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
environment:
|
||||
- DEBUG=false
|
||||
- FM_DB=/srv/var/feed-master.bdb
|
||||
- FM_CONF=/srv/etc/fm.yml
|
||||
volumes:
|
||||
- ./feed-master-config:/srv/etc
|
||||
- ./feed-master-data:/srv/var
|
||||
- ./feed-master-images:/srv/images
|
||||
ports:
|
||||
- "8097:8080"
|
||||
networks:
|
||||
- web
|
||||
|
||||
# TLC Search - Flask app for searching YouTube transcripts
|
||||
search:
|
||||
image: gitea.ghost.tel/knight/tlc-search:latest
|
||||
container_name: tlc-search
|
||||
hostname: tlc-search
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- feed-master
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- RSS_FEED_URL=/rss/youtube-unified
|
||||
- RSS_FEED_UPSTREAM=http://feed-master:8080
|
||||
volumes:
|
||||
- ./channels.yml:/app/python_app/channels.yml:ro
|
||||
- ./data:/app/data:ro
|
||||
ports:
|
||||
- "8088:8080"
|
||||
networks:
|
||||
- web
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
168
stacks/tlc/feed-master-config.yml.example
Normal file
168
stacks/tlc/feed-master-config.yml.example
Normal file
@@ -0,0 +1,168 @@
|
||||
# Feed Master Configuration
|
||||
# Auto-generated from channels.yml
|
||||
# Do not edit manually - regenerate using generate_feed_config_simple.py
|
||||
|
||||
feeds:
|
||||
youtube-unified:
|
||||
title: YouTube Unified Feed
|
||||
description: Aggregated feed from all YouTube channels
|
||||
link: https://youtube.com
|
||||
language: "en-us"
|
||||
sources:
|
||||
- name: A Quality Existence
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC6vg0HkKKlgsWk-3HfV-vnw&format=Mrss
|
||||
- name: Adams Fall
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCbD1Pm0TOcRK2zaCrwgcTTg&format=Mrss
|
||||
- name: Andrea with the Bangs
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCeWWxwzgLYUbfjWowXhVdYw&format=Mrss
|
||||
- name: Aphrael Pilotson
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCGHuURJ1XFHzPSeokf6510A&format=Mrss
|
||||
- name: Cassidy van der Kamp
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCM9Z05vuQhMEwsV03u6DrLA&format=Mrss
|
||||
- name: Channel UCCebR16tXbv
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCCebR16tXbv5Ykk9_WtCCug&format=Mrss
|
||||
- name: Channel UCiJmdXTb76i
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCiJmdXTb76i8eIPXdJyf8ZQ&format=Mrss
|
||||
- name: Charlie's Little Corner
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC952hDf_C4nYJdqwK7VzTxA&format=Mrss
|
||||
- name: Chris Howard
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC704NVL2DyzYg3rMU9r1f7A&format=Mrss
|
||||
- name: Christian Baxter
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCU5SNBfTo4umhjYz6M0Jsmg&format=Mrss
|
||||
- name: Climbing Mt Sophia
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCk9O91WwruXmgu1NQrKZZEw&format=Mrss
|
||||
- name: Corner Citizen
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCAXyF_HFeMgwS8nkGVeroAA&format=Mrss
|
||||
- name: Davidbusuttil9086
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCxV18lwwh29DiWuooz7UCvg&format=Mrss
|
||||
- name: Ein Sof - Infinite Reflections
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC4Rmxg7saTfwIpvq3QEzylQ&format=Mrss
|
||||
- name: Emily Rajeh
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCn5mf-fcpBmkepIpZ8eFRng&format=Mrss
|
||||
- name: Eric Seitz
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCTdH4nh6JTcfKUAWvmnPoIQ&format=Mrss
|
||||
- name: Ethan Caughey
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCv2Qft5mZrmA9XAwnl9PU-g&format=Mrss
|
||||
- name: faturechi
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCprytROeCztMOMe8plyJRMg&format=Mrss
|
||||
- name: Finding Ideas
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC6Tvr9mBXNaAxLGRA_sUSRA&format=Mrss
|
||||
- name: Free Rilian
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCT8Lq3ufaGEnCSS8WpFatqw&format=Mrss
|
||||
- name: From Whom All Blessings Flow
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC_-YQbnPfBbIezMr1adZZiQ&format=Mrss
|
||||
- name: Grahampardun
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCB0C8DEIQlQzvSGuGriBxtA&format=Mrss
|
||||
- name: Grail Country
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCsi_x8c12NW9FR7LL01QXKA&format=Mrss
|
||||
- name: Grey Hamilton
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCrZyTWGMdRM9_P26RKPvh3A&format=Mrss
|
||||
- name: Grizwald Grim
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCAqTQ5yLHHH44XWwWXLkvHQ&format=Mrss
|
||||
- name: Jesspurviance
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCQ7rVoApmYIpcmU7fB9RPyw&format=Mrss
|
||||
- name: John Vervaeke
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCpqDUjTsof-kTNpnyWper_Q&format=Mrss
|
||||
- name: Jonathan Dumeer
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC4GiA5Hnwy415uVRymxPK-w&format=Mrss
|
||||
- name: Jonathan Pageau
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCtCTSf3UwRU14nYWr_xm-dQ&format=Mrss
|
||||
- name: Jordan B Peterson
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCL_f53ZEJxp8TtlOkHwMV9Q&format=Mrss
|
||||
- name: Jordan Hall
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCMzT-mdCqoyEv_-YZVtE7MQ&format=Mrss
|
||||
- name: Joseph Lambrecht
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCiOZYvBGHw1Y6wyzffwEp9g&format=Mrss
|
||||
- name: Justinsmorningcoffee
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCPUVeoQYyq8cndWwyczX6RA&format=Mrss
|
||||
- name: Kale Zelden
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCiukuaNd_qzRDTW9qe2OC1w&format=Mrss
|
||||
- name: Lancecleaver227
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCwF5LWNOFou_50bT65bq4Bg&format=Mrss
|
||||
- name: Lucas Vos
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCez1fzMRGctojfis2lfRYug&format=Mrss
|
||||
- name: Luke Thompson
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC_dnk5D4tFCRYCrKIcQlcfw&format=Mrss
|
||||
- name: Marc Jackson
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCMJCtS8jKouJ2d8UIYzW3vg&format=Mrss
|
||||
- name: Mark D Parker
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCVdSgEf9bLXFMBGSMhn7x4Q&format=Mrss
|
||||
- name: Mark S
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC977g6oGYIJDQnsZOGjQBBA&format=Mrss
|
||||
- name: Mary Kochan
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC2leFZRD0ZlQDQxpR2Zd8oA&format=Mrss
|
||||
- name: Matthewparlato5626
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCosBhpwwGh_ueYq4ZSi5dGw&format=Mrss
|
||||
- name: mcmosav
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCnojyPW0IgLWTQ0SaDQ1KBA&format=Mrss
|
||||
- name: Michaelmartin8681
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCpLJJLVB_7v4Igq-9arja1A&format=Mrss
|
||||
- name: More Christ
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCEPOn4cgvrrerg_-q_Ygw1A&format=Mrss
|
||||
- name: Neal Daedalus
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC8mJqpS_EBbMcyuzZDF0TEw&format=Mrss
|
||||
- name: Nechama Gluck
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC5goUoFM4LPim4eY4pwRXYw&format=Mrss
|
||||
- name: OG Rose
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC6zHDj4D323xJkblnPTvY3Q&format=Mrss
|
||||
- name: Paul Anleitner
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC2yCyOMUeem-cYwliC-tLJg&format=Mrss
|
||||
- name: Paul Rene Nichols
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCDCfI162vhPvwdxW6X4nmiw&format=Mrss
|
||||
- name: Paul VanderKlay
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCGsDIP_K6J6VSTqlq-9IPlg&format=Mrss
|
||||
- name: President Foxman
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCMVG5eqpYFVEB-a9IqAOuHA&format=Mrss
|
||||
- name: Rafekelley
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCDIPXp88qjAV3TiaR5Uo3iQ&format=Mrss
|
||||
- name: Randos United
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCEzWTLDYmL8soRdQec9Fsjw&format=Mrss
|
||||
- name: Randos United 2
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC1KgNsMdRoIA_njVmaDdHgA&format=Mrss
|
||||
- name: Rebel Wisdom
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCFQ6Gptuq-sLflbJ4YY3Umw&format=Mrss
|
||||
- name: Restoring Meaning
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCzX6R3ZLQh5Zma_5AsPcqPA&format=Mrss
|
||||
- name: Rigel Windsong Thurston
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCWehDXDEdUpB58P7-Bg1cHg&format=Mrss
|
||||
- name: Rightinchrist
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCZA5mUAyYcCL1kYgxbeMNrA&format=Mrss
|
||||
- name: Ron Copperman
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC5yLuFQCms4nb9K2bGQLqIw&format=Mrss
|
||||
- name: Sartori Studios
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC8SErJkYnDsYGh1HxoZkl-g&format=Mrss
|
||||
- name: Secular Koranism
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCFLovlJ8RFApfjrf2y157xg&format=Mrss
|
||||
- name: Shoulder Serf
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UChptV-kf8lnncGh7DA2m8Pw&format=Mrss
|
||||
- name: Skankenstein
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCUSyTPWW4JaG1YfUPddw47Q&format=Mrss
|
||||
- name: Strange Theology
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCEY1vGNBPsC3dCatZyK3Jkw&format=Mrss
|
||||
- name: The Anadromist
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCIAtCuzdvgNJvSYILnHtdWA&format=Mrss
|
||||
- name: The Chris Show
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UClIDP7_Kzv_7tDQjTv9EhrA&format=Mrss
|
||||
- name: The Meaning Code
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCgp_r6WlBwDSJrP43Mz07GQ&format=Mrss
|
||||
- name: the plebistocrat
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCaJ0CqiiMSTq4X0rycUOIjw&format=Mrss
|
||||
- name: The Young Levite
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC1a4VtU_SMSfdRiwMJR33YQ&format=Mrss
|
||||
- name: TheCommonToad
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC-QiBn6GsM3JZJAeAQpaGAA&format=Mrss
|
||||
- name: TheScrollersPodcast
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC5uv-BxzCrN93B_5qbOdRWw&format=Mrss
|
||||
- name: Transfigured
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCg7Ed0lecvko58ibuX1XHng&format=Mrss
|
||||
- name: UpCycleClub
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCzw2FNI3IRphcAoVcUENOgQ&format=Mrss
|
||||
- name: Wavesofobsession
|
||||
url: http://rss-bridge/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UCedgru6YCto3zyXjlbuQuqA&format=Mrss
|
||||
|
||||
system:
|
||||
update: 5m
|
||||
max_per_feed: 5
|
||||
max_total: 200
|
||||
max_keep: 1000
|
||||
base_url: http://localhost:8097
|
||||
Reference in New Issue
Block a user