backend: harden rootless app lifecycle orchestration
This commit is contained in:
@@ -8,6 +8,8 @@ use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::update::host_sudo;
|
||||
|
||||
pub const DEFAULT_SRV_ROOT: &str = "/var/lib/archipelago/filebrowser";
|
||||
pub const DEFAULT_DATA_DIR: &str = "/var/lib/archipelago/filebrowser-data";
|
||||
pub const DEFAULT_CONFIG_PATH: &str = "/var/lib/archipelago/filebrowser-data/.filebrowser.json";
|
||||
@@ -39,17 +41,11 @@ pub enum EnsureOutcome {
|
||||
}
|
||||
|
||||
pub async fn ensure_config(paths: &EnsurePaths) -> Result<EnsureOutcome> {
|
||||
fs::create_dir_all(&paths.srv_root)
|
||||
.await
|
||||
.with_context(|| format!("creating {}", paths.srv_root.display()))?;
|
||||
fs::create_dir_all(&paths.data_dir)
|
||||
.await
|
||||
.with_context(|| format!("creating {}", paths.data_dir.display()))?;
|
||||
create_dir_all_or_sudo(&paths.srv_root).await?;
|
||||
create_dir_all_or_sudo(&paths.data_dir).await?;
|
||||
|
||||
for d in ["Documents", "Photos", "Music", "Downloads", "Builds"] {
|
||||
fs::create_dir_all(paths.srv_root.join(d))
|
||||
.await
|
||||
.with_context(|| format!("creating {}/{}", paths.srv_root.display(), d))?;
|
||||
create_dir_all_or_sudo(&paths.srv_root.join(d)).await?;
|
||||
}
|
||||
|
||||
if paths.config_path.exists() {
|
||||
@@ -60,27 +56,67 @@ pub async fn ensure_config(paths: &EnsurePaths) -> Result<EnsureOutcome> {
|
||||
.config_path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow::anyhow!("config_path has no parent directory"))?;
|
||||
fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("creating {}", parent.display()))?;
|
||||
create_dir_all_or_sudo(parent).await?;
|
||||
|
||||
let tmp = paths.config_path.with_extension("tmp");
|
||||
fs::write(&tmp, DEFAULT_CONFIG_JSON)
|
||||
.await
|
||||
.with_context(|| format!("writing tmp {}", tmp.display()))?;
|
||||
fs::rename(&tmp, &paths.config_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"renaming {} -> {}",
|
||||
tmp.display(),
|
||||
paths.config_path.display()
|
||||
)
|
||||
})?;
|
||||
write_config_atomically(paths).await?;
|
||||
|
||||
Ok(EnsureOutcome::Written)
|
||||
}
|
||||
|
||||
async fn create_dir_all_or_sudo(path: &std::path::Path) -> Result<()> {
|
||||
match fs::create_dir_all(path).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
let path = path.to_string_lossy();
|
||||
let status = host_sudo(&["mkdir", "-p", &path])
|
||||
.await
|
||||
.with_context(|| format!("creating {path} via sudo"))?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("mkdir -p {path} via sudo exited with {status}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e).with_context(|| format!("creating {}", path.display())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_config_atomically(paths: &EnsurePaths) -> Result<()> {
|
||||
let tmp = paths.config_path.with_extension("tmp");
|
||||
match fs::write(&tmp, DEFAULT_CONFIG_JSON).await {
|
||||
Ok(()) => {
|
||||
fs::rename(&tmp, &paths.config_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"renaming {} -> {}",
|
||||
tmp.display(),
|
||||
paths.config_path.display()
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
let script = format!(
|
||||
"set -eu\ncat > '{}' <<'FILEBROWSERCONF'\n{}FILEBROWSERCONF\n",
|
||||
shell_quote(&paths.config_path.to_string_lossy()),
|
||||
DEFAULT_CONFIG_JSON
|
||||
);
|
||||
let status = host_sudo(&["sh", "-lc", &script])
|
||||
.await
|
||||
.context("writing .filebrowser.json via sudo")?;
|
||||
if !status.success() {
|
||||
anyhow::bail!("writing .filebrowser.json via sudo exited with {status}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e).with_context(|| format!("writing tmp {}", tmp.display())),
|
||||
}
|
||||
}
|
||||
|
||||
fn shell_quote(s: &str) -> String {
|
||||
s.replace('\'', "'\\''")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user