Compare commits

..

10 Commits

Author SHA1 Message Date
jonathan
a0a31eae62 removed unionfs because we manually sync the differences 2025-10-12 12:37:37 +02:00
jonathan
e8b2a52571 very early commit 2025-10-12 01:25:36 +02:00
jonathan
6a692cd666 updated project description 2025-10-12 01:25:04 +02:00
jonathan
923b03a34d removed var/log from tmpfs mount as /var is already mounted as tmpfs 2025-10-12 01:23:07 +02:00
jonathan
81f0741809 removed early mount service 2025-10-12 01:21:22 +02:00
jonathan
f77ca7f659 working defconfig 2025-10-12 01:19:41 +02:00
jonathan
7bfb18949b fixed network configuration 2025-10-12 01:19:07 +02:00
jonathan
466ee9d188 added pre-init script that setup things before starting systemd 2025-10-12 01:17:57 +02:00
jonathan
1ab48bdc2d added own service for creating overlays 2025-10-11 19:19:58 +02:00
jonathan
3489e7fba8 changed system to systemd 2025-10-11 19:19:02 +02:00
10 changed files with 525 additions and 33 deletions

View File

@@ -0,0 +1,4 @@
# nimux/Config.in
menu "Nimux packages"
source "$BR2_EXTERNAL_NIMUX_PATH/package/python-xonsh/Config.in"
endmenu

View File

@@ -1,60 +1,47 @@
BR2_x86_64=y BR2_x86_64=y
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_12=y BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_12=y
BR2_GLOBAL_PATCH_DIR="board/qemu/patches" BR2_GLOBAL_PATCH_DIR="board/qemu/patches"
#BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_NIMUX_PATH)/patches" BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
BR2_RELRO_PARTIAL=y BR2_RELRO_PARTIAL=y
BR2_FORTIFY_SOURCE_2=y BR2_FORTIFY_SOURCE_2=y
BR2_TARGET_GENERIC_HOSTNAME="nimux" BR2_TARGET_GENERIC_HOSTNAME="nimux"
BR2_TARGET_GENERIC_ISSUE="Welcome to Nimux!" BR2_TARGET_GENERIC_ISSUE="Welcome to Nimux!"
BR2_TARGET_GENERIC_PASSWD_SHA512=y BR2_TARGET_GENERIC_PASSWD_SHA512=y
BR2_TARGET_ROOTFS_CPIO=y BR2_INIT_SYSTEMD=y
BR2_TARGET_ROOTFS_CPIO_GZIP=y BR2_INIT_SYSTEMD_VAR_NONE=y
BR2_TARGET_ENABLE_ROOT_LOGIN=y
BR2_TARGET_GENERIC_ROOT_PASSWD="hellotux" BR2_TARGET_GENERIC_ROOT_PASSWD="hellotux"
BR2_TARGET_ROOTFS_INITRAMFS=y
BR2_TARGET_ROOTFS_ISO9660=y
BR2_TARGET_ROOTFS_ISO9660_ISOLINUX=y
BR2_TARGET_SYSLINUX=y
BR2_TARGET_SYSLINUX_LEGACY_BIOS=y
BR2_TARGET_SYSLINUX_ISOLINUX=y
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
BR2_SYSTEM_BIN_SH_BASH=y BR2_SYSTEM_BIN_SH_BASH=y
BR2_SYSTEM_BIN_SH="bash" # BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set
BR2_SYSTEM_DHCP="eth0"
BR2_SYSTEM_DEFAULT_PATH="/bin:/sbin:/usr/bin:/usr/sbin"
BR2_ENABLE_LOCALE_WHITELIST="C C.UTF-8 en_US en_US.UTF-8" BR2_ENABLE_LOCALE_WHITELIST="C C.UTF-8 en_US en_US.UTF-8"
BR2_GENERATE_LOCALE="C en_US" BR2_GENERATE_LOCALE="C en_US"
BR2_TARGET_TZ_INFO=y
BR2_TARGET_LOCALTIME="Europe/Amsterdam" BR2_TARGET_LOCALTIME="Europe/Amsterdam"
BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_NIMUX_PATH)/rootfs-overlay" BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_NIMUX_PATH)/rootfs-overlay"
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh"
BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.27" BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.27"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_NIMUX_PATH)/board/qemu/x86_64/linux.config" BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_NIMUX_PATH)/board/qemu/x86_64/linux.config"
#BR2_LINUX_KERNEL_XZ=y
#BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
BR2_PACKAGE_LINUX_TOOLS_CPUPOWER=y BR2_PACKAGE_LINUX_TOOLS_CPUPOWER=y
#BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_NIMUX_PATH)/board/pc/busybox.config"
BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y
BR2_PACKAGE_SQUASHFS=y BR2_PACKAGE_SQUASHFS=y
BR2_PACKAGE_SQUASHFS_GZIP=y
BR2_PACKAGE_SSHFS=y
BR2_PACKAGE_ZFS=y BR2_PACKAGE_ZFS=y
BR2_PACKAGE_ACPID=y BR2_PACKAGE_ACPID=y
BR2_PACKAGE_EUDEV_RULES_GEN=y
BR2_PACKAGE_HDPARM=y BR2_PACKAGE_HDPARM=y
BR2_PACKAGE_HWDATA=y BR2_PACKAGE_HWDATA=y
BR2_PACKAGE_HWDATA_IAB_OUI_TXT=y BR2_PACKAGE_HWDATA_IAB_OUI_TXT=y
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON_SETUPTOOLS=y
BR2_PACKAGE_CA_CERTIFICATES=y BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_LIBCURL=y
BR2_PACKAGE_LIBCURL_CURL=y BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_LIBCAP=y BR2_PACKAGE_LIBGLIB2=y
BR2_PACKAGE_BRIDGE_UTILS=y BR2_PACKAGE_BRIDGE_UTILS=y
BR2_PACKAGE_IFTOP=y BR2_PACKAGE_IFTOP=y
BR2_PACKAGE_NTP=y BR2_PACKAGE_NTP=y
BR2_PACKAGE_NANO=y BR2_PACKAGE_OPENSSH=y
BR2_PACKAGE_NANO_TINY=n
BR2_PACKAGE_BASH=y
BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_BASH_COMPLETION=y
BR2_PACKAGE_INOTIFY_TOOLS=y BR2_PACKAGE_INOTIFY_TOOLS=y
BR2_PACKAGE_SUDO=y BR2_PACKAGE_SUDO=y
@@ -62,12 +49,11 @@ BR2_PACKAGE_CPULOAD=y
BR2_PACKAGE_DOCKER_CLI=y BR2_PACKAGE_DOCKER_CLI=y
BR2_PACKAGE_DOCKER_COMPOSE=y BR2_PACKAGE_DOCKER_COMPOSE=y
BR2_PACKAGE_DOCKER_ENGINE=y BR2_PACKAGE_DOCKER_ENGINE=y
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y BR2_PACKAGE_SYSTEMD_ANALYZE=y
BR2_SYSTEM_DHCP="eth0" BR2_PACKAGE_NANO=y
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh" # BR2_PACKAGE_NANO_TINY is not set
#BR2_ROOTFS_POST_IMAGE_SCRIPT="board/qemu/post-image.sh" BR2_TARGET_ROOTFS_SQUASHFS=y
BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
# BR2_TARGET_ROOTFS_EXT2=y
# BR2_TARGET_ROOTFS_TAR is not set # BR2_TARGET_ROOTFS_TAR is not set
BR2_PACKAGE_HOST_QEMU=y BR2_PACKAGE_HOST_QEMU=y
BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y
BR2_PACKAGE_PYTHON_XONSH=y

View File

@@ -1,2 +1,2 @@
name: NIMUX name: NIMUX
desc: The custom buildroot overlay for nimux desc: Nimux is the imutable lightweigth HomeLab OS with docker in its core

View File

@@ -5,6 +5,6 @@ tmpfs /dev/shm tmpfs nosuid,nodev,mode=0777
tmpfs /tmp tmpfs nosuid,nodev,mode=1777 0 0 tmpfs /tmp tmpfs nosuid,nodev,mode=1777 0 0
tmpfs /run tmpfs nosuid,nodev,mode=0755 0 0 tmpfs /run tmpfs nosuid,nodev,mode=0755 0 0
tmpfs /mnt tmpfs nosuid,nodev,mode=0755 0 0 tmpfs /mnt tmpfs nosuid,nodev,mode=0755 0 0
tmpfs /var/log tmpfs nosuid,nodev,mode=0775 0 0 tmpfs /root tmpfs nosuid,nodev,mode=0755 0 0
sysfs /sys sysfs defaults,nosuid,nodev,noexec 0 0 sysfs /sys sysfs defaults,nosuid,nodev,noexec 0 0
cgroup2 /sys/fs/cgroup cgroup2 defaults,nosuid,nodev,noexec 0 0 cgroup2 /sys/fs/cgroup cgroup2 defaults,nosuid,nodev,noexec 0 0

View File

@@ -0,0 +1,11 @@
[Match]
# Match all real Ethernet interfaces
Type=ether
Name=!lo !docker* !veth* !br* !virbr* !tap* !tun* !vmnet* !zt* !tailscale* !wg*
[Network]
DHCP=yes
IPv6AcceptRA=yes
[DHCP]
UseDNS=yes

View File

@@ -0,0 +1,3 @@
[Resolve]
DNSSEC=no
DNSOverTLS=no

View File

@@ -0,0 +1,23 @@
#!/bin/xonsh --no-rc
# Nimux Early Mount (xonsh)
# #mount /mnt
# print("mount /mnt")
# /bin/mount /mnt
# # mount root on future accesable place
# print("re-mount rootfs")
# /bin/mkdir -p /mnt/rootfs
# /bin/mount --bind / /mnt/rootfs
# /bin/mount --make-rprivate /mnt/rootfs/
# # import zpool
# print("import zpool")
# /sbin/zpool import nimux-zfs
# # overlay etc and home with a persistant zfs dataset
# print("overlay mount etc")
# /bin/unionfs -o cow,nonempty /mnt/rootfs.overlay/etc=RW:/mnt/rootfs/etc=RO /etc
# print("overlay mount home")
# /bin/unionfs -o cow,nonempty /mnt/rootfs.overlay/home=RW:/mnt/rootfs/home=RO /home

View File

