Fix production mode: resolve static/data paths relative to exe location, include static/ in release packages
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -73,6 +73,7 @@ jobs:
|
||||
RELEASE_DIR="${{ matrix.asset_name }}-${{ github.ref_name }}"
|
||||
mkdir -p "$RELEASE_DIR"
|
||||
cp "backend/target/${{ matrix.target }}/release/ironpad" "$RELEASE_DIR/"
|
||||
cp -r backend/static "$RELEASE_DIR/static"
|
||||
cp README.md LICENSE "$RELEASE_DIR/"
|
||||
tar czf "$RELEASE_DIR.tar.gz" "$RELEASE_DIR"
|
||||
echo "ASSET=$RELEASE_DIR.tar.gz" >> $GITHUB_ENV
|
||||
@@ -84,6 +85,7 @@ jobs:
|
||||
RELEASE_DIR="${{ matrix.asset_name }}-${{ github.ref_name }}"
|
||||
mkdir -p "$RELEASE_DIR"
|
||||
cp "backend/target/${{ matrix.target }}/release/ironpad.exe" "$RELEASE_DIR/"
|
||||
cp -r backend/static "$RELEASE_DIR/static"
|
||||
cp README.md LICENSE "$RELEASE_DIR/"
|
||||
7z a "$RELEASE_DIR.zip" "$RELEASE_DIR"
|
||||
echo "ASSET=$RELEASE_DIR.zip" >> $GITHUB_ENV
|
||||
|
||||
@@ -5,19 +5,39 @@ use std::sync::OnceLock;
|
||||
/// Priority: IRONPAD_DATA_DIR env var > auto-detect (production vs development).
|
||||
static DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
/// Directory where the executable lives.
|
||||
/// Used to resolve `static/` and `data/` in production mode.
|
||||
static EXE_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
/// Get the directory containing the executable.
|
||||
/// Falls back to "." if detection fails.
|
||||
pub fn exe_dir() -> &'static Path {
|
||||
EXE_DIR.get_or_init(|| {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if we're in production mode (static/index.html exists next to the binary).
|
||||
pub fn is_production() -> bool {
|
||||
exe_dir().join("static").join("index.html").exists()
|
||||
}
|
||||
|
||||
/// Initialize the data directory path. Call once at startup.
|
||||
///
|
||||
/// Resolution order:
|
||||
/// 1. `IRONPAD_DATA_DIR` environment variable (if set)
|
||||
/// 2. `./data` if `static/index.html` exists (production mode)
|
||||
/// 2. `{exe_dir}/data` if `{exe_dir}/static/index.html` exists (production mode)
|
||||
/// 3. `../data` (development mode, binary runs from backend/)
|
||||
pub fn init_data_dir() {
|
||||
let path = if let Ok(custom) = std::env::var("IRONPAD_DATA_DIR") {
|
||||
tracing::info!("Using custom data directory from IRONPAD_DATA_DIR");
|
||||
PathBuf::from(custom)
|
||||
} else if Path::new("static/index.html").exists() {
|
||||
} else if is_production() {
|
||||
// Production mode: data/ is next to the binary
|
||||
PathBuf::from("data")
|
||||
exe_dir().join("data")
|
||||
} else {
|
||||
// Development mode: binary runs from backend/, data/ is one level up
|
||||
PathBuf::from("../data")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
@@ -95,14 +94,19 @@ async fn main() {
|
||||
.layer(cors);
|
||||
|
||||
// Check for embedded frontend (production mode)
|
||||
let static_dir = Path::new("static");
|
||||
let has_frontend = static_dir.join("index.html").exists();
|
||||
// Resolve relative to the executable's directory, not the working directory
|
||||
let has_frontend = config::is_production();
|
||||
|
||||
if has_frontend {
|
||||
// Production mode: serve frontend from static/ and use SPA fallback
|
||||
info!("Production mode: serving frontend from static/");
|
||||
let serve_dir = ServeDir::new("static")
|
||||
.fallback(tower_http::services::ServeFile::new("static/index.html"));
|
||||
// Production mode: serve frontend from static/ next to the exe
|
||||
let static_path = config::exe_dir().join("static");
|
||||
let index_path = static_path.join("index.html");
|
||||
info!(
|
||||
"Production mode: serving frontend from {}",
|
||||
static_path.display()
|
||||
);
|
||||
let serve_dir =
|
||||
ServeDir::new(&static_path).fallback(tower_http::services::ServeFile::new(index_path));
|
||||
app = app.fallback_service(serve_dir);
|
||||
} else {
|
||||
// Development mode: API-only
|
||||
|
||||
Reference in New Issue
Block a user