diff --git a/builder/src/build.rs b/builder/src/build.rs
index f213400..bd09d56 100644
--- a/builder/src/build.rs
+++ b/builder/src/build.rs
@@ -24,7 +24,7 @@ use common::repository::remote::download_packages;
use common::{
anyhow::{anyhow, bail, Result},
flate2::{write::GzEncoder, Compression},
- maestro_utils::{fhs, user::get_euid},
+ maestro_utils::user::get_euid,
package::{DependencyType, Package},
repository::{
get_package_with_constraint, get_recursive_dependencies, PackagesWithRepositoryMap,
@@ -112,7 +112,7 @@ pub struct BuildProcess {
/// - `input_path` is the path to the directory containing information to build the package
/// - `package` is the package to build
async fn create_sysroot(sysroot: &Path, input_path: &Path, package: &Package) -> Result<()> {
- if let Err(e) = fhs::create_dirs(sysroot, false) {
+ if let Err(e) = common::fhs::create_dirs(sysroot, false) {
bail!("FHS creation failed: {e}");
}
create_dev_nodes(sysroot)?;
diff --git a/common/src/fhs.rs b/common/src/fhs.rs
new file mode 100644
index 0000000..077bf19
--- /dev/null
+++ b/common/src/fhs.rs
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2026 Luc LenĂ´tre
+ *
+ * This file is part of Maestro.
+ *
+ * Maestro is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * Maestro. If not, see .
+ */
+
+use std::{error::Error, fs, io::ErrorKind, os::unix, path::Path};
+
+/// Creates the FHS folder hierarchy on the disk.
+///
+/// - `sysroot` is the path to the FHS system root.
+/// - `log` is whether to print on creation or not.
+pub fn create_dirs(sysroot: &Path, log: bool) -> Result<(), Box> {
+ let dirs = &[
+ "boot",
+ "dev",
+ "etc",
+ "home",
+ "media",
+ "mnt",
+ "opt",
+ "proc",
+ "root",
+ "run",
+ "srv",
+ "sys",
+ "tmp",
+ "usr",
+ "var",
+ "etc/opt",
+ "etc/sysconfig",
+ "run/lock",
+ "run/log",
+ "usr/bin",
+ "usr/include",
+ "usr/lib",
+ "usr/lib/firmware",
+ "usr/local",
+ "usr/sbin",
+ "usr/share",
+ "usr/src",
+ "usr/share/doc",
+ "usr/share/info",
+ "usr/share/locale",
+ "usr/share/man",
+ "usr/share/misc",
+ "usr/local/bin",
+ "usr/local/include",
+ "usr/local/lib",
+ "usr/local/sbin",
+ "usr/local/share",
+ "usr/local/src",
+ "usr/local/share/doc",
+ "usr/local/share/info",
+ "usr/local/share/locale",
+ "usr/local/share/man",
+ "usr/local/share/misc",
+ "var/cache",
+ "var/lib",
+ "var/local",
+ "var/log",
+ "var/mail",
+ "var/opt",
+ "var/spool",
+ "var/lib/misc",
+ ];
+ for path in dirs {
+ if log {
+ println!("Create directory `{path}`");
+ }
+ let path = sysroot.join(path);
+ match fs::create_dir(path) {
+ Ok(_) => {}
+ Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
+ Err(e) => return Err(e.into()),
+ }
+ }
+ let links: &[(&str, &str)] = &[
+ ("usr/bin", "bin"),
+ ("usr/sbin", "sbin"),
+ ("usr/lib", "lib"),
+ ("usr/lib", "lib64"),
+ ("lib", "usr/lib64"),
+ ];
+ for (target, link) in links {
+ if log {
+ println!("Create symlink `{link}` -> `{target}`");
+ }
+ let path = sysroot.join(link);
+ match unix::fs::symlink(target, &path) {
+ Ok(_) => {}
+ Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
+ Err(e) => return Err(e.into()),
+ }
+ }
+ Ok(())
+}
diff --git a/common/src/lib.rs b/common/src/lib.rs
index 448f1da..f05f897 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -27,6 +27,7 @@ pub use utils as maestro_utils;
#[cfg(feature = "network")]
pub mod download;
+pub mod fhs;
pub mod lock;
pub mod package;
pub mod repository;