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
|
# Create non-root user
|
||||||
RUN useradd --create-home --shell /bin/bash timelapse && \
|
RUN useradd --create-home --shell /bin/bash timelapse && \
|
||||||
mkdir -p output && \
|
mkdir -p output config && \
|
||||||
chown -R timelapse:timelapse /app
|
chown -R timelapse:timelapse /app
|
||||||
|
|
||||||
USER timelapse
|
USER timelapse
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
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.
|
/// Main configuration struct.
|
||||||
///
|
///
|
||||||
/// This is the single source of truth for all configuration.
|
/// 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
|
// Write to a temp file then atomically rename to avoid partial writes
|
||||||
let tmp_path = path.with_extension("toml.tmp");
|
let tmp_path = path.with_extension("toml.tmp");
|
||||||
std::fs::write(&tmp_path, content)?;
|
std::fs::write(&tmp_path, &content).map_err(|e| {
|
||||||
std::fs::rename(&tmp_path, path)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
src/main.rs
35
src/main.rs
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! Web server for creating selfie timelapses from Immich.
|
//! 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 std::net::SocketAddr;
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
|
@ -61,13 +61,34 @@ async fn main() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_config() -> anyhow::Result<Config> {
|
fn load_config() -> anyhow::Result<Config> {
|
||||||
// Try loading from config.toml first
|
let config_path = std::path::Path::new(CONFIG_PATH);
|
||||||
let config = if std::path::Path::new("config.toml").exists() {
|
|
||||||
tracing::info!("Loading configuration from config.toml");
|
// Try loading from config/config.toml, or create a default one
|
||||||
Config::from_file("config.toml")?.with_env()
|
let config = if config_path.exists() {
|
||||||
|
tracing::info!("Loading configuration from {}", CONFIG_PATH);
|
||||||
|
Config::from_file(config_path)?.with_env()
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("No config.toml found, using environment variables");
|
tracing::info!("No {} found, creating default config file", CONFIG_PATH);
|
||||||
Config::from_env()
|
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
|
// Warn but don't abort if config is incomplete - the server can still start
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
AlignmentConfig, BlurConfig, BrightnessConfig, EyeFilterConfig, FaceResolutionConfig,
|
AlignmentConfig, BlurConfig, BrightnessConfig, EyeFilterConfig, FaceResolutionConfig,
|
||||||
HeadPoseConfig, OutputConfig, ProcessingConfig, TimeIntervalConfig, TimestampConfig,
|
HeadPoseConfig, OutputConfig, ProcessingConfig, TimeIntervalConfig, TimestampConfig,
|
||||||
VideoConfig,
|
VideoConfig, CONFIG_PATH,
|
||||||
};
|
};
|
||||||
use crate::web::state::AppState;
|
use crate::web::state::AppState;
|
||||||
use axum::{extract::State, http::StatusCode, response::Json};
|
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
|
// Persist to file outside the lock using spawn_blocking for the fs write
|
||||||
let save_result =
|
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 {
|
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),
|
Ok(Err(e)) => tracing::warn!("Failed to persist config to file: {}", e),
|
||||||
Err(e) => tracing::warn!("Config save task panicked: {}", e),
|
Err(e) => tracing::warn!("Config save task panicked: {}", e),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue