-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Preferences overhaul #2377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
SamantazFox
wants to merge
8
commits into
iv-org:master
Choose a base branch
from
SamantazFox:preference-overhaul
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Preferences overhaul #2377
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
22cc8f4
Move preferences/config-related code to separate files
SamantazFox ddc3d61
Merge ConfigPreferences and Preferences structs
SamantazFox e611e21
Make Preferences a class
SamantazFox b9e7dfa
Add 'require' statements for JSON/YAML
SamantazFox 232378e
Add a few utility methods
SamantazFox 0f0677b
Move URIConverter/FamilyConverter/StringToCookies modules to config.cr
SamantazFox 555de5f
Use enum where possible
SamantazFox 0989fe8
Add URI ser/des for user Preferences
SamantazFox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
# | ||
# This file contains the server config data structures and | ||
# all the associated validation/parsing routines. | ||
# | ||
|
||
struct DBConfig | ||
include YAML::Serializable | ||
|
||
property user : String | ||
property password : String | ||
|
||
property host : String | ||
property port : Int32 | ||
|
||
property dbname : String | ||
end | ||
|
||
class Config | ||
include YAML::Serializable | ||
|
||
property channel_threads : Int32 = 1 # Number of threads to use for crawling videos from channels (for updating subscriptions) | ||
property feed_threads : Int32 = 1 # Number of threads to use for updating feeds | ||
property output : String = "STDOUT" # Log file path or STDOUT | ||
property log_level : LogLevel = LogLevel::Info # Default log level, valid YAML values are ints and strings, see src/invidious/helpers/logger.cr | ||
property db : DBConfig? = nil # Database configuration with separate parameters (username, hostname, etc) | ||
|
||
@[YAML::Field(converter: Config::URIConverter)] | ||
property database_url : URI = URI.parse("") # Database configuration using 12-Factor "Database URL" syntax | ||
property decrypt_polling : Bool = true # Use polling to keep decryption function up to date | ||
property full_refresh : Bool = false # Used for crawling channels: threads should check all videos uploaded by a channel | ||
property https_only : Bool? # Used to tell Invidious it is behind a proxy, so links to resources should be https:// | ||
property hmac_key : String? # HMAC signing key for CSRF tokens and verifying pubsub subscriptions | ||
property domain : String? # Domain to be used for links to resources on the site where an absolute URL is required | ||
property use_pubsub_feeds : Bool | Int32 = false # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) | ||
property popular_enabled : Bool = true | ||
property captcha_enabled : Bool = true | ||
property login_enabled : Bool = true | ||
property registration_enabled : Bool = true | ||
property statistics_enabled : Bool = false | ||
property admins : Array(String) = [] of String | ||
property external_port : Int32? = nil | ||
|
||
property default_user_preferences : Preferences = Preferences.new | ||
|
||
property dmca_content : Array(String) = [] of String # For compliance with DMCA, disables download widget using list of video IDs | ||
property check_tables : Bool = false # Check table integrity, automatically try to add any missing columns, create tables, etc. | ||
property cache_annotations : Bool = false # Cache annotations requested from IA, will not cache empty annotations or annotations that only contain cards | ||
property banner : String? = nil # Optional banner to be displayed along top of page for announcements, etc. | ||
property hsts : Bool? = true # Enables 'Strict-Transport-Security'. Ensure that `domain` and all subdomains are served securely | ||
property disable_proxy : Bool? | Array(String)? = false # Disable proxying server-wide: options: 'dash', 'livestreams', 'downloads', 'local' | ||
|
||
@[YAML::Field(converter: Config::FamilyConverter)] | ||
property force_resolve : Socket::Family = Socket::Family::UNSPEC # Connect to YouTube over 'ipv6', 'ipv4'. Will sometimes resolve fix issues with rate-limiting (see https://github.com/ytdl-org/youtube-dl/issues/21729) | ||
property port : Int32 = 3000 # Port to listen for connections (overrided by command line argument) | ||
property host_binding : String = "0.0.0.0" # Host to bind (overrided by command line argument) | ||
property pool_size : Int32 = 100 # Pool size for HTTP requests to youtube.com and ytimg.com (each domain has a separate pool of `pool_size`) | ||
property use_quic : Bool = true # Use quic transport for youtube api | ||
|
||
@[YAML::Field(converter: Config::StringToCookies)] | ||
property cookies : HTTP::Cookies = HTTP::Cookies.new # Saved cookies in "name1=value1; name2=value2..." format | ||
property captcha_key : String? = nil # Key for Anti-Captcha | ||
property captcha_api_url : String = "https://api.anti-captcha.com" # API URL for Anti-Captcha | ||
|
||
def disabled?(option) | ||
case disabled = CONFIG.disable_proxy | ||
when Bool | ||
return disabled | ||
when Array | ||
if disabled.includes? option | ||
return true | ||
else | ||
return false | ||
end | ||
else | ||
return false | ||
end | ||
end | ||
|
||
def self.load | ||
# Load config from file or YAML string env var | ||
env_config_file = "INVIDIOUS_CONFIG_FILE" | ||
env_config_yaml = "INVIDIOUS_CONFIG" | ||
|
||
config_file = ENV.has_key?(env_config_file) ? ENV.fetch(env_config_file) : "config/config.yml" | ||
config_yaml = ENV.has_key?(env_config_yaml) ? ENV.fetch(env_config_yaml) : File.read(config_file) | ||
|
||
config = Config.from_yaml(config_yaml) | ||
|
||
# Update config from env vars (upcased and prefixed with "INVIDIOUS_") | ||
{% for ivar in Config.instance_vars %} | ||
{% env_id = "INVIDIOUS_#{ivar.id.upcase}" %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should limit this to just a subset? Allowing for all Invidious config values to be modified via environmental variable seems unnecessary, with the resulting code being rather complex too. |
||
|
||
if ENV.has_key?({{env_id}}) | ||
# puts %(Config.{{ivar.id}} : Loading from env var {{env_id}}) | ||
env_value = ENV.fetch({{env_id}}) | ||
success = false | ||
|
||
# Use YAML converter if specified | ||
{% ann = ivar.annotation(::YAML::Field) %} | ||
{% if ann && ann[:converter] %} | ||
puts %(Config.{{ivar.id}} : Parsing "#{env_value}" as {{ivar.type}} with {{ann[:converter]}} converter) | ||
config.{{ivar.id}} = {{ann[:converter]}}.from_yaml(YAML::ParseContext.new, YAML::Nodes.parse(ENV.fetch({{env_id}})).nodes[0]) | ||
puts %(Config.{{ivar.id}} : Set to #{config.{{ivar.id}}}) | ||
success = true | ||
|
||
# Use regular YAML parser otherwise | ||
{% else %} | ||
{% ivar_types = ivar.type.union? ? ivar.type.union_types : [ivar.type] %} | ||
# Sort types to avoid parsing nulls and numbers as strings | ||
{% ivar_types = ivar_types.sort_by { |ivar_type| ivar_type == Nil ? 0 : ivar_type == Int32 ? 1 : 2 } %} | ||
{{ivar_types}}.each do |ivar_type| | ||
if !success | ||
begin | ||
# puts %(Config.{{ivar.id}} : Trying to parse "#{env_value}" as #{ivar_type}) | ||
config.{{ivar.id}} = ivar_type.from_yaml(env_value) | ||
puts %(Config.{{ivar.id}} : Set to #{config.{{ivar.id}}} (#{ivar_type})) | ||
success = true | ||
rescue | ||
# nop | ||
end | ||
end | ||
end | ||
{% end %} | ||
|
||
# Exit on fail | ||
if !success | ||
puts %(Config.{{ivar.id}} failed to parse #{env_value} as {{ivar.type}}) | ||
exit(1) | ||
end | ||
end | ||
{% 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) | ||
end | ||
end | ||
|
||
return config | ||
end | ||
|
||
module URIConverter | ||
def self.to_yaml(value : URI, yaml : YAML::Nodes::Builder) | ||
yaml.scalar value.normalize! | ||
end | ||
|
||
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : URI | ||
if node.is_a?(YAML::Nodes::Scalar) | ||
URI.parse node.value | ||
else | ||
node.raise "Expected scalar, not #{node.class}" | ||
end | ||
end | ||
end | ||
|
||
module FamilyConverter | ||
def self.to_yaml(value : Socket::Family, yaml : YAML::Nodes::Builder) | ||
case value | ||
when Socket::Family::UNSPEC | ||
yaml.scalar nil | ||
when Socket::Family::INET | ||
yaml.scalar "ipv4" | ||
when Socket::Family::INET6 | ||
yaml.scalar "ipv6" | ||
when Socket::Family::UNIX | ||
raise "Invalid socket family #{value}" | ||
end | ||
end | ||
|
||
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Socket::Family | ||
if node.is_a?(YAML::Nodes::Scalar) | ||
case node.value.downcase | ||
when "ipv4" | ||
Socket::Family::INET | ||
when "ipv6" | ||
Socket::Family::INET6 | ||
else | ||
Socket::Family::UNSPEC | ||
end | ||
else | ||
node.raise "Expected scalar, not #{node.class}" | ||
end | ||
end | ||
end | ||
|
||
module StringToCookies | ||
def self.to_yaml(value : HTTP::Cookies, yaml : YAML::Nodes::Builder) | ||
(value.map { |c| "#{c.name}=#{c.value}" }).join("; ").to_yaml(yaml) | ||
end | ||
|
||
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : HTTP::Cookies | ||
unless node.is_a?(YAML::Nodes::Scalar) | ||
node.raise "Expected scalar, not #{node.class}" | ||
end | ||
|
||
cookies = HTTP::Cookies.new | ||
node.value.split(";").each do |cookie| | ||
next if cookie.strip.empty? | ||
name, value = cookie.split("=", 2) | ||
cookies << HTTP::Cookie.new(name.strip, value.strip) | ||
end | ||
|
||
cookies | ||
end | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should also allow the location of the config file to be set during compile time, which can be done through macros.
Might be helpful for those who wish to deploy Invidious in a more traditional way, but don't want to call the binary with
INVIDIOUS_CONFIG_FILE
each time.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about also restoring the
-c / --config
option?