asteroid/config.lisp

242 lines
7.9 KiB
Common Lisp

;; -*-lisp-*-
;; Configuration management for Asteroid Radio
;; This file centralizes all configuration parameters and provides
;; mechanisms to load them from environment variables or config files
(in-package :asteroid)
;;; Configuration Structure
;;; All configuration is loaded from environment variables with sensible defaults
(defclass asteroid-config ()
((server-port
:initarg :server-port
:accessor config-server-port
:initform 8080
:documentation "HTTP server port")
(music-library-path
:initarg :music-library-path
:accessor config-music-library-path
:initform nil
:documentation "Path to music library directory")
(stream-base-url
:initarg :stream-base-url
:accessor config-stream-base-url
:initform "http://localhost:8000"
:documentation "Base URL for Icecast stream server")
(icecast-admin-user
:initarg :icecast-admin-user
:accessor config-icecast-admin-user
:initform "admin"
:documentation "Icecast admin username")
(icecast-admin-password
:initarg :icecast-admin-password
:accessor config-icecast-admin-password
:initform nil
:documentation "Icecast admin password (MUST be set via environment)")
(supported-formats
:initarg :supported-formats
:accessor config-supported-formats
:initform '("mp3" "flac" "ogg" "wav")
:documentation "List of supported audio formats")
(max-history-size
:initarg :max-history-size
:accessor config-max-history-size
:initform 50
:documentation "Maximum number of tracks in stream history")
(database-backend
:initarg :database-backend
:accessor config-database-backend
:initform :i-lambdalite
:documentation "Database backend to use (:i-lambdalite or :postgresql)")
(postgres-host
:initarg :postgres-host
:accessor config-postgres-host
:initform "localhost"
:documentation "PostgreSQL host")
(postgres-port
:initarg :postgres-port
:accessor config-postgres-port
:initform 5432
:documentation "PostgreSQL port")
(postgres-database
:initarg :postgres-database
:accessor config-postgres-database
:initform "asteroid"
:documentation "PostgreSQL database name")
(postgres-user
:initarg :postgres-user
:accessor config-postgres-user
:initform "asteroid"
:documentation "PostgreSQL username")
(postgres-password
:initarg :postgres-password
:accessor config-postgres-password
:initform nil
:documentation "PostgreSQL password (MUST be set via environment)")
(tls-enabled
:initarg :tls-enabled
:accessor config-tls-enabled
:initform nil
:documentation "Whether TLS/HTTPS is enabled")
(tls-certificate-path
:initarg :tls-certificate-path
:accessor config-tls-certificate-path
:initform nil
:documentation "Path to TLS certificate file")
(tls-key-path
:initarg :tls-key-path
:accessor config-tls-key-path
:initform nil
:documentation "Path to TLS private key file"))
(:documentation "Configuration object for Asteroid Radio"))
;;; Global configuration instance
(defvar *config* nil
"Global configuration instance")
;;; Environment variable helpers
(defun getenv (name &optional default)
"Get environment variable NAME, or DEFAULT if not set"
(let ((value (uiop:getenv name)))
(if (and value (not (string= value "")))
value
default)))
(defun parse-integer-safe (string &optional default)
"Parse STRING as integer, return DEFAULT on error"
(handler-case
(parse-integer string)
(error () default)))
(defun parse-boolean (string)
"Parse STRING as boolean (true/false, yes/no, 1/0)"
(when string
(member (string-downcase string)
'("true" "yes" "1" "t" "y")
:test #'string=)))
;;; Configuration loading
(defun load-config-from-env ()
"Load configuration from environment variables"
(make-instance 'asteroid-config
:server-port (parse-integer-safe
(getenv "ASTEROID_SERVER_PORT")
8080)
:music-library-path (or (getenv "ASTEROID_MUSIC_PATH")
(merge-pathnames "music/library/"
(asdf:system-source-directory :asteroid)))
:stream-base-url (getenv "ASTEROID_STREAM_URL" "http://localhost:8000")
:icecast-admin-user (getenv "ICECAST_ADMIN_USER" "admin")
:icecast-admin-password (getenv "ICECAST_ADMIN_PASSWORD")
:supported-formats '("mp3" "flac" "ogg" "wav")
:max-history-size (parse-integer-safe
(getenv "ASTEROID_MAX_HISTORY")
50)
:database-backend (let ((backend (getenv "ASTEROID_DB_BACKEND")))
(cond
((string-equal backend "postgresql") :postgresql)
((string-equal backend "postgres") :postgresql)
(t :i-lambdalite)))
:postgres-host (getenv "POSTGRES_HOST" "localhost")
:postgres-port (parse-integer-safe
(getenv "POSTGRES_PORT")
5432)
:postgres-database (getenv "POSTGRES_DB" "asteroid")
:postgres-user (getenv "POSTGRES_USER" "asteroid")
:postgres-password (getenv "POSTGRES_PASSWORD")
:tls-enabled (parse-boolean (getenv "ASTEROID_TLS_ENABLED"))
:tls-certificate-path (getenv "ASTEROID_TLS_CERT")
:tls-key-path (getenv "ASTEROID_TLS_KEY")))
(defun init-config ()
"Initialize global configuration"
(unless *config*
(setf *config* (load-config-from-env)))
;; Validate critical configuration
(validate-config *config*)
*config*)
(defun validate-config (config)
"Validate configuration and warn about missing critical values"
(unless (config-icecast-admin-password config)
(warn "ICECAST_ADMIN_PASSWORD not set! Icecast status checks will fail."))
(when (eq (config-database-backend config) :postgresql)
(unless (config-postgres-password config)
(warn "POSTGRES_PASSWORD not set! PostgreSQL connection will fail.")))
(when (config-tls-enabled config)
(unless (and (config-tls-certificate-path config)
(config-tls-key-path config))
(error "TLS enabled but certificate or key path not configured!")))
t)
;;; Convenience accessors for backward compatibility
(defun get-config-value (key)
"Get configuration value by key (for backward compatibility)"
(unless *config*
(init-config))
(case key
(:server-port (config-server-port *config*))
(:music-library-path (config-music-library-path *config*))
(:stream-base-url (config-stream-base-url *config*))
(:icecast-admin-user (config-icecast-admin-user *config*))
(:icecast-admin-password (config-icecast-admin-password *config*))
(:supported-formats (config-supported-formats *config*))
(otherwise (error "Unknown configuration key: ~a" key))))
;;; Export configuration for display (without sensitive data)
(defun config-summary ()
"Return configuration summary (without passwords)"
(unless *config*
(init-config))
`(("Server Port" . ,(config-server-port *config*))
("Music Library" . ,(namestring (config-music-library-path *config*)))
("Stream URL" . ,(config-stream-base-url *config*))
("Icecast Admin User" . ,(config-icecast-admin-user *config*))
("Icecast Password Set" . ,(if (config-icecast-admin-password *config*) "Yes" "No"))
("Supported Formats" . ,(format nil "~{~a~^, ~}" (config-supported-formats *config*)))
("Database Backend" . ,(config-database-backend *config*))
("PostgreSQL Host" . ,(config-postgres-host *config*))
("PostgreSQL Port" . ,(config-postgres-port *config*))
("PostgreSQL Database" . ,(config-postgres-database *config*))
("PostgreSQL User" . ,(config-postgres-user *config*))
("PostgreSQL Password Set" . ,(if (config-postgres-password *config*) "Yes" "No"))
("TLS Enabled" . ,(if (config-tls-enabled *config*) "Yes" "No"))))