Setup config folder to be a mounted volume
This commit is contained in:
parent
55a3ff7378
commit
fbafb2c1e1
4 changed files with 59 additions and 13 deletions
|
|
@ -60,7 +60,7 @@ COPY --from=frontend-build /app/frontend/dist/ ./frontend/dist/
|
|||
|
||||
# Create non-root user
|
||||
RUN useradd --create-home --shell /bin/bash timelapse && \
|
||||
mkdir -p output && \
|
||||
mkdir -p output config && \
|
||||
chown -R timelapse:timelapse /app
|
||||
|
||||
USER timelapse
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ use std::path::PathBuf;
|
|||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
/// Default path to the config file, relative to the working directory.
|
||||
pub const CONFIG_PATH: &str = "config/config.toml";
|
||||
|
||||
/// Main configuration struct.
|
||||
///
|
||||
/// This is the single source of truth for all configuration.
|
||||
|
|
@ -735,8 +738,30 @@ impl Config {
|
|||
|
||||
// Write to a temp file then atomically rename to avoid partial writes
|
||||
let tmp_path = path.with_extension("toml.tmp");
|
||||
std::fs::write(&tmp_path, content)?;
|
||||
std::fs::rename(&tmp_path, path)?;
|
||||
std::fs::write(&tmp_path, &content).map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
Error::Config(format!(
|
||||
"Permission denied writing config to '{}'. \
|
||||
If running in Docker, mount a writable volume at the config directory \
|
||||
(e.g. -v /host/config:/app/config).",
|
||||
tmp_path.display()
|
||||
))
|
||||
} else {
|
||||
Error::Io(e)
|
||||
}
|
||||
})?;
|
||||
std::fs::rename(&tmp_path, path).map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
Error::Config(format!(
|
||||
"Permission denied saving config to '{}'. \
|
||||
If running in Docker, mount a writable volume at the config directory \
|
||||
(e.g. -v /host/config:/app/config).",
|
||||
path.display()
|
||||
))
|
||||
} else {
|
||||
Error::Io(e)
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
src/main.rs
35
src/main.rs
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! Web server for creating selfie timelapses from Immich.
|
||||
|
||||
use immich_timelapse::{config::Config, models::DlibLandmarks, web};
|
||||
use immich_timelapse::{config::{Config, CONFIG_PATH}, models::DlibLandmarks, web};
|
||||
use std::net::SocketAddr;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
|
|
@ -61,13 +61,34 @@ async fn main() -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
fn load_config() -> anyhow::Result<Config> {
|
||||
// Try loading from config.toml first
|
||||
let config = if std::path::Path::new("config.toml").exists() {
|
||||
tracing::info!("Loading configuration from config.toml");
|
||||
Config::from_file("config.toml")?.with_env()
|
||||
let config_path = std::path::Path::new(CONFIG_PATH);
|
||||
|
||||
// Try loading from config/config.toml, or create a default one
|
||||
let config = if config_path.exists() {
|
||||
tracing::info!("Loading configuration from {}", CONFIG_PATH);
|
||||
Config::from_file(config_path)?.with_env()
|
||||
} else {
|
||||
tracing::info!("No config.toml found, using environment variables");
|
||||
Config::from_env()
|
||||
tracing::info!("No {} found, creating default config file", CONFIG_PATH);
|
||||
let default_config = Config::from_env();
|
||||
// Write default config so it can be customized via volume mount
|
||||
match default_config.save_to_file(config_path) {
|
||||
Ok(()) => tracing::info!("Default config written to {}", CONFIG_PATH),
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("permission denied") || msg.contains("Permission denied") {
|
||||
tracing::warn!(
|
||||
"Could not write default config to {}: permission denied. \
|
||||
If running in Docker, ensure the config directory is writable \
|
||||
(e.g. mount a volume with the correct permissions: \
|
||||
-v /host/config:/app/config). Continuing with in-memory defaults.",
|
||||
CONFIG_PATH
|
||||
);
|
||||
} else {
|
||||
tracing::warn!("Could not write default config to {}: {}", CONFIG_PATH, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
default_config
|
||||
};
|
||||
|
||||
// Warn but don't abort if config is incomplete - the server can still start
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use crate::config::{
|
||||
AlignmentConfig, BlurConfig, BrightnessConfig, EyeFilterConfig, FaceResolutionConfig,
|
||||
HeadPoseConfig, OutputConfig, ProcessingConfig, TimeIntervalConfig, TimestampConfig,
|
||||
VideoConfig,
|
||||
VideoConfig, CONFIG_PATH,
|
||||
};
|
||||
use crate::web::state::AppState;
|
||||
use axum::{extract::State, http::StatusCode, response::Json};
|
||||
|
|
@ -314,10 +314,10 @@ pub async fn update_config(
|
|||
|
||||
// Persist to file outside the lock using spawn_blocking for the fs write
|
||||
let save_result =
|
||||
tokio::task::spawn_blocking(move || config_snapshot.save_to_file("config.toml")).await;
|
||||
tokio::task::spawn_blocking(move || config_snapshot.save_to_file(CONFIG_PATH)).await;
|
||||
|
||||
match save_result {
|
||||
Ok(Ok(())) => tracing::info!("Configuration updated and persisted to config.toml"),
|
||||
Ok(Ok(())) => tracing::info!("Configuration updated and persisted to {}", CONFIG_PATH),
|
||||
Ok(Err(e)) => tracing::warn!("Failed to persist config to file: {}", e),
|
||||
Err(e) => tracing::warn!("Config save task panicked: {}", e),
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue