137 lines
3.4 KiB
Ruby
137 lines
3.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "toml-rb"
|
|
require "fileutils"
|
|
|
|
module Volumen
|
|
# Loads and merges volumen configuration from a TOML file, then applies
|
|
# command-line overrides on top of the file values and built-in defaults.
|
|
class Config
|
|
DEFAULT_PATH = "/etc/volumen/config.toml"
|
|
|
|
DEFAULTS = {
|
|
"server" => { "host" => "0.0.0.0", "port" => 9090 },
|
|
"content_dir" => "/var/lib/volumen/posts",
|
|
"users_file" => "/var/lib/volumen/users.toml",
|
|
"site" => {
|
|
"title" => "My Blog",
|
|
"description" => "A blog powered by volumen.",
|
|
"base_url" => "https://example.com",
|
|
"language" => "en",
|
|
"author" => "Anonymous"
|
|
},
|
|
"admin" => {
|
|
"password_hash" => "",
|
|
"session_key" => "",
|
|
"session_ttl" => 86_400
|
|
}
|
|
}.freeze
|
|
|
|
TEMPLATE = <<~TOML
|
|
# volumen configuration.
|
|
|
|
[server]
|
|
host = "0.0.0.0"
|
|
port = 9090
|
|
|
|
# Directory of your Markdown posts (.md with TOML frontmatter).
|
|
content_dir = "/var/lib/volumen/posts"
|
|
|
|
# File storing admin users (managed from the admin Settings page).
|
|
users_file = "/var/lib/volumen/users.toml"
|
|
|
|
[site]
|
|
title = "My Blog"
|
|
description = "A blog powered by volumen."
|
|
base_url = "https://example.com"
|
|
language = "en"
|
|
author = "Anonymous"
|
|
|
|
[admin]
|
|
# Bootstrap admin login: paste a hash from `volumen hash-password` to sign
|
|
# in as user "admin". Once you manage users in Settings, users_file wins.
|
|
password_hash = ""
|
|
# Secret signing session cookies (>= 64 bytes). Empty = random per start.
|
|
session_key = ""
|
|
# Session lifetime in seconds (24h default).
|
|
session_ttl = 86400
|
|
TOML
|
|
|
|
def self.load(path: DEFAULT_PATH, overrides: {})
|
|
data = File.exist?(path) ? TomlRB.load_file(path) : {}
|
|
merged = deep_merge(DEFAULTS, data)
|
|
new(apply_overrides(merged, overrides))
|
|
end
|
|
|
|
# Writes the commented template to `path` if it does not exist yet.
|
|
# Returns the path on success, or nil if it already existed or could not
|
|
# be created.
|
|
def self.generate_default(path)
|
|
return nil if File.exist?(path)
|
|
|
|
FileUtils.mkdir_p(File.dirname(path))
|
|
File.write(path, TEMPLATE)
|
|
path
|
|
rescue SystemCallError
|
|
nil
|
|
end
|
|
|
|
def self.deep_merge(base, override)
|
|
base.merge(override) do |_key, base_val, override_val|
|
|
if base_val.is_a?(Hash) && override_val.is_a?(Hash)
|
|
deep_merge(base_val, override_val)
|
|
else
|
|
override_val
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.apply_overrides(data, overrides)
|
|
server = data["server"].dup
|
|
server["host"] = overrides[:host] if overrides[:host]
|
|
server["port"] = overrides[:port] if overrides[:port]
|
|
|
|
result = data.dup
|
|
result["server"] = server
|
|
result["content_dir"] = overrides[:content] if overrides[:content]
|
|
result
|
|
end
|
|
|
|
private_class_method :deep_merge, :apply_overrides
|
|
|
|
attr_reader :data
|
|
|
|
def initialize(data)
|
|
@data = data
|
|
end
|
|
|
|
def host
|
|
@data["server"]["host"]
|
|
end
|
|
|
|
def port
|
|
@data["server"]["port"]
|
|
end
|
|
|
|
def content_dir
|
|
@data["content_dir"]
|
|
end
|
|
|
|
def users_file
|
|
@data["users_file"]
|
|
end
|
|
|
|
def site
|
|
@data["site"]
|
|
end
|
|
|
|
def language
|
|
@data["site"]["language"]
|
|
end
|
|
|
|
def admin
|
|
@data["admin"]
|
|
end
|
|
end
|
|
end
|