Build front-end via Nix and harden Docker/Nix image generation

- Build the Flutter web app at release time (nix/frontend.nix) and bundle
  it next to the binary at $out/share/oott/web; resolve it at runtime
  relative to the executable. Remove the prebuilt backend/web from git.
- Add web_server.{ip_address,port,api_key} options to the NixOS module so
  the generated config deserializes (was missing, causing a startup panic).
- Docker image: set SSL_CERT_FILE for outbound TLS, drop the heavy
  nixos/nix base image, trim contents to [oott cacert], and ensure /tmp
  exists.
- Remove the unused "nix" flake input and commit flake.lock.
- Provide Swagger UI to utoipa-swagger-ui offline via a pinned fetchurl so
  the package builds in the Nix sandbox; skip the redundant check phase
  (tests run via backend/run_tests.sh).
- sample_oott.toml: set database.path to /db/oott.db for the Docker image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-01 21:43:08 -04:00
co-authored by Claude Opus 4.8
parent a90bcc360d
commit 20c51faaea
40 changed files with 138 additions and 166284 deletions
+36 -1
View File
@@ -1,4 +1,5 @@
use std::error::Error;
use std::path::{Path, PathBuf};
use crate::model::device_events::{DeviceEvent, DeviceEventScanner, DeviceEventType};
use crate::model::devices::{Device, DeviceSummary};
@@ -100,9 +101,24 @@ impl Modify for SecurityAddon {
}
}
/// Resolve the directory that holds the bundled front-end (Flutter web) assets.
///
/// The assets are installed next to the binary at `<exe_dir>/../share/oott/web`
/// (see the Nix package definition). When the executable location cannot be
/// determined we fall back to `./web` relative to the current directory.
fn resolve_web_root(exe_dir: Option<&Path>) -> PathBuf {
match exe_dir {
Some(dir) => dir.join("../share/oott/web"),
None => PathBuf::from("./web"),
}
}
pub async fn serve() -> Result<(), Box<dyn Error>> {
info!("Starting web server");
let static_files = ServeDir::new("./web");
let exe = std::env::current_exe().ok();
let web_root = resolve_web_root(exe.as_deref().and_then(Path::parent));
info!("Serving front-end assets from {}", web_root.display());
let static_files = ServeDir::new(web_root);
// Allow all origins and headers for API
let cors_layer = CorsLayer::new()
@@ -208,3 +224,22 @@ async fn auth(request: Request, next: Next) -> Result<Response, StatusCode> {
Err(StatusCode::UNAUTHORIZED)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn web_root_is_relative_to_the_executable() {
let root = resolve_web_root(Some(Path::new("/nix/store/abc-oott/bin")));
assert_eq!(
root,
PathBuf::from("/nix/store/abc-oott/bin/../share/oott/web")
);
}
#[test]
fn web_root_falls_back_to_local_dir_without_an_executable() {
assert_eq!(resolve_web_root(None), PathBuf::from("./web"));
}
}