Compare commits
10 Commits
a87facd376
...
a0a31eae62
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0a31eae62 | ||
|
|
e8b2a52571 | ||
|
|
6a692cd666 | ||
|
|
923b03a34d | ||
|
|
81f0741809 | ||
|
|
f77ca7f659 | ||
|
|
7bfb18949b | ||
|
|
466ee9d188 | ||
|
|
1ab48bdc2d | ||
|
|
3489e7fba8 |
@@ -0,0 +1,4 @@
|
||||
# nimux/Config.in
|
||||
menu "Nimux packages"
|
||||
source "$BR2_EXTERNAL_NIMUX_PATH/package/python-xonsh/Config.in"
|
||||
endmenu
|
||||
|
||||
@@ -1,60 +1,47 @@
|
||||
BR2_x86_64=y
|
||||
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_12=y
|
||||
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_FORTIFY_SOURCE_2=y
|
||||
BR2_TARGET_GENERIC_HOSTNAME="nimux"
|
||||
BR2_TARGET_GENERIC_ISSUE="Welcome to Nimux!"
|
||||
BR2_TARGET_GENERIC_PASSWD_SHA512=y
|
||||
BR2_TARGET_ROOTFS_CPIO=y
|
||||
BR2_TARGET_ROOTFS_CPIO_GZIP=y
|
||||
BR2_TARGET_ENABLE_ROOT_LOGIN=y
|
||||
BR2_INIT_SYSTEMD=y
|
||||
BR2_INIT_SYSTEMD_VAR_NONE=y
|
||||
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"
|
||||
# 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_GENERATE_LOCALE="C en_US"
|
||||
BR2_TARGET_TZ_INFO=y
|
||||
BR2_TARGET_LOCALTIME="Europe/Amsterdam"
|
||||
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_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.27"
|
||||
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_XZ=y
|
||||
#BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=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_GZIP=y
|
||||
BR2_PACKAGE_SSHFS=y
|
||||
BR2_PACKAGE_ZFS=y
|
||||
BR2_PACKAGE_ACPID=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
BR2_PACKAGE_HDPARM=y
|
||||
BR2_PACKAGE_HWDATA=y
|
||||
BR2_PACKAGE_HWDATA_IAB_OUI_TXT=y
|
||||
BR2_PACKAGE_PYTHON3=y
|
||||
BR2_PACKAGE_PYTHON_SETUPTOOLS=y
|
||||
BR2_PACKAGE_CA_CERTIFICATES=y
|
||||
BR2_PACKAGE_LIBCURL=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_LIBCAP=y
|
||||
BR2_PACKAGE_LIBGLIB2=y
|
||||
BR2_PACKAGE_BRIDGE_UTILS=y
|
||||
BR2_PACKAGE_IFTOP=y
|
||||
BR2_PACKAGE_NTP=y
|
||||
BR2_PACKAGE_NANO=y
|
||||
BR2_PACKAGE_NANO_TINY=n
|
||||
BR2_PACKAGE_BASH=y
|
||||
BR2_PACKAGE_OPENSSH=y
|
||||
BR2_PACKAGE_BASH_COMPLETION=y
|
||||
BR2_PACKAGE_INOTIFY_TOOLS=y
|
||||
BR2_PACKAGE_SUDO=y
|
||||
@@ -62,12 +49,11 @@ BR2_PACKAGE_CPULOAD=y
|
||||
BR2_PACKAGE_DOCKER_CLI=y
|
||||
BR2_PACKAGE_DOCKER_COMPOSE=y
|
||||
BR2_PACKAGE_DOCKER_ENGINE=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_SYSTEM_DHCP="eth0"
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh"
|
||||
#BR2_ROOTFS_POST_IMAGE_SCRIPT="board/qemu/post-image.sh"
|
||||
BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
|
||||
# BR2_TARGET_ROOTFS_EXT2=y
|
||||
BR2_PACKAGE_SYSTEMD_ANALYZE=y
|
||||
BR2_PACKAGE_NANO=y
|
||||
# BR2_PACKAGE_NANO_TINY is not set
|
||||
BR2_TARGET_ROOTFS_SQUASHFS=y
|
||||
# BR2_TARGET_ROOTFS_TAR is not set
|
||||
BR2_PACKAGE_HOST_QEMU=y
|
||||
BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y
|
||||
BR2_PACKAGE_PYTHON_XONSH=y
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
name: NIMUX
|
||||
desc: The custom buildroot overlay for nimux
|
||||
desc: Nimux is the imutable lightweigth HomeLab OS with docker in its core
|
||||
|
||||
@@ -5,6 +5,6 @@ tmpfs /dev/shm tmpfs nosuid,nodev,mode=0777
|
||||
tmpfs /tmp tmpfs nosuid,nodev,mode=1777 0 0
|
||||
tmpfs /run 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
|
||||
cgroup2 /sys/fs/cgroup cgroup2 defaults,nosuid,nodev,noexec 0 0
|
||||
|
||||
11
rootfs-overlay/etc/systemd/network/10-dhcp-all.network
Normal file
11
rootfs-overlay/etc/systemd/network/10-dhcp-all.network
Normal 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
|
||||
3
rootfs-overlay/etc/systemd/resolved.conf
Normal file
3
rootfs-overlay/etc/systemd/resolved.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
[Resolve]
|
||||
DNSSEC=no
|
||||
DNSOverTLS=no
|
||||
23
rootfs-overlay/usr/lib/nimux/nimux-early-mount.xsh
Executable file
23
rootfs-overlay/usr/lib/nimux/nimux-early-mount.xsh
Executable 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
|
||||
|
||||
383
rootfs-overlay/usr/lib/nimux/nimux-firstlogin
Normal file
383
rootfs-overlay/usr/lib/nimux/nimux-firstlogin
Normal 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
|
||||
@@ -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
61
rootfs-overlay/usr/sbin/init
Executable 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
|
||||
Reference in New Issue
Block a user