Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ FROM crystallang/crystal:1.16.3-alpine AS builder
RUN apk add --no-cache sqlite-static yaml-static

ARG release
ARG api_only

WORKDIR /invidious
COPY ./shard.yml ./shard.yml
Expand All @@ -21,16 +22,13 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml

RUN crystal spec --warnings all \
--link-flags "-lxml2 -llzma"
RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
crystal build ./src/invidious.cr \
--release \
--static --warnings all \
--link-flags "-lxml2 -llzma"; \
else \
crystal build ./src/invidious.cr \
--static --warnings all \
--link-flags "-lxml2 -llzma"; \
fi

RUN --mount=type=cache,target=/root/.cache/crystal \
crystal build ./src/invidious.cr \
${release:+--release} \
--static --warnings all \
--link-flags "-lxml2 -llzma" \
${api_only:+-Dapi_only -Dskip_videojs_download}

FROM alpine:3.21
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
Expand Down
17 changes: 7 additions & 10 deletions docker/Dockerfile.arm64
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ RUN apk add --no-cache 'crystal=1.14.0-r0' shards sqlite-static yaml-static yaml
zlib-static openssl-libs-static openssl-dev musl-dev xz-static

ARG release
ARG api_only

WORKDIR /invidious
COPY ./shard.yml ./shard.yml
Expand All @@ -22,16 +23,12 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
RUN crystal spec --warnings all \
--link-flags "-lxml2 -llzma"

RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
crystal build ./src/invidious.cr \
--release \
--static --warnings all \
--link-flags "-lxml2 -llzma"; \
else \
crystal build ./src/invidious.cr \
--static --warnings all \
--link-flags "-lxml2 -llzma"; \
fi
RUN --mount=type=cache,target=/root/.cache/crystal \
crystal build ./src/invidious.cr \
${release:+--release} \
--static --warnings all \
--link-flags "-lxml2 -llzma" \
${api_only:+-Dapi_only -Dskip_videojs_download}

FROM alpine:3.21
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
Expand Down
103 changes: 67 additions & 36 deletions src/invidious.cr
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,33 @@ require "yaml"
require "compress/zip"
require "protodec/utils"

require "./invidious/database/*"
require "./invidious/database/migrations/*"
# Database requires
{% unless flag?(:api_only) %}
require "./invidious/database/*"
require "./invidious/database/migrations/*"
{% else %}
require "./invidious/database/api_only_stubs"
{% end %}

# Core requires
require "./invidious/http_server/*"
require "./invidious/helpers/*"
require "./invidious/yt_backend/*"
require "./invidious/frontend/*"
require "./invidious/videos/*"

require "./invidious/jsonify/**"

require "./invidious/*"
require "./invidious/requires"
require "./invidious/comments/*"
require "./invidious/channels/*"
require "./invidious/user/*"
require "./invidious/search/*"
require "./invidious/routes/**"
require "./invidious/jobs/base_job"
require "./invidious/jobs/*"

# Jobs (not needed in API-only mode)
{% unless flag?(:api_only) %}
require "./invidious/jobs/base_job"
require "./invidious/jobs/*"
{% end %}

# Declare the base namespace for invidious
module Invidious
Expand All @@ -60,7 +69,13 @@ alias IV = Invidious
CONFIG = Config.load
HMAC_KEY = CONFIG.hmac_key

PG_DB = DB.open CONFIG.database_url
# Database connection
{% unless flag?(:api_only) %}
PG_DB = DB.open CONFIG.database_url
{% else %}
require "./invidious/api_only_types"
PG_DB = DummyDB.new
{% end %}
ARCHIVE_URL = URI.parse("https://archive.org")
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
REDDIT_URL = URI.parse("https://www.reddit.com")
Expand Down Expand Up @@ -133,7 +148,11 @@ Kemal.config.extra_options do |parser|
exit
end
parser.on("--migrate", "Run any migrations (beta, use at your own risk!!") do
Invidious::Database::Migrator.new(PG_DB).migrate
{% unless flag?(:api_only) %}
Invidious::Database::Migrator.new(PG_DB).migrate
{% else %}
puts "Database migrations are not available in API-only mode"
{% end %}
exit
end
end
Expand All @@ -147,9 +166,11 @@ OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mo
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level, CONFIG.colorize_logs)

# Check table integrity
Invidious::Database.check_integrity(CONFIG)
{% unless flag?(:api_only) %}
Invidious::Database.check_integrity(CONFIG)
{% end %}

{% if !flag?(:skip_videojs_download) %}
{% if !flag?(:skip_videojs_download) && !flag?(:api_only) %}
# Resolve player dependencies. This is done at compile time.
#
# Running the script by itself would show some colorful feedback while this doesn't.
Expand All @@ -175,38 +196,48 @@ DECRYPT_FUNCTION =

# Start jobs

if CONFIG.channel_threads > 0
Invidious::Jobs.register Invidious::Jobs::RefreshChannelsJob.new(PG_DB)
end

if CONFIG.feed_threads > 0
Invidious::Jobs.register Invidious::Jobs::RefreshFeedsJob.new(PG_DB)
end

if CONFIG.statistics_enabled
Invidious::Jobs.register Invidious::Jobs::StatisticsRefreshJob.new(PG_DB, SOFTWARE)
end