@@ -0,0 +1,383 @@
# /etc/profile.d/nimux-first-login.sh
# Nimux first-login bootstrap (mirror) — numbered disk selection + safeguards + default proposal
case $- in *i*) ;; *) return 0 ;; esac
SENTINEL="/var/lib/nimux/firstboot.done"
[ -f "$SENTINEL" ] && return 0
run_bootstrap() {
set -euo pipefail
need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing: $1"; exit 1; }; }
need zpool; need zfs; need lsblk; command -v blkid >/dev/null 2>&1 || true; command -v blockdev >/dev/null 2>&1 || true
CUR_HOST="$(hostname)"
POOL="${CUR_HOST}-zfs"
FSTAB="/etc/fstab"
OVERLAY_ROOT="/mnt/overlays/root"
ADMINUSER=""
NEW_LANG="${LANG:-en_US.UTF-8}"
DISK1=""; DISK2=""
DEFAULT_IDX1=""; DEFAULT_IDX2=""
# -------------------- Helpers --------------------
canonical() { readlink -f "$1"; }
has_partitions() {
# returns 0 (true) if the disk has child partitions
local d="$1"
lsblk -n "$d" -o TYPE | grep -q '^part$'
}
fstype_of() {
# Try lsblk first, then blkid
local d="$1" fs
fs="$(lsblk -dn -o FSTYPE "$d" 2>/dev/null | tr -d ' ')"
if [ -z "$fs" ] && command -v blkid >/dev/null 2>&1; then
fs="$(blkid -o value -s TYPE "$d" 2>/dev/null | tr -d ' ')"
fi
echo "${fs:-}"
}
bytes_of() {
# size in bytes (fallback to lsblk if blockdev not present)
local d="$1"
if command -v blockdev >/dev/null 2>&1; then
blockdev --getsize64 "$d" 2>/dev/null || echo 0
else
lsblk -bdn -o SIZE "$d" 2>/dev/null || echo 0
fi
}
is_whole_disk() {
# TYPE=disk
local d="$1"
[ "$(lsblk -dn -o TYPE "$d" 2>/dev/null)" = "disk" ]
}
is_zfs_member() {
local d="$1"
[ "$(fstype_of "$d")" = "zfs_member" ]
}
print_disks_table() {
echo "Available whole disks:"
printf " %-5s %-20s %-12s %-12s %s\n" "No." "PATH" "SIZE" "FSTYPE" "MODEL"
local i=1
while read -r name size type fstype model; do
[ "$type" = "disk" ] || continue
local path="/dev/$name"
local sz="$size"
printf " [%d] %-20s %-12s %-12s %s\n" "$i" "$path" "$sz" "${fstype:--}" "${model:-"-"}"
i=$((i+1))
done < <(lsblk -dn -o NAME,SIZE,TYPE,FSTYPE,MODEL)
}
build_disk_arrays() {
# Build arrays for selection UI
mapfile -t DISK_LINES < <(lsblk -dn -o NAME,SIZE,TYPE,FSTYPE,MODEL | awk '$3=="disk"{print}')
mapfile -t DISK_PATHS < <(printf "%s\n" "${DISK_LINES[@]}" | awk '{print "/dev/"$1}')
[ "${#DISK_PATHS[@]}" -ge 1 ] || { echo "No whole disks found."; exit 1; }
}
propose_two_largest_empty() {
# Choose two largest disks that are: TYPE=disk, no partitions, no FSTYPE, not zfs_member
local candidates=()
local sizes=()
for p in "${DISK_PATHS[@]}"; do
is_whole_disk "$p" || continue
if has_partitions "$p"; then continue; fi
local fs; fs="$(fstype_of "$p")"
[ -n "$fs" ] && continue
is_zfs_member "$p" && continue
local b; b="$(bytes_of "$p")"
candidates+=("$p"); sizes+=("$b")
done
if [ "${#candidates[@]}" -lt 2 ]; then
# Not enough clean disks to propose
DEFAULT_IDX1=""; DEFAULT_IDX2=""
return 0
fi
# Sort candidates by size desc (simple bubble for small lists)
local idxs=()
local i; for ((i=0;i<${#candidates[@]};i++)); do idxs+=("$i"); done
local swapped=1 j tmp
while [ $swapped -eq 1 ]; do
swapped=0
for ((j=0;j<${#idxs[@]}-1;j++)); do
if [ "${sizes[${idxs[$j]}]}" -lt "${sizes[${idxs[$j+1]}]}" ]; then
tmp="${idxs[$j]}"; idxs[$j]="${idxs[$j+1]}"; idxs[$j+1]="$tmp"; swapped=1
fi
done
done
local top1="${candidates[${idxs[0]}]}"
local top2="${candidates[${idxs[1]}]}"
# Map to numbered list indices shown to user
local n i=1
for n in "${DISK_PATHS[@]}"; do
if [ "$(canonical "$n")" = "$(canonical "$top1")" ]; then DEFAULT_IDX1="$i"; fi
if [ "$(canonical "$n")" = "$(canonical "$top2")" ]; then DEFAULT_IDX2="$i"; fi
i=$((i+1))
done
}
warn_if_risky() {
# Return 0 if OK; non-zero if user declines risk
local d="$1" label="$2"
local risk=0 msg=""
if has_partitions "$d"; then risk=1; msg+="\n - $label has existing partitions"; fi
local fs; fs="$(fstype_of "$d")"
if [ -n "$fs" ]; then risk=1; msg+="\n - $label has existing filesystem: $fs"; fi
if [ "$risk" -eq 0 ]; then return 0; fi
echo -e "WARNING:$msg"
read -r -p "Type 'force' to override and continue with $label: " force
[ "${force:-}" = "force" ] || { echo "Refusing risky selection for $label."; return 1; }
return 0
}
pick_two_disks() {
echo
print_disks_table
build_disk_arrays
propose_two_largest_empty
local default_hint=""
if [ -n "$DEFAULT_IDX1" ] && [ -n "$DEFAULT_IDX2" ]; then
default_hint=" [${DEFAULT_IDX1} ${DEFAULT_IDX2}]"
echo
echo "Proposed (largest clean disks): ${DEFAULT_IDX1} and ${DEFAULT_IDX2}"
echo "Press Enter to accept, or type two numbers to override."
fi
while :; do
echo
read -r -p "Select TWO disks by number (e.g. '1 3')${default_hint}: " CHOICE
if [ -z "${CHOICE// }" ] && [ -n "$DEFAULT_IDX1" ] && [ -n "$DEFAULT_IDX2" ]; then
idx1="$DEFAULT_IDX1"; idx2="$DEFAULT_IDX2"
else
CHOICE="$(echo "$CHOICE" | tr ',' ' ' | xargs)"
set +e; set -- $CHOICE; set -e
if [ $# -ne 2 ]; then echo "Please provide exactly two numbers."; continue; fi
idx1="$1"; idx2="$2"
fi
case "$idx1" in (*[!0-9]*|'') echo "Invalid first selection."; continue;; esac
case "$idx2" in (*[!0-9]*|'') echo "Invalid second selection."; continue;; esac
[ "$idx1" -ge 1 ] && [ "$idx1" -le "${#DISK_PATHS[@]}" ] || { echo "First number out of range."; continue; }
[ "$idx2" -ge 1 ] && [ "$idx2" -le "${#DISK_PATHS[@]}" ] || { echo "Second number out of range."; continue; }
[ "$idx1" -ne "$idx2" ] || { echo "Disks must be different."; continue; }
local cand1="${DISK_PATHS[$((idx1-1))]}"
local cand2="${DISK_PATHS[$((idx2-1))]}"
# Risk checks
warn_if_risky "$cand1" "Disk 1" || continue
warn_if_risky "$cand2" "Disk 2" || continue
DISK1="$cand1"; DISK2="$cand2"
echo
echo "Selected:"
echo " Disk 1: $DISK1"
echo " Disk 2: $DISK2"
read -r -p "Proceed to create MIRRORED pool on these disks? [y/N]: " yn
case "${yn:-N}" in y|Y) break;; *) echo "Re-selecting…";; esac
done
}
create_pool_and_datasets() {
echo
echo "== Creating mirrored ZFS pool and datasets =="
if zpool list -H -o name 2>/dev/null | grep -qx "$POOL"; then
echo "Pool '$POOL' already exists; skipping creation."
else
ASHIFT="12"
zpool create -f \
-o ashift="$ASHIFT" \
-O compression=zstd -O atime=off -O xattr=sa -O acltype=posixacl \
"$POOL" mirror "$DISK1" "$DISK2"
fi
zfs set mountpoint=none "$POOL" || true
create_ds() { # $1=dataset $2=mountpoint|"none"
local ds="$1" mp="$2"
if ! zfs list -H -o name | grep -qx "$ds"; then
if [ "$mp" = "none" ]; then
zfs create -o mountpoint=none "$ds"
else
zfs create -o mountpoint="$mp" "$ds"
fi
else
[ "$mp" = "none" ] && zfs set mountpoint=none "$ds" || zfs set mountpoint="$mp" "$ds"
fi
}
create_ds "${POOL}" "none"
create_ds "${POOL}/data" "/mnt/data"
create_ds "${POOL}/docker" "/var/lib/docker"
create_ds "${POOL}/overlay-layers" "none"
create_ds "${POOL}/overlay-layers/root" "$OVERLAY_ROOT"
mkdir -p /mnt/data /var/lib/docker "$OVERLAY_ROOT"
zfs mount -a
}
setup_overlays() {
echo
echo "== Setting up overlays for /etc and /home =="
mkdir -p "${OVERLAY_ROOT}/etc" "${OVERLAY_ROOT}/etc.work" \
"${OVERLAY_ROOT}/home" "${OVERLAY_ROOT}/home.work"
chmod 700 "${OVERLAY_ROOT}/etc.work" "${OVERLAY_ROOT}/home.work"
touch "$FSTAB"
add_fstab_line() { local line="$1" pattern="$2"; grep -qsE "$pattern" "$FSTAB" || echo "$line" >> "$FSTAB"; }
add_fstab_line \
"ZFS=${POOL}/overlay-layers/root $OVERLAY_ROOT zfs defaults,nofail 0 0" \
"^ZFS=${POOL}/overlay-layers/root[[:space:]]+$OVERLAY_ROOT[[:space:]]+zfs"
add_fstab_line \
"overlay /etc overlay nofail,x-systemd.requires-mounts-for=$OVERLAY_ROOT,lowerdir=/etc,upperdir=${OVERLAY_ROOT}/etc,workdir=${OVERLAY_ROOT}/etc.work 0 0" \
"^overlay[[:space:]]+/etc[[:space:]]+overlay.*upperdir=${OVERLAY_ROOT}/etc,workdir=${OVERLAY_ROOT}/etc.work"
add_fstab_line \
"overlay /home overlay nofail,x-systemd.requires-mounts-for=$OVERLAY_ROOT,lowerdir=/home,upperdir=${OVERLAY_ROOT}/home,workdir=${OVERLAY_ROOT}/home.work 0 0" \
"^overlay[[:space:]]+/home[[:space:]]+overlay.*upperdir=${OVERLAY_ROOT}/home,workdir=${OVERLAY_ROOT}/home.work"
}
ask_hostname_and_maybe_rename_pool() {
echo
echo "== Hostname =="
read -r -p "New hostname (leave blank to keep '$CUR_HOST'): " NEW_HOSTNAME
NEW_HOSTNAME="${NEW_HOSTNAME:-$CUR_HOST}"
if command -v hostnamectl >/dev/null 2>&1; then
hostnamectl set-hostname "$NEW_HOSTNAME"
else
echo "$NEW_HOSTNAME" > /etc/hostname
fi
local NEWPOOL="${NEW_HOSTNAME}-zfs"
if [ "$NEWPOOL" != "$POOL" ]; then
echo "Renaming pool '$POOL' -> '$NEWPOOL' via export/import…"
zfs unmount -a || true
sed -i "s/ZFS=${POOL}\//ZFS=${NEWPOOL}\//g" "$FSTAB"
zpool export "$POOL"
zpool import "$POOL" "$NEWPOOL"
zfs set mountpoint=none "$NEWPOOL" || true
zfs set mountpoint=/mnt/data "$NEWPOOL/data" || true
zfs set mountpoint=/var/lib/docker "$NEWPOOL/docker" || true
zfs set mountpoint=none "$NEWPOOL/overlay-layers" || true
zfs set mountpoint="$OVERLAY_ROOT" "$NEWPOOL/overlay-layers/root" || true
zfs mount -a
POOL="$NEWPOOL"
fi
}
set_locale_step() {
echo
echo "== Locale =="
read -r -p "Locale (LANG) [${NEW_LANG}]: " LANG_INPUT
NEW_LANG="${LANG_INPUT:-$NEW_LANG}"
printf 'LANG=%s\n' "$NEW_LANG" > /etc/locale.conf
if command -v localectl >/dev/null 2>&1; then
localectl set-locale "LANG=$NEW_LANG" || true
fi
}
create_admin_user() {
echo
echo "== Create admin user with sudo rights =="
while :; do
read -r -p "Admin username (e.g., nimuxadmin): " ADMINUSER
[ -n "${ADMINUSER:-}" ] && break
echo "Username cannot be empty."
done
if id "$ADMINUSER" >/dev/null 2>&1; then
echo "User '$ADMINUSER' already exists."
else
if [ -x /bin/bash ]; then SHELLPATH=/bin/bash
elif [ -x /bin/sh ]; then SHELLPATH=/bin/sh
else SHELLPATH=/bin/sh; fi
useradd -m -s "$SHELLPATH" "$ADMINUSER"
echo "Set password for $ADMINUSER:"
passwd "$ADMINUSER"
fi
if command -v sudo >/dev/null 2>&1; then
getent group sudo >/dev/null 2>&1 || groupadd sudo
usermod -aG sudo "$ADMINUSER" || true
mkdir -p /etc/sudoers.d
SUDO_FILE="/etc/sudoers.d/90-${ADMINUSER}"
[ -f "$SUDO_FILE" ] || { echo "${ADMINUSER} ALL=(ALL) ALL" > "$SUDO_FILE"; chmod 440 "$SUDO_FILE"; }
else
echo "NOTE: 'sudo' not installed; grant temporary root via 'su' until sudo is available."
fi
}
disable_root_login() {
echo
echo "== Disable root logins =="
passwd -l root || true
if [ -f /etc/ssh/sshd_config ]; then
if grep -qE '^\s*PermitRootLogin\s+' /etc/ssh/sshd_config; then
sed -i 's/^\s*PermitRootLogin\s\+.*/PermitRootLogin no/' /etc/ssh/sshd_config
else
printf '\nPermitRootLogin no\n' >> /etc/ssh/sshd_config
fi
if grep -qE '^\s*PermitEmptyPasswords\s+' /etc/ssh/sshd_config'; then
sed -i 's/^\s*PermitEmptyPasswords\s\+.*/PermitEmptyPasswords no/' /etc/ssh/sshd_config
else
printf 'PermitEmptyPasswords no\n' >> /etc/ssh/sshd_config
fi
(command -v systemctl >/dev/null 2>&1 && systemctl reload sshd) || \
(command -v rc-service >/dev/null 2>&1 && rc-service sshd reload) || true
fi
}
mark_completion() {
mkdir -p "$(dirname "$SENTINEL")"
echo "ok" > "$SENTINEL"
echo
echo "Bootstrap complete."
echo " - Pool: ${POOL}"
echo " - Overlays for /etc and /home will take effect on next reboot."
echo " - Locale: ${NEW_LANG}"
echo " - Admin user: ${ADMINUSER}"
echo " - Root login disabled"
}
# -------------------- Ordered flow --------------------
echo
echo "=== Nimux First-Login ==="
build_disk_arrays
pick_two_disks
create_pool_and_datasets
setup_overlays
ask_hostname_and_maybe_rename_pool
set_locale_step
create_admin_user
disable_root_login
mark_completion
}
if [ "$EUID" -ne 0 ]; then
echo
echo "[Nimux] First-boot setup needs elevated privileges. Re-running with sudo…"
echo
sudo bash -c "$(declare -f run_bootstrap); run_bootstrap" && \
echo "Setup completed (or already done)." || \
echo "Setup aborted or failed."
else
run_bootstrap
fi
[ -f "$SENTINEL" ] && chmod -x /etc/profile.d/nimux-first-login.sh 2>/dev/null || true

View File

@@ -0,0 +1,21 @@
[Unit]
Description=Nimux early mount: ZFS import + overlay for /etc and /home
DefaultDependencies=no
Before=sysinit.target
Conflicts=shutdown.target
Wants=sysinit.target
[Service]
Type=oneshot
RemainAfterExit=yes
Environment=XONSH_HISTORY_BACKEND=none
Environment=XONSH_CACHE_DIR=/dev/null
Environment=XONSH_DATA_DIR=/dev/null
Environment=HOME=/tmp/xonsh-home
ExecStart=/usr/lib/nimux/nimux-early-mount.xsh
TimeoutStartSec=30s
[Install]
WantedBy=sysinit.target

61
rootfs-overlay/usr/sbin/init Executable file
View File

@@ -0,0 +1,61 @@
#!/bin/sh
# Nimux pre-systemd boot
export ROOT_DEV=/dev/ram0
export RUN_NIMUX=/run/nimux
export ROOTFS_ORIG=$RUN_NIMUX/rootfs.orig
export NIMUX_OVERLAY=$RUN_NIMUX/overlay
# create the minimal required mount
/bin/echo "[init] Cmdline:" "$@"
/bin/echo "[init] perform early mounts"
/bin/mount -t proc proc /proc
/bin/mount -t sysfs sysfs /sys
/bin/mount -t devtmpfs devtmpfs /dev
/bin/mount -t tmpfs tmpfs /run
/bin/mount -t tmpfs tmpfs /mnt
# mount root on a future accesable place
/bin/echo "[init] re-mount rootfs"
/bin/mkdir -p $ROOTFS_ORIG
/bin/mount -t squashfs -o ro $ROOT_DEV $ROOTFS_ORIG
/bin/mount --make-rprivate $ROOTFS_ORIG
# overlay the /var directory with a volatile tmpfs
/bin/echo "[init] overlay /var"
export VAR_LOWER=$ROOTFS_ORIG/var
export VAR_UPPER=$NIMUX_OVERLAY/var/upper
export VAR_WORK=$NIMUX_OVERLAY/var/work
/bin/mkdir -p $VAR_UPPER $VAR_WORK
/bin/mount -t overlay overlay-var -o lowerdir=$VAR_LOWER,upperdir=$VAR_UPPER,workdir=$VAR_WORK /var
# ovelay /etc and patch with persistant storage
# we don´t overlay on persistent storage, because this will cause bootfailures
# when the lower dir (rootfs.squashfs) is updated by a future release of Numix
/bin/echo "[init] overlay /etc"
export ETC_LOWER=$ROOTFS_ORIG/etc
export ETC_UPPER=$NIMUX_OVERLAY/etc/upper
export ETC_WORK=$NIMUX_OVERLAY/etc/work
/bin/mkdir -p $ETC_UPPER $ETC_WORK
/bin/mount -t overlay overlay-etc -o lowerdir=$ETC_LOWER,upperdir=$ETC_UPPER,workdir=$ETC_WORK /etc
# import zpool (this will also mount etc & home)
/bin/echo "[init] import zpool"
/sbin/zpool import nimux-zfs
# update etc dir with persitent data
export ETC_PERSIST=$NIMUX_OVERLAY/etc/persist
/bin/echo "[init] update /etc with persistant data"
/bin/echo "[init] /etc persist dir: " $ETC_PERSIST
/bin/cp -R $ETC_PERSIST/* /etc
# hand over to systemd
/bin/echo "[init] handover control to systemd"
exec /usr/lib/systemd/systemd
# we shouldn´t get to here
echo "[init] ERROR: switch_root failed!" >&2
export PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\] (init-rescue)# "
exec sh