if (CONFIG.use_pubsub_feeds.is_a?(Bool) && CONFIG.use_pubsub_feeds.as(Bool)) || (CONFIG.use_pubsub_feeds.is_a?(Int32) && CONFIG.use_pubsub_feeds.as(Int32) > 0)
Invidious::Jobs.register Invidious::Jobs::SubscribeToFeedsJob.new(PG_DB, HMAC_KEY)
end
{% unless flag?(:api_only) %}
if CONFIG.channel_threads > 0
Invidious::Jobs.register Invidious::Jobs::RefreshChannelsJob.new(PG_DB)
end

if CONFIG.popular_enabled
Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB)
end
if CONFIG.feed_threads > 0
Invidious::Jobs.register Invidious::Jobs::RefreshFeedsJob.new(PG_DB)
end

NOTIFICATION_CHANNEL = ::Channel(VideoNotification).new(32)
CONNECTION_CHANNEL = ::Channel({Bool, ::Channel(PQ::Notification)}).new(32)
Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(NOTIFICATION_CHANNEL, CONNECTION_CHANNEL, CONFIG.database_url)
if CONFIG.statistics_enabled
Invidious::Jobs.register Invidious::Jobs::StatisticsRefreshJob.new(PG_DB, SOFTWARE)
end

Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
if (CONFIG.use_pubsub_feeds.is_a?(Bool) && CONFIG.use_pubsub_feeds.as(Bool)) || (CONFIG.use_pubsub_feeds.is_a?(Int32) && CONFIG.use_pubsub_feeds.as(Int32) > 0)
Invidious::Jobs.register Invidious::Jobs::SubscribeToFeedsJob.new(PG_DB, HMAC_KEY)
end

Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new
if CONFIG.popular_enabled
Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB)
end

Invidious::Jobs.start_all
NOTIFICATION_CHANNEL = ::Channel(VideoNotification).new(32)
CONNECTION_CHANNEL = ::Channel({Bool, ::Channel(PQ::Notification)}).new(32)
Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(NOTIFICATION_CHANNEL, CONNECTION_CHANNEL, CONFIG.database_url)

Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new

Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new

Invidious::Jobs.start_all
{% else %}
# Define channels for API-only mode (even though they won't be used)
NOTIFICATION_CHANNEL = ::Channel(VideoNotification).new(1)
CONNECTION_CHANNEL = ::Channel({Bool, ::Channel(PQ::Notification)}).new(1)
{% end %}

def popular_videos
Invidious::Jobs::PullPopularVideosJob::POPULAR_VIDEOS.get
{% if flag?(:api_only) %}
[] of ChannelVideo
{% else %}
Invidious::Jobs::PullPopularVideosJob::POPULAR_VIDEOS.get
{% end %}
end

# Routing
Expand Down
50 changes: 50 additions & 0 deletions src/invidious/api_only_types.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# API-only mode type definitions
# This file provides dummy type definitions when running in API-only mode

# Dummy DB class for API-only mode
class DummyDB
def query_all(*args, as : T.class) forall T
[] of T
end

def query_one(*args, as : T.class) forall T
raise "Database not available in API-only mode"
end

def query_one?(*args, as : T.class) forall T
nil
end

def scalar(*args)
0
end

def exec(*args)
nil
end
end

# VideoNotification struct for API-only mode
struct VideoNotification
property video_id : String
property channel_id : String
property published : Time

def initialize(@video_id = "", @channel_id = "", @published = Time.utc)
end

def self.from_video(video : ChannelVideo) : VideoNotification
VideoNotification.new(video.id, video.ucid, video.published)
end
end

# PQ module with Notification for API-only mode
module PQ
struct Notification
property channel : String = ""
property payload : String = ""

def initialize(@channel = "", @payload = "")
end
end
end
39 changes: 24 additions & 15 deletions src/invidious/config.cr
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ class Config
property full_refresh : Bool = false

# Jobs config structure. See jobs.cr and jobs/base_job.cr
property jobs = Invidious::Jobs::JobsConfig.new
{% unless flag?(:api_only) %}
property jobs = Invidious::Jobs::JobsConfig.new
{% end %}

# Used to tell Invidious it is behind a proxy, so links to resources should be https://
property https_only : Bool?
Expand Down Expand Up @@ -285,21 +287,28 @@ class Config
end

# Build database_url from db.* if it's not set directly
if config.database_url.to_s.empty?
if db = config.db
config.database_url = URI.new(
scheme: "postgres",
user: db.user,
password: db.password,
host: db.host,
port: db.port,
path: db.dbname,
)
else
puts "Config: Either database_url or db.* is required"
exit(1)
{% unless flag?(:api_only) %}
if config.database_url.to_s.empty?
if db = config.db
config.database_url = URI.new(
scheme: "postgres",
user: db.user,
password: db.password,
host: db.host,
port: db.port,
path: db.dbname,
)
else
puts "Config: Either database_url or db.* is required"
exit(1)
end
end
end
{% else %}
# In API-only mode, database is optional
if config.database_url.to_s.empty?
config.database_url = URI.parse("postgres://dummy:dummy@localhost/dummy")
end
{% end %}

# Check if the socket configuration is valid
if sb = config.socket_binding
Expand Down
Loading