diff --git a/airootfs/etc/gtk-3.0/settings.ini b/airootfs/etc/gtk-3.0/settings.ini index 9316b99b..a9f0b9b9 100644 --- a/airootfs/etc/gtk-3.0/settings.ini +++ b/airootfs/etc/gtk-3.0/settings.ini @@ -1,7 +1,7 @@ [Settings] -gtk-theme-name=adw-gtk-theme-dark +gtk-theme-name=adw-gtk3-dark gtk-icon-theme-name=Papirus -gtk-font-name=FreeSans 11 +gtk-font-name=Hack Nerd Font 10 gtk-cursor-theme-name=Adwaita gtk-cursor-theme-size=16 gtk-application-prefer-dark-theme=true diff --git a/airootfs/etc/skel/.config/foot/foot.ini b/airootfs/etc/skel/.config/foot/foot.ini index 8b013bb5..563f499e 100644 --- a/airootfs/etc/skel/.config/foot/foot.ini +++ b/airootfs/etc/skel/.config/foot/foot.ini @@ -27,7 +27,7 @@ include=/tmp/foot-matugen-colors.ini # selection-background=4C566A [main] -font=JetBrainsMono Nerd Font:size=8.5 +font=Hack Nerd Font:size=8.5 dpi-aware=yes pad=14x12 diff --git a/airootfs/etc/skel/.config/gtk-3.0/settings.ini b/airootfs/etc/skel/.config/gtk-3.0/settings.ini index a6deb294..a9f0b9b9 100644 --- a/airootfs/etc/skel/.config/gtk-3.0/settings.ini +++ b/airootfs/etc/skel/.config/gtk-3.0/settings.ini @@ -1,7 +1,7 @@ [Settings] gtk-theme-name=adw-gtk3-dark gtk-icon-theme-name=Papirus -gtk-font-name=FreeSans 11 +gtk-font-name=Hack Nerd Font 10 gtk-cursor-theme-name=Adwaita gtk-cursor-theme-size=16 gtk-application-prefer-dark-theme=true diff --git a/airootfs/etc/skel/.config/gtk-4.0/settings.ini b/airootfs/etc/skel/.config/gtk-4.0/settings.ini index a6deb294..a9f0b9b9 100644 --- a/airootfs/etc/skel/.config/gtk-4.0/settings.ini +++ b/airootfs/etc/skel/.config/gtk-4.0/settings.ini @@ -1,7 +1,7 @@ [Settings] gtk-theme-name=adw-gtk3-dark gtk-icon-theme-name=Papirus -gtk-font-name=FreeSans 11 +gtk-font-name=Hack Nerd Font 10 gtk-cursor-theme-name=Adwaita gtk-cursor-theme-size=16 gtk-application-prefer-dark-theme=true diff --git a/airootfs/etc/skel/.config/hypr/hyprland.conf b/airootfs/etc/skel/.config/hypr/hyprland.conf index c85e3f89..8f85734b 100644 --- a/airootfs/etc/skel/.config/hypr/hyprland.conf +++ b/airootfs/etc/skel/.config/hypr/hyprland.conf @@ -29,7 +29,7 @@ exec-once = gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' exec-once = gsettings set org.gnome.desktop.interface text-scaling-factor 0.75 exec-once = bash -c 'CURSOR=$(grep -m1 "^gtk-cursor-theme-name=" ~/.config/gtk-3.0/settings.ini 2>/dev/null | cut -d= -f2 || echo Adwaita); gsettings set org.gnome.desktop.interface cursor-theme "$CURSOR"' exec-once = bash -c 'CSIZE=$(grep -m1 "^gtk-cursor-theme-size=" ~/.config/gtk-3.0/settings.ini 2>/dev/null | cut -d= -f2 || echo 16); gsettings set org.gnome.desktop.interface cursor-size "$CSIZE"' -exec-once = bash -c 'FONT=$(grep -m1 "^gtk-font-name=" ~/.config/gtk-3.0/settings.ini 2>/dev/null | cut -d= -f2 || echo "JetBrainsMono Nerd Font 10"); gsettings set org.gnome.desktop.interface font-name "$FONT"' +exec-once = bash -c 'FONT=$(grep -m1 "^gtk-font-name=" ~/.config/gtk-3.0/settings.ini 2>/dev/null | cut -d= -f2 || echo "Hack Nerd Font 10"); gsettings set org.gnome.desktop.interface font-name "$FONT"' exec-once = mkdir -p ~/Pictures/Screenshots # Auto-launch installer in live environment (checks archiso/Ventoy conditions) exec-once = sudo /usr/local/bin/mados-installer-autostart & diff --git a/airootfs/etc/skel/.config/polybar/config.ini b/airootfs/etc/skel/.config/polybar/config.ini index 46d3ac7f..bba7b2a3 100644 --- a/airootfs/etc/skel/.config/polybar/config.ini +++ b/airootfs/etc/skel/.config/polybar/config.ini @@ -31,8 +31,8 @@ module-margin-left = 2 module-margin-right = 2 font-0 = "Michroma:size=8;2" -font-1 = "Iosevka Nerd Font:size=8;2" -font-2 = "JetBrainsMono Nerd Font:size=8;2" +font-1 = "Hack Nerd Font:size=8;2" +font-2 = "Hack Nerd Font:size=8;2" font-3 = "Noto Color Emoji:size=8;2" font-4 = "Symbols Nerd Font:size=8;2" diff --git a/airootfs/etc/skel/.config/waybar/style.css b/airootfs/etc/skel/.config/waybar/style.css index 06c54ffa..4a539f5c 100644 --- a/airootfs/etc/skel/.config/waybar/style.css +++ b/airootfs/etc/skel/.config/waybar/style.css @@ -11,7 +11,7 @@ window#waybar { background: linear-gradient(90deg, rgba(46, 52, 64, 0.75) 0%, rgba(59, 66, 82, 0.75) 50%, rgba(46, 52, 64, 0.75) 100%); color: #ECEFF4; - font-family: "Michroma", "JetBrainsMono Nerd Font", monospace; + font-family: "Michroma", "Hack Nerd Font", monospace; font-size: 8px; font-weight: 500; } diff --git a/airootfs/etc/skel/.config/wofi/style.css b/airootfs/etc/skel/.config/wofi/style.css index 1fb0599d..ad592119 100644 --- a/airootfs/etc/skel/.config/wofi/style.css +++ b/airootfs/etc/skel/.config/wofi/style.css @@ -1,7 +1,7 @@ /* Nord LED Theme for Wofi */ * { - font-family: "JetBrainsMono Nerd Font", monospace; + font-family: "Hack Nerd Font", monospace; font-size: 11px; } diff --git a/airootfs/root/customize_airootfs.d/02-themes.sh b/airootfs/root/customize_airootfs.d/02-themes.sh index df69572e..35b247e8 100644 --- a/airootfs/root/customize_airootfs.d/02-themes.sh +++ b/airootfs/root/customize_airootfs.d/02-themes.sh @@ -3,11 +3,27 @@ # Atomic module for theme installation set -euo pipefail -install_nordic_theme() { - return 0 -} +apply_default_gtk_theme() { + local settings_file="/etc/gtk-3.0/settings.ini" + + mkdir -p /etc/gtk-3.0 + if [[ -f "$settings_file" ]]; then + sed -i 's/^gtk-theme-name=.*/gtk-theme-name=adw-gtk3-dark/' "$settings_file" + sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=Papirus/' "$settings_file" + else + cat > "$settings_file" << 'EOF' +[Settings] +gtk-theme-name=adw-gtk3-dark +gtk-icon-theme-name=Papirus +gtk-font-name=Hack Nerd Font 10 +gtk-cursor-theme-name=Adwaita +gtk-cursor-theme-size=16 +gtk-application-prefer-dark-theme=true +gtk-decoration-layout=:minimize,maximize,close +EOF + fi -install_nordzy_icons() { + echo "✓ Default GTK theme set to adw-gtk3-dark (Papirus icons)" return 0 } @@ -64,9 +80,7 @@ install_logos_and_splashes() { } install_themes() { - install_nordic_theme - install_nordzy_icons - + apply_default_gtk_theme install_michroma_font install_logos_and_splashes fc-cache -f /usr/share/fonts/truetype/ 2>/dev/null || true diff --git a/airootfs/root/customize_airootfs.d/03-apps.sh b/airootfs/root/customize_airootfs.d/03-apps.sh index 9485bd29..c9eb2a4b 100644 --- a/airootfs/root/customize_airootfs.d/03-apps.sh +++ b/airootfs/root/customize_airootfs.d/03-apps.sh @@ -13,10 +13,6 @@ MADOS_APPS=( "mados-wallpaper" ) -NUCLEAR_GITHUB_REPO="madoslinux" -NUCLEAR_INSTALL_DIR="/opt/nuclear" -NUCLEAR_BIN="/usr/local/bin/nuclear" - GITHUB_REPO="madoslinux" INSTALLER_APP="mados-installer" INSTALLER_GITHUB_REPO="madoslinux" @@ -67,6 +63,256 @@ clone_latest_tag() { return 0 } +strip_vcs_metadata() { + local target_dir="$1" + + [[ -d "$target_dir" ]] || return 0 + + rm -rf \ + "$target_dir/.git" \ + "$target_dir/.github" \ + "$target_dir/.gitignore" \ + "$target_dir/.gitattributes" \ + "$target_dir/.gitmodules" +} + +assert_installer_contract() { + local install_path="$1" + + local required_files=( + "${install_path}/__main__.py" + "${install_path}/installer/steps.py" + "${install_path}/scripts/configure-grub.sh" + "${install_path}/scripts/setup-bootloader.sh" + "${install_path}/scripts/apply-configuration.sh" + "${install_path}/scripts/enable-services.sh" + ) + + local f + for f in "${required_files[@]}"; do + if [[ ! -f "$f" ]]; then + echo "ERROR: Installer contract missing required file: $f" + return 1 + fi + done + + if grep -q 'ensure_btrfs_rootflags()' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh still defines ensure_btrfs_rootflags (duplicates rootflags)" + return 1 + fi + + if ! grep -q 'Drop malformed bare subvol= tokens' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh missing bare subvol token sanitizer" + return 1 + fi + + if grep -q 'ensure_cmdline_token "subvol=' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh still injects bare subvol= kernel args" + return 1 + fi + + if grep -q '^ensure_btrfs_rootflags$' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh still calls ensure_btrfs_rootflags (duplicates rootflags)" + return 1 + fi + + if grep -q '^ensure_cmdline_token "splash"$' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh still injects splash in GRUB_CMDLINE_LINUX" + return 1 + fi + + if grep -q '^ensure_cmdline_token "quiet"$' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh still injects quiet in GRUB_CMDLINE_LINUX" + return 1 + fi + + if ! grep -q 'sanitize_grub_cmdline_key "GRUB_CMDLINE_LINUX"' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh missing GRUB_CMDLINE_LINUX sanitizer call" + return 1 + fi + + if ! grep -q 'sanitize_grub_cmdline_key "GRUB_CMDLINE_LINUX_DEFAULT"' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh missing GRUB_CMDLINE_LINUX_DEFAULT sanitizer call" + return 1 + fi + + if ! grep -q 'sanitize_generated_grub_cfg()' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh missing grub.cfg sanitizer" + return 1 + fi + + if ! grep -q 'grub.cfg still contains invalid rootflag= token' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh missing grub.cfg rootflag assertion" + return 1 + fi + + if ! grep -q 'grub.cfg still contains invalid bare subvol= token' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh missing grub.cfg bare subvol assertion" + return 1 + fi + + if grep -q 'ensure_cmdline_token "rootflag=' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh still injects legacy rootflag= token" + return 1 + fi + + if grep -q 'rootflags=subvol=@' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh still forces rootflags=subvol=@" + return 1 + fi + + if ! grep -q 'retrying without ACL/xattr' "${install_path}/installer/steps.py"; then + echo "ERROR: Installer contract check failed: steps.py missing rsync metadata fallback" + return 1 + fi + + if grep -q 'wifi.backend=iwd' "${install_path}/scripts/apply-configuration.sh"; then + echo "ERROR: Installer contract check failed: apply-configuration.sh still forces iwd backend" + return 1 + fi + + if grep -q 'enable_service iwd' "${install_path}/scripts/enable-services.sh"; then + echo "ERROR: Installer contract check failed: enable-services.sh still enables iwd" + return 1 + fi + + if grep -q 'Current=sddm-astron_theme' "${install_path}/scripts/apply-configuration.sh"; then + echo "ERROR: Installer contract check failed: apply-configuration.sh still sets astron SDDM theme" + return 1 + fi + + if ! grep -q 'autologin-live.conf' "${install_path}/scripts/apply-configuration.sh"; then + echo "ERROR: Installer contract check failed: apply-configuration.sh missing SDDM autologin cleanup" + return 1 + fi + + if ! grep -q 'Current=pixel-night-city' "${install_path}/scripts/apply-configuration.sh"; then + echo "ERROR: Installer contract check failed: apply-configuration.sh missing SDDM theme pin" + return 1 + fi + + if grep -q 'systemctl enable getty@tty2.service' "${install_path}/scripts/apply-configuration.sh"; then + echo "ERROR: Installer contract check failed: apply-configuration.sh still enables getty@tty2" + return 1 + fi + + if grep -q 'systemctl enable getty@tty1.service' "${install_path}/scripts/apply-configuration.sh"; then + echo "ERROR: Installer contract check failed: apply-configuration.sh still enables getty@tty1 fallback" + return 1 + fi + + if ! grep -q '"linux-lts"' "${install_path}/installer/steps.py"; then + echo "ERROR: Installer contract check failed: steps.py missing linux-lts kernel fallback" + return 1 + fi + + if ! grep -q 'supported kernel not found' "${install_path}/installer/steps.py"; then + echo "ERROR: Installer contract check failed: steps.py missing generic supported-kernel error path" + return 1 + fi + + if ! grep -q 'for candidate in linux-lts linux-mados linux linux-zen' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Installer contract check failed: configure-grub.sh missing kernel fallback candidate detection" + return 1 + fi + + if ! grep -q 'for candidate in linux-lts linux-mados linux linux-zen' "${install_path}/scripts/rebuild-initramfs.sh"; then + echo "ERROR: Installer contract check failed: rebuild-initramfs.sh missing kernel fallback candidate detection" + return 1 + fi + + if ! grep -q '/boot/vmlinuz-linux-lts' "${install_path}/scripts/setup-bootloader.sh"; then + echo "ERROR: Installer contract check failed: setup-bootloader.sh missing linux-lts signing/validation fallback" + return 1 + fi + + if ! grep -q 'KERNEL_NAME=""' "${install_path}/scripts/configure-limine.sh"; then + echo "ERROR: Installer contract check failed: configure-limine.sh missing dynamic kernel detection" + return 1 + fi + + return 0 +} + +clone_and_install_app() { + local repo="$1" + local app_name="$2" + local module_name="${app_name//-/_}" + local install_path="${INSTALL_DIR}/${module_name}" + local bin_path="${BIN_DIR}/${app_name}" + + echo "Installing ${app_name}..." + + local build_dir="${BUILD_DIR}/${module_name}_$$" + rm -rf "$build_dir" + mkdir -p "$build_dir" + cd "$BUILD_DIR" + + local retries=3 + local count=0 + while [ $count -lt $retries ]; do + if clone_latest_main "https://github.com/${repo}.git" "${build_dir}/${module_name}"; then + break + fi + count=$((count + 1)) + echo " Retry $count/$retries..." + sleep 2 + done + + if [ $count -eq $retries ]; then + echo "ERROR: Failed to clone ${repo} after $retries attempts" + rm -rf "$build_dir" + return 1 + fi + + mkdir -p "$INSTALL_DIR" + rm -rf "$install_path" + mv "${build_dir}/${module_name}" "$install_path" + strip_vcs_metadata "$install_path" + rm -rf "$build_dir" + + # Keep mados-wallpaper as assets-only package (no executable wrappers) + if [[ "$app_name" != "mados-wallpaper" ]]; then + # Use original bash wrapper if it exists and is actually a bash script (for complex apps like installer) + if [[ -f "${install_path}/${app_name}" && "$app_name" == "mados-installer" ]]; then + # Skip - installer has special handling below + : + else + # Create wrapper script for all apps + # mados-updater needs root privileges to manage /etc and system updates + if [[ "$app_name" == "mados-updater" ]]; then + cat > "$bin_path" << EOF +#!/bin/bash +if [[ "\$EUID" -ne 0 ]]; then + exec sudo -E "\$0" "\$@" +fi +export PYTHONPATH="${INSTALL_DIR}:\${PYTHONPATH:-}" +cd "${install_path}" +exec python3 -m "${module_name}" "\$@" +EOF + else + # cd to install_path so python3 -m can find the module + cat > "$bin_path" << EOF +#!/bin/bash +export PYTHONPATH="${INSTALL_DIR}:\${PYTHONPATH:-}" +cd "${install_path}" +exec python3 -m "${module_name}" "\$@" +EOF + fi + chmod +x "$bin_path" + fi + fi + + # Copy desktop file if exists (except assets-only wallpaper package) + if [[ "$app_name" != "mados-wallpaper" && -f "${install_path}/${app_name}.desktop" ]]; then + cp "${install_path}/${app_name}.desktop" /usr/share/applications/ + echo " → Installed desktop file" + fi + + echo "✓ ${app_name} installed to ${install_path}" + return 0 +} + install_mados_apps() { for app in "${MADOS_APPS[@]}"; do clone_and_install_app "${GITHUB_REPO}/${app}" "$app" @@ -107,6 +353,7 @@ install_installer() { mkdir -p "$INSTALL_DIR" rm -rf "$install_path" mv "${build_dir}/${installer_module}" "$install_path" + strip_vcs_metadata "$install_path" rm -rf "$build_dir" # Keep installed-system Plymouth logo size identical to live ISO theme. @@ -168,7 +415,6 @@ fi rm -f /etc/sddm.conf.d/autologin-live.conf # Do not write an empty [Autologin] block; defaults already keep autologin disabled. rm -f /etc/sddm.conf.d/90-disable-autologin.conf -systemctl enable mados-sddm-env.service EOSDDM fi echo " → Removed installer iwd backend override line" @@ -371,6 +617,428 @@ PY echo " → Hardened installer rsync for VFAT /boot metadata limitations" fi + if [[ -f "${install_path}/installer/steps.py" ]]; then + python3 - "${install_path}/installer/steps.py" <<'PY' +from pathlib import Path +import re +import sys + +p = Path(sys.argv[1]) +t = p.read_text(encoding="utf-8") + +if "supported kernel not found" not in t: + new_func = '''def _ensure_kernel_in_target(app): + """Ensure a supported kernel image exists in target /boot before entering the chroot.""" + os.makedirs("/mnt/boot", exist_ok=True) + + preferred_kernels = [ + "linux-lts", + "linux-mados", + "linux", + "linux-zen", + ] + + def _target_path(kernel_name): + return f"/mnt/boot/vmlinuz-{kernel_name}" + + def _copy_kernel(src, kernel_name): + target = _target_path(kernel_name) + subprocess.run(["cp", src, target], check=True) + log_message(app, f" Copied kernel from {src} -> {target}") + + def _detect_kernel_name_from_module_path(path): + mod_ver = os.path.basename(os.path.dirname(path)).lower() + if "lts" in mod_ver: + return "linux-lts" + if "mados" in mod_ver: + return "linux-mados" + if "zen" in mod_ver: + return "linux-zen" + return "linux" + + log_message(app, " DEBUG: Checking archiso bootmnt kernel paths:") + for f in sorted(globmod.glob("/run/archiso/bootmnt/arch/boot/x86_64/vmlinuz*")): + log_message(app, f" {f}") + log_message(app, " DEBUG: Checking live system /boot:") + for f in sorted(globmod.glob("/boot/vmlinuz*")): + log_message(app, f" {f}") + log_message(app, " DEBUG: Checking live system /lib/modules:") + for d in sorted(globmod.glob("/lib/modules/*")): + log_message(app, f" {d}") + log_message(app, " DEBUG: Checking /usr/lib/modules/*/vmlinuz:") + for f in sorted(globmod.glob("/usr/lib/modules/*/vmlinuz")): + log_message(app, f" {f}") + log_message(app, " DEBUG: Checking /lib/modules/*/vmlinuz:") + for f in sorted(globmod.glob("/lib/modules/*/vmlinuz")): + log_message(app, f" {f}") + + for kernel_name in preferred_kernels: + target = _target_path(kernel_name) + if os.path.isfile(target) and os.access(target, os.R_OK) and os.path.getsize(target) > 0: + log_message(app, f" Kernel already exists in target /boot: {target}") + return + + log_message(app, " Kernel not found in target /boot, copying from live system...") + + for kernel_name in preferred_kernels: + for src in ( + f"/run/archiso/bootmnt/arch/boot/x86_64/vmlinuz-{kernel_name}", + f"/boot/vmlinuz-{kernel_name}", + ): + if os.path.isfile(src) and os.access(src, os.R_OK): + _copy_kernel(src, kernel_name) + return + + for search_glob in ( + "/usr/lib/modules/*/vmlinuz", + "/lib/modules/*/vmlinuz", + "/mnt/usr/lib/modules/*/vmlinuz", + ): + for vmlinuz in sorted(globmod.glob(search_glob), reverse=True): + if os.path.isfile(vmlinuz) and os.access(vmlinuz, os.R_OK): + kernel_name = _detect_kernel_name_from_module_path(vmlinuz) + _copy_kernel(vmlinuz, kernel_name) + return + + log_message( + app, + " ERROR: Could not find supported kernel (linux-lts/linux-mados/linux/linux-zen) in live system", + ) + raise RuntimeError("supported kernel not found") +''' + + pattern = re.compile(r"def _ensure_kernel_in_target\(app\):.*?\n\ndef step_partition_disk", re.S) + if pattern.search(t): + t = pattern.sub(new_func + "\n\ndef step_partition_disk", t, count=1) + p.write_text(t, encoding="utf-8") +PY + if ! grep -q 'supported kernel not found' "${install_path}/installer/steps.py"; then + echo "ERROR: Failed to add installer kernel fallback support" + return 1 + fi + echo " → Added linux-lts fallback to installer kernel copy step" + fi + + if [[ -f "${install_path}/scripts/configure-grub.sh" ]]; then + python3 - "${install_path}/scripts/configure-grub.sh" <<'PY' +from pathlib import Path +import sys + +p = Path(sys.argv[1]) +t = p.read_text(encoding="utf-8") + +old = '''KERNEL="linux-mados" + +if [ ! -f /boot/vmlinuz-${KERNEL} ]; then + echo "ERROR: No madOS kernel found in /boot" + exit 1 +fi + +mkdir -p /etc/default +[ -f /etc/default/grub ] || touch /etc/default/grub + +mkdir -p /etc/mados +echo "$KERNEL" > /etc/mados/default-kernel +echo " Selected default kernel: $KERNEL" +''' + +new = '''KERNEL="" +for candidate in linux-lts linux-mados linux linux-zen; do + if [ -f "/boot/vmlinuz-${candidate}" ]; then + KERNEL="${candidate}" + break + fi +done + +if [ -z "$KERNEL" ]; then + echo "ERROR: No supported kernel found in /boot" + exit 1 +fi + +mkdir -p /etc/default +[ -f /etc/default/grub ] || touch /etc/default/grub + +mkdir -p /etc/mados +echo "$KERNEL" > /etc/mados/default-kernel +echo " Selected default kernel: $KERNEL" +''' + +changed = False +if "for candidate in linux-lts linux-mados linux linux-zen" not in t and old in t: + t = t.replace(old, new, 1) + changed = True + +if 'if ! grep -q "vmlinuz-linux-mados" /boot/grub/grub.cfg; then' in t: + t = t.replace( + 'if ! grep -q "vmlinuz-linux-mados" /boot/grub/grub.cfg; then', + 'if ! grep -q "vmlinuz-${KERNEL}" /boot/grub/grub.cfg; then', + 1, + ) + changed = True + +if 'echo "ERROR: grub.cfg does not contain linux-mados entry"' in t: + t = t.replace( + 'echo "ERROR: grub.cfg does not contain linux-mados entry"', + 'echo "ERROR: grub.cfg does not contain vmlinuz-${KERNEL} entry"', + 1, + ) + changed = True + +if changed: + p.write_text(t, encoding="utf-8") +PY + if ! grep -q 'for candidate in linux-lts linux-mados linux linux-zen' "${install_path}/scripts/configure-grub.sh"; then + echo "ERROR: Failed to add kernel fallback detection to configure-grub.sh" + return 1 + fi + echo " → Added linux-lts fallback to installer GRUB configuration" + fi + + if [[ -f "${install_path}/scripts/rebuild-initramfs.sh" ]]; then + python3 - "${install_path}/scripts/rebuild-initramfs.sh" <<'PY' +from pathlib import Path +import sys + +p = Path(sys.argv[1]) +t = p.read_text(encoding="utf-8") + +old_kernel_select = '''KERNEL="linux-mados" +if [ ! -s /boot/vmlinuz-${KERNEL} ] || [ ! -r /boot/vmlinuz-${KERNEL} ]; then + echo " ERROR: Could not find kernel image. Reinstalling ${KERNEL} package..." + $PACMAN -Sy --noconfirm ${KERNEL} || { echo "FATAL: Failed to install kernel"; exit 1; } +fi +''' + +new_kernel_select = '''KERNEL="" +for candidate in linux-lts linux-mados linux linux-zen; do + if [ -s "/boot/vmlinuz-${candidate}" ] && [ -r "/boot/vmlinuz-${candidate}" ]; then + KERNEL="${candidate}" + break + fi +done + +if [ -z "$KERNEL" ]; then + KERNEL="linux-lts" + echo " WARNING: Could not find kernel image in /boot. Installing ${KERNEL} package..." + $PACMAN -Sy --noconfirm ${KERNEL} || { echo "FATAL: Failed to install kernel"; exit 1; } +fi + +if [ ! -s "/boot/vmlinuz-${KERNEL}" ] || [ ! -r "/boot/vmlinuz-${KERNEL}" ]; then + echo " ERROR: Could not find readable kernel image: /boot/vmlinuz-${KERNEL}" + exit 1 +fi +''' + +old_modules_detect = '''TARGET_KVER="" +for kver in /lib/modules/*/; do + kver_name=$($BASENAME "$kver") + if [[ "$kver_name" == *"mados"* ]]; then + TARGET_KVER="$kver_name" + echo " Found target kernel: $TARGET_KVER" + break + fi +done + +if [ -z "$TARGET_KVER" ]; then + echo " ERROR: No madOS kernel modules found in /lib/modules" + echo " Available kernels:" + $LS /lib/modules/ 2>/dev/null || echo " (none)" + exit 1 +fi +''' + +new_modules_detect = '''TARGET_KVER="" +for kver in /lib/modules/*/; do + kver_name=$($BASENAME "$kver") + case "$KERNEL" in + linux-lts) + [[ "$kver_name" == *"lts"* ]] || continue + ;; + linux-mados) + [[ "$kver_name" == *"mados"* ]] || continue + ;; + linux-zen) + [[ "$kver_name" == *"zen"* ]] || continue + ;; + linux) + [[ "$kver_name" == *"arch"* || "$kver_name" == *"linux"* ]] || continue + ;; + esac + TARGET_KVER="$kver_name" + echo " Found target kernel: $TARGET_KVER" + break +done + +if [ -z "$TARGET_KVER" ]; then + echo " WARNING: No matching kernel modules found for ${KERNEL}; using first available module tree" + for kver in /lib/modules/*/; do + TARGET_KVER=$($BASENAME "$kver") + break + done +fi + +if [ -z "$TARGET_KVER" ]; then + echo " ERROR: No kernel modules found in /lib/modules" + echo " Available kernels:" + $LS /lib/modules/ 2>/dev/null || echo " (none)" + exit 1 +fi +''' + +changed = False +if "for candidate in linux-lts linux-mados linux linux-zen" not in t and old_kernel_select in t: + t = t.replace(old_kernel_select, new_kernel_select, 1) + changed = True + +if old_modules_detect in t: + t = t.replace(old_modules_detect, new_modules_detect, 1) + changed = True + +if changed: + p.write_text(t, encoding="utf-8") +PY + if ! grep -q 'for candidate in linux-lts linux-mados linux linux-zen' "${install_path}/scripts/rebuild-initramfs.sh"; then + echo "ERROR: Failed to add kernel fallback detection to rebuild-initramfs.sh" + return 1 + fi + echo " → Added linux-lts fallback to installer initramfs rebuild" + fi + + if [[ -f "${install_path}/scripts/setup-bootloader.sh" ]]; then + python3 - "${install_path}/scripts/setup-bootloader.sh" <<'PY' +from pathlib import Path +import re +import sys + +p = Path(sys.argv[1]) +t = p.read_text(encoding="utf-8") + +old_sign_loop = ''' for path in \ + /boot/EFI/BOOT/BOOTX64.EFI \ + /boot/EFI/BOOT/grubx64.efi \ + /boot/EFI/madOS/grubx64.efi \ + /boot/vmlinuz-linux-mados; do + if [ -f "$path" ]; then + artifacts+=("$path") + fi + done +''' + +new_sign_loop = ''' for path in \ + /boot/EFI/BOOT/BOOTX64.EFI \ + /boot/EFI/BOOT/grubx64.efi \ + /boot/EFI/madOS/grubx64.efi; do + if [ -f "$path" ]; then + artifacts+=("$path") + fi + done + + for path in \ + /boot/vmlinuz-linux-lts \ + /boot/vmlinuz-linux-mados \ + /boot/vmlinuz-linux \ + /boot/vmlinuz-linux-zen; do + if [ -f "$path" ]; then + artifacts+=("$path") + fi + done +''' + +new_validate_fn = '''validate_boot_artifacts() { + local required_paths=( + "/boot/EFI/BOOT/BOOTX64.EFI" + "/boot/EFI/madOS/grubx64.efi" + ) + + local path + for path in "${required_paths[@]}"; do + if [ ! -s "$path" ]; then + echo "ERROR: Required boot artifact missing: $path" + exit 1 + fi + done + + local kernel_found=0 + local kernel_path + for kernel_path in \ + /boot/vmlinuz-linux-lts \ + /boot/vmlinuz-linux-mados \ + /boot/vmlinuz-linux \ + /boot/vmlinuz-linux-zen; do + if [ -s "$kernel_path" ]; then + kernel_found=1 + break + fi + done + + if [ "$kernel_found" -ne 1 ]; then + echo "ERROR: Required kernel artifact missing: expected one of /boot/vmlinuz-linux-lts|linux-mados|linux|linux-zen" + exit 1 + fi +} +''' + +changed = False +if old_sign_loop in t: + t = t.replace(old_sign_loop, new_sign_loop, 1) + changed = True + +validate_pattern = re.compile(r"validate_boot_artifacts\(\) \{.*?\n\}\n\nrequire_cmd", re.S) +if validate_pattern.search(t): + t = validate_pattern.sub(new_validate_fn + "\nrequire_cmd", t, count=1) + changed = True + +if changed: + p.write_text(t, encoding="utf-8") +PY + if ! grep -q '/boot/vmlinuz-linux-lts' "${install_path}/scripts/setup-bootloader.sh"; then + echo "ERROR: Failed to add linux-lts boot artifact fallback to setup-bootloader.sh" + return 1 + fi + echo " → Added linux-lts fallback to installer bootloader setup" + fi + + if [[ -f "${install_path}/scripts/configure-limine.sh" ]]; then + python3 - "${install_path}/scripts/configure-limine.sh" <<'PY' +from pathlib import Path +import sys + +p = Path(sys.argv[1]) +t = p.read_text(encoding="utf-8") + +if 'KERNEL_NAME=""' not in t: + marker = "mkdir -p /boot/EFI/BOOT\n\n" + insert = '''KERNEL_NAME="" +for candidate in linux-lts linux-mados linux linux-zen; do + if [ -f "/boot/vmlinuz-${candidate}" ] && [ -f "/boot/initramfs-${candidate}.img" ]; then + KERNEL_NAME="${candidate}" + break + fi +done + +if [ -z "$KERNEL_NAME" ]; then + echo "ERROR: No supported kernel/initramfs pair found in /boot" + exit 1 +fi + +''' + if marker in t: + t = t.replace(marker, marker + insert, 1) + + t = t.replace("path: boot():/vmlinuz-linux-mados-zen", "path: boot():/vmlinuz-${KERNEL_NAME}") + t = t.replace( + "module_path: boot():/initramfs-linux-mados-zen.img", + "module_path: boot():/initramfs-${KERNEL_NAME}.img", + ) + p.write_text(t, encoding="utf-8") +PY + if ! grep -q 'KERNEL_NAME=""' "${install_path}/scripts/configure-limine.sh"; then + echo "ERROR: Failed to add dynamic kernel detection to configure-limine.sh" + return 1 + fi + echo " → Added linux-lts fallback to installer Limine configuration" + fi + if [[ -f "${install_path}/scripts/enable-services.sh" ]]; then sed -i '/enable_service iwd/d' "${install_path}/scripts/enable-services.sh" if ! grep -q 'enable_service sshd' "${install_path}/scripts/enable-services.sh"; then @@ -474,6 +1142,7 @@ install_oh_my_zsh() { fi mv "${build_dir}/ohmyzsh" "$omz_dir" + strip_vcs_metadata "$omz_dir" rm -rf "$build_dir" if [[ -d /home/mados ]]; then @@ -498,8 +1167,7 @@ SKWD_WALL_INSTALL_DIR="/usr/local/share/skwd-wall" SKWD_WALL_COMPAT_DIR="/opt/mados/skwd-wall" SKWD_WALL_BIN="/usr/local/bin/skwd-wall" -# Canonical skwd-wall installer -install_skwd_wall() { +install_skwd_wall_legacy() { echo "Installing skwd-wall..." local build_dir="${BUILD_DIR}/skwd-wall_$$" @@ -524,24 +1192,17 @@ install_skwd_wall() { return 1 fi - mkdir -p "$INSTALL_DIR" "/usr/local/share" "/opt/mados" /etc/skel/.config/skwd-wall /etc/skel/.config/systemd/user + mkdir -p "$INSTALL_DIR" "/usr/local/share" "/opt/mados" rm -rf "$SKWD_WALL_INSTALL_DIR" mv "${build_dir}/skwd-wall" "$SKWD_WALL_INSTALL_DIR" + strip_vcs_metadata "$SKWD_WALL_INSTALL_DIR" rm -rf "$SKWD_WALL_COMPAT_DIR" ln -s "$SKWD_WALL_INSTALL_DIR" "$SKWD_WALL_COMPAT_DIR" - if [[ ! -e "$SKWD_WALL_INSTALL_DIR/scripts" && -d "$SKWD_WALL_INSTALL_DIR/data/scripts" ]]; then - ln -s "$SKWD_WALL_INSTALL_DIR/data/scripts" "$SKWD_WALL_INSTALL_DIR/scripts" - fi - - if [[ -f "$SKWD_WALL_INSTALL_DIR/data/config.json.example" ]]; then - cp "$SKWD_WALL_INSTALL_DIR/data/config.json.example" /etc/skel/.config/skwd-wall/config.json - sed -i 's/"compositor":[[:space:]]*"[^"]*"/"compositor": "hyprland"/' /etc/skel/.config/skwd-wall/config.json - fi + mkdir -p /etc/skel/.config/skwd-wall /etc/skel/.config/systemd/user - if [[ ! -f /etc/skel/.config/systemd/user/skwd-wall.service ]]; then - cat > /etc/skel/.config/systemd/user/skwd-wall.service << 'SKWD_WALL_SERVICE_FALLBACK' + cat > /etc/skel/.config/systemd/user/skwd-wall.service << 'SKWD_WALL_SERVICE' [Unit] Description=skwd-wall wallpaper selector daemon Documentation=https://github.com/madkoding/skwd-wall @@ -556,126 +1217,443 @@ RestartSec=2 [Install] WantedBy=graphical-session.target -SKWD_WALL_SERVICE_FALLBACK +SKWD_WALL_SERVICE + + if [[ -f "$SKWD_WALL_INSTALL_DIR/data/config.json.example" ]]; then + cp "$SKWD_WALL_INSTALL_DIR/data/config.json.example" /etc/skel/.config/skwd-wall/config.json + fi + + if [[ -f /etc/skel/.config/skwd-wall/config.json ]]; then + sed -i 's/"compositor":[[:space:]]*"[^"]*"/"compositor": "hyprland"/' /etc/skel/.config/skwd-wall/config.json fi if [[ -d /home/mados ]]; then mkdir -p /home/mados/.config/skwd-wall /home/mados/.config/systemd/user + cp /etc/skel/.config/systemd/user/skwd-wall.service /home/mados/.config/systemd/user/skwd-wall.service + if [[ -f /etc/skel/.config/skwd-wall/config.json ]]; then cp /etc/skel/.config/skwd-wall/config.json /home/mados/.config/skwd-wall/config.json fi - if [[ -f /etc/skel/.config/systemd/user/skwd-wall.service ]]; then - cp /etc/skel/.config/systemd/user/skwd-wall.service /home/mados/.config/systemd/user/skwd-wall.service - fi + chown -R 1000:1000 /home/mados/.config/skwd-wall /home/mados/.config/systemd fi - for helper in /usr/local/bin/mados-wallpaper-picker /usr/local/bin/skwd-wall /usr/local/bin/mados-skwd-wall-daemon /usr/local/bin/mados-skwd-wall-sources /usr/local/bin/mados-skwd-wall-doctor; do - if [[ -f "$helper" ]]; then - chmod +x "$helper" - fi - done - rm -rf "$build_dir" - echo "✓ skwd-wall installed to ${SKWD_WALL_INSTALL_DIR}" - return 0 + cat > "$SKWD_WALL_BIN" << 'SKWD_WALL_WRAPPER' +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -eq 0 ]]; then + exec /usr/local/bin/mados-wallpaper-picker toggle +fi + +exec /usr/local/bin/mados-wallpaper-picker "$@" +SKWD_WALL_WRAPPER + chmod +x "$SKWD_WALL_BIN" + + cat > /usr/local/bin/mados-skwd-wall-sources << 'MADOS_SKWD_WALL_SOURCES' +#!/usr/bin/env python3 + +import argparse +import hashlib +import json +import os +import shutil +import sys +from pathlib import Path + +DEFAULT_SOURCES = [ + "~/.local/share/mados/wallpapers", + "~/Pictures/Wallpapers", + "/usr/share/backgrounds", + "/usr/share/wallpapers", + "/usr/share/mados/wallpapers", + "/opt/mados/mados_wallpaper", +] + +EXCLUDED_SOURCE_PATHS = [ + "/usr/share/backgrounds/sway", +] + +IMAGE_EXTENSIONS = { + ".jpg", + ".jpeg", + ".png", + ".webp", + ".gif", + ".bmp", + ".tif", + ".tiff", } -install_nuclear() { - echo "Installing Nuclear music player..." +VIDEO_EXTENSIONS = { + ".mp4", + ".mkv", + ".mov", + ".webm", + ".avi", +} - echo " → Fetching release info from GitHub API..." - local release_json - release_json=$(curl -fSL "https://api.github.com/repos/${NUCLEAR_GITHUB_REPO}/nuclear/releases/latest") || { - echo "ERROR: Failed to fetch releases API (curl failed with $?)" - return 1 - } - if [[ -z "$release_json" ]]; then - echo "ERROR: Empty response from GitHub API" - return 1 - fi +def home() -> Path: + return Path.home() - local release_tag appimage_url - release_tag=$(printf '%s' "$release_json" | python3 -c 'import sys, json; print(json.load(sys.stdin).get("tag_name", ""))') - appimage_url=$(printf '%s' "$release_json" | python3 -c ' -import sys, json -for a in json.load(sys.stdin).get("assets", []): - if a.get("name", "").lower().endswith(".appimage"): - print(a.get("browser_download_url", "")) - break -') - if [[ -z "$release_tag" || -z "$appimage_url" ]]; then - echo "ERROR: Could not resolve Nuclear release (tag='${release_tag}', url='${appimage_url}')" - return 1 +def config_file() -> Path: + xdg = os.environ.get("XDG_CONFIG_HOME", str(home() / ".config")) + return Path(xdg) / "skwd-wall" / "config.json" + + +def cache_root() -> Path: + xdg = os.environ.get("XDG_CACHE_HOME", str(home() / ".cache")) + return Path(xdg) / "skwd-wall" + + +def union_dir() -> Path: + return cache_root() / "wallpaper-union" + + +def normalize(path: str) -> str: + value = os.path.expandvars(path.strip()) + value = os.path.expanduser(value) + return str(Path(value).resolve()) + + +def ensure_config() -> dict: + path = config_file() + path.parent.mkdir(parents=True, exist_ok=True) + + if not path.is_file(): + install_dir = Path("/usr/local/share/skwd-wall") + example = install_dir / "data" / "config.json.example" + if example.is_file(): + data = json.loads(example.read_text()) + else: + data = {} + else: + try: + data = json.loads(path.read_text()) + except json.JSONDecodeError: + data = {} + + if not isinstance(data, dict): + data = {} + + paths = data.get("paths") + if not isinstance(paths, dict): + paths = {} + data["paths"] = paths + + sources = paths.get("wallpaperSources") + if not isinstance(sources, list): + sources = [] + + merged = [] + seen = set() + + legacy = paths.get("wallpaper") + if isinstance(legacy, str) and legacy.strip(): + legacy_norm = normalize(legacy) + if legacy_norm not in seen and not legacy_norm.endswith("/wallpaper-union"): + merged.append(legacy_norm) + seen.add(legacy_norm) + + for item in sources: + if isinstance(item, str) and item.strip(): + norm = normalize(item) + if norm not in seen: + merged.append(norm) + seen.add(norm) + + for item in DEFAULT_SOURCES: + norm = normalize(item) + if norm not in seen: + merged.append(norm) + seen.add(norm) + + paths["wallpaperSources"] = merged + paths["wallpaper"] = str(union_dir()) + if not isinstance(paths.get("videoWallpaper"), str) or not str(paths["videoWallpaper"]).strip(): + paths["videoWallpaper"] = str(union_dir()) + + data["compositor"] = "hyprland" + save_config(data) + return data + + +def save_config(data: dict) -> None: + path = config_file() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=4, ensure_ascii=False) + "\n") + + +def iter_files(source: Path): + excluded_paths = [Path(item) for item in EXCLUDED_SOURCE_PATHS] + for path in source.rglob("*"): + if path.is_file(): + if any(path.is_relative_to(excluded) for excluded in excluded_paths): + continue + ext = path.suffix.lower() + if ext in IMAGE_EXTENSIONS or ext in VIDEO_EXTENSIONS: + yield path + + +def build_union_from_sources(sources: list[str]) -> None: + target = union_dir() + parent = target.parent + temp = parent / (target.name + ".tmp") + old = parent / (target.name + ".old") + + shutil.rmtree(temp, ignore_errors=True) + temp.mkdir(parents=True, exist_ok=True) + + for src in sources: + source = Path(src) + if not source.is_dir(): + continue + + for item in iter_files(source): + digest = hashlib.sha1(str(item).encode("utf-8")).hexdigest()[:10] + stem = item.stem or "wallpaper" + safe = "".join(ch if ch.isalnum() or ch in "-_" else "_" for ch in stem)[:80] + name = f"{safe}__{digest}{item.suffix.lower()}" + link = temp / name + if not link.exists(): + try: + link.symlink_to(item) + except OSError: + pass + + shutil.rmtree(old, ignore_errors=True) + if target.exists() or target.is_symlink(): + target.rename(old) + temp.rename(target) + shutil.rmtree(old, ignore_errors=True) + + +def load_sources() -> list[str]: + data = ensure_config() + paths = data.get("paths", {}) + sources = paths.get("wallpaperSources", []) + return [s for s in sources if isinstance(s, str)] + + +def cmd_sync() -> int: + sources = load_sources() + build_union_from_sources(sources) + return 0 + + +def cmd_list() -> int: + for entry in load_sources(): + print(entry) + return 0 + + +def cmd_add(path: str) -> int: + data = ensure_config() + paths = data["paths"] + sources = [s for s in paths.get("wallpaperSources", []) if isinstance(s, str)] + norm = normalize(path) + if norm not in sources: + sources.append(norm) + paths["wallpaperSources"] = sources + save_config(data) + build_union_from_sources(sources) + return 0 + + +def cmd_remove(path: str) -> int: + data = ensure_config() + paths = data["paths"] + sources = [s for s in paths.get("wallpaperSources", []) if isinstance(s, str)] + norm = normalize(path) + sources = [s for s in sources if s != norm] + paths["wallpaperSources"] = sources + save_config(data) + build_union_from_sources(sources) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Manage skwd-wall wallpaper sources") + sub = parser.add_subparsers(dest="cmd") + + sub.add_parser("sync") + sub.add_parser("list") + add_p = sub.add_parser("add") + add_p.add_argument("path") + rm_p = sub.add_parser("remove") + rm_p.add_argument("path") + + args = parser.parse_args() + if args.cmd in {None, "sync"}: + return cmd_sync() + if args.cmd == "list": + return cmd_list() + if args.cmd == "add": + return cmd_add(args.path) + if args.cmd == "remove": + return cmd_remove(args.path) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +MADOS_SKWD_WALL_SOURCES + chmod +x /usr/local/bin/mados-skwd-wall-sources + + cat > /usr/local/bin/mados-skwd-wall-daemon << 'MADOS_SKWD_WALL_DAEMON' +#!/usr/bin/env bash +set -euo pipefail + +export SKWD_WALL_INSTALL="/usr/local/share/skwd-wall" +export SKWD_WALL_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/skwd-wall" + +/usr/local/bin/mados-skwd-wall-sources sync >/dev/null 2>&1 || true + +exec quickshell -p /usr/local/share/skwd-wall/daemon.qml +MADOS_SKWD_WALL_DAEMON + chmod +x /usr/local/bin/mados-skwd-wall-daemon + + cat > /usr/local/bin/mados-skwd-wall-doctor << 'MADOS_SKWD_WALL_DOCTOR' +#!/usr/bin/env bash +set -euo pipefail + +echo "== skwd-wall doctor ==" + +check_cmd() { + local cmd="$1" + if command -v "$cmd" >/dev/null 2>&1; then + echo "[ok] $cmd" + else + echo "[missing] $cmd" fi +} - echo " → Nuclear release: ${release_tag}" - echo " → AppImage: ${appimage_url}" +check_cmd quickshell +check_cmd awww +check_cmd matugen +check_cmd ffmpeg +check_cmd magick +check_cmd sqlite3 +check_cmd inotifywait + +if [[ -f /usr/local/share/skwd-wall/daemon.qml ]]; then + echo "[ok] daemon.qml" +else + echo "[missing] /usr/local/share/skwd-wall/daemon.qml" +fi - local appimage_path="${BUILD_DIR}/nuclear.AppImage" - local retries=5 - local count=0 +if [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/skwd-wall/config.json" ]]; then + echo "[ok] config.json" +else + echo "[missing] ~/.config/skwd-wall/config.json" +fi + +echo "-- systemd user --" +systemctl --user is-enabled skwd-wall.service 2>/dev/null || true +systemctl --user is-active skwd-wall.service 2>/dev/null || true + +echo "-- ipc --" +quickshell ipc -p /usr/local/share/skwd-wall/daemon.qml show 2>/dev/null || true + +echo "-- recent logs --" +journalctl --user -u skwd-wall.service -n 40 --no-pager 2>/dev/null || true +MADOS_SKWD_WALL_DOCTOR + chmod +x /usr/local/bin/mados-skwd-wall-doctor + + echo "✓ skwd-wall installed to ${SKWD_WALL_INSTALL_DIR}" + return 0 +} + +# Canonical skwd-wall installer (overrides legacy wrapper-heavy flow) +install_skwd_wall() { + echo "Installing skwd-wall..." + local build_dir="${BUILD_DIR}/skwd-wall_$$" + rm -rf "$build_dir" + mkdir -p "$build_dir" + cd "$BUILD_DIR" + + local retries=3 + local count=0 while [ $count -lt $retries ]; do - echo " → Downloading... (attempt $((count + 1))/$retries)" - if curl -fSL -o "$appimage_path" "$appimage_url"; then - echo " → Download complete" + if GIT_TERMINAL_PROMPT=0 git clone --depth=1 --single-branch --branch main --no-tags "https://github.com/${SKWD_WALL_REPO}.git" "${build_dir}/skwd-wall"; then break fi count=$((count + 1)) - if [ $count -lt $retries ]; then - echo " → Retrying in 10 seconds..." - sleep 10 - fi + echo " Retry $count/$retries..." + sleep 2 done if [ $count -eq $retries ]; then - echo "ERROR: Failed to download Nuclear after $retries attempts" + echo "ERROR: Failed to clone skwd-wall after $retries attempts" + rm -rf "$build_dir" return 1 fi - if [ ! -f "$appimage_path" ] || [ ! -s "$appimage_path" ]; then - echo "ERROR: Downloaded file is missing or empty" - return 1 + mkdir -p "$INSTALL_DIR" "/usr/local/share" "/opt/mados" /etc/skel/.config/skwd-wall /etc/skel/.config/systemd/user + rm -rf "$SKWD_WALL_INSTALL_DIR" + mv "${build_dir}/skwd-wall" "$SKWD_WALL_INSTALL_DIR" + strip_vcs_metadata "$SKWD_WALL_INSTALL_DIR" + + rm -rf "$SKWD_WALL_COMPAT_DIR" + ln -s "$SKWD_WALL_INSTALL_DIR" "$SKWD_WALL_COMPAT_DIR" + + if [[ ! -e "$SKWD_WALL_INSTALL_DIR/scripts" && -d "$SKWD_WALL_INSTALL_DIR/data/scripts" ]]; then + ln -s "$SKWD_WALL_INSTALL_DIR/data/scripts" "$SKWD_WALL_INSTALL_DIR/scripts" fi - echo " → File size: $(stat -c%s "$appimage_path" 2>/dev/null || echo 'unknown') bytes" + if [[ -f "$SKWD_WALL_INSTALL_DIR/data/config.json.example" ]]; then + cp "$SKWD_WALL_INSTALL_DIR/data/config.json.example" /etc/skel/.config/skwd-wall/config.json + sed -i 's/"compositor":[[:space:]]*"[^"]*"/"compositor": "hyprland"/' /etc/skel/.config/skwd-wall/config.json + fi - mkdir -p "${NUCLEAR_INSTALL_DIR}" - cp "$appimage_path" "${NUCLEAR_INSTALL_DIR}/nuclear.AppImage" - chmod +x "${NUCLEAR_INSTALL_DIR}/nuclear.AppImage" + if [[ ! -f /etc/skel/.config/systemd/user/skwd-wall.service ]]; then + cat > /etc/skel/.config/systemd/user/skwd-wall.service << 'SKWD_WALL_SERVICE_FALLBACK' +[Unit] +Description=skwd-wall wallpaper selector daemon +Documentation=https://github.com/madkoding/skwd-wall +PartOf=graphical-session.target +After=graphical-session.target - cat > "${NUCLEAR_BIN}" << 'NUCLEAR_WRAPPER' -#!/bin/bash -exec /opt/nuclear/nuclear.AppImage "$@" -NUCLEAR_WRAPPER - chmod +x "${NUCLEAR_BIN}" - - cat > /usr/share/applications/nuclear.desktop << 'NUCLEAR_DESKTOP' -[Desktop Entry] -Name=Nuclear Music Player -GenericName=Music Player -Comment=Free, open-source music player without ads or tracking -Exec=nuclear %U -Icon=nuclear -Terminal=false -Type=Application -Categories=Audio;Music;Player;AudioVideo; -MimeType=application/ogg;audio/flac;audio/mp3;audio/mpeg;audio/mpegurl;audio/mp4;audio/ogg;audio/vorbis;audio/wav;audio/x-flac;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-ogg;audio/x-vorbis;audio/x-wav;x-scheme-handler/nuclear; -Keywords=music;player;audio;streaming;mp3;flac;ogg; -NUCLEAR_DESKTOP - - mkdir -p /etc/skel/.local/share/applications - cp /usr/share/applications/nuclear.desktop /etc/skel/.local/share/applications/ - - echo "✓ Nuclear ${release_tag} installed to ${NUCLEAR_INSTALL_DIR}" +[Service] +Type=simple +ExecStart=/usr/local/bin/mados-skwd-wall-daemon +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=graphical-session.target +SKWD_WALL_SERVICE_FALLBACK + fi + + if [[ -d /home/mados ]]; then + mkdir -p /home/mados/.config/skwd-wall /home/mados/.config/systemd/user + if [[ -f /etc/skel/.config/skwd-wall/config.json ]]; then + cp /etc/skel/.config/skwd-wall/config.json /home/mados/.config/skwd-wall/config.json + fi + if [[ -f /etc/skel/.config/systemd/user/skwd-wall.service ]]; then + cp /etc/skel/.config/systemd/user/skwd-wall.service /home/mados/.config/systemd/user/skwd-wall.service + fi + chown -R 1000:1000 /home/mados/.config/skwd-wall /home/mados/.config/systemd + fi + + for helper in /usr/local/bin/mados-wallpaper-picker /usr/local/bin/skwd-wall /usr/local/bin/mados-skwd-wall-daemon /usr/local/bin/mados-skwd-wall-sources /usr/local/bin/mados-skwd-wall-doctor; do + if [[ -f "$helper" ]]; then + chmod +x "$helper" + fi + done + + rm -rf "$build_dir" + + echo "✓ skwd-wall installed to ${SKWD_WALL_INSTALL_DIR}" return 0 } -restrict_wallpaper_desktop_to_hyprland() { +restrict_wallpaper_desktop_to_sway() { local desktop local desktop_files=( "/usr/share/applications/mados-wallpaper.desktop" @@ -687,41 +1665,15 @@ restrict_wallpaper_desktop_to_hyprland() { [[ -f "$desktop" ]] || continue if grep -q '^OnlyShowIn=' "$desktop"; then - sed -i 's/^OnlyShowIn=.*/OnlyShowIn=Hyprland;/' "$desktop" + sed -i 's/^OnlyShowIn=.*/OnlyShowIn=Sway;/' "$desktop" elif grep -q '^\[Desktop Entry\]' "$desktop"; then - sed -i '/^\[Desktop Entry\]/a OnlyShowIn=Hyprland;' "$desktop" + sed -i '/^\[Desktop Entry\]/a OnlyShowIn=Sway;' "$desktop" fi - echo " → Restricted $(basename "$desktop") to Hyprland" + echo " → Restricted $(basename "$desktop") to Sway" done } -setup_music_assets() { - if [[ ! -d /usr/share/music ]] || [[ -z "$(ls -A /usr/share/music 2>/dev/null)" ]]; then - return 0 - fi - - mkdir -p /etc/skel/Music /home/mados/Music - - for f in /usr/share/music/*; do - [[ -f "$f" ]] || continue - base="$(basename "$f")" - if [[ ! -e "/etc/skel/Music/$base" ]]; then - ln -s "$f" "/etc/skel/Music/$base" - fi - if [[ -d /home/mados && ! -e "/home/mados/Music/$base" ]]; then - ln -s "$f" "/home/mados/Music/$base" - chown -h 1000:1000 "/home/mados/Music/$base" - fi - done - - if [[ -d /home/mados ]]; then - chown -R 1000:1000 /home/mados/Music - fi - - echo " → Music demo files linked to ~/Music" -} - setup_wallpaper_assets() { local wallpaper_dir="${INSTALL_DIR}/mados_wallpaper" @@ -733,32 +1685,7 @@ setup_wallpaper_assets() { mkdir -p /usr/share/icons/hicolor/scalable/apps cp "$wallpaper_dir/mados-wallpaper.svg" /usr/share/icons/hicolor/scalable/apps/ fi - - mkdir -p /etc/skel/.local/share/mados/wallpapers - mkdir -p /etc/skel/Pictures/Wallpapers - mkdir -p /usr/share/mados/wallpapers - cp "$wallpaper_dir"/*.png /etc/skel/.local/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpg /etc/skel/.local/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpeg /etc/skel/.local/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.png /etc/skel/Pictures/Wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpg /etc/skel/Pictures/Wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpeg /etc/skel/Pictures/Wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.png /usr/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpg /usr/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpeg /usr/share/mados/wallpapers/ 2>/dev/null || true - - if [[ -d /home/mados ]]; then - mkdir -p /home/mados/.local/share/mados/wallpapers - mkdir -p /home/mados/Pictures/Wallpapers - cp "$wallpaper_dir"/*.png /home/mados/.local/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpg /home/mados/.local/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpeg /home/mados/.local/share/mados/wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.png /home/mados/Pictures/Wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpg /home/mados/Pictures/Wallpapers/ 2>/dev/null || true - cp "$wallpaper_dir"/*.jpeg /home/mados/Pictures/Wallpapers/ 2>/dev/null || true - chown -R 1000:1000 /home/mados/.local/share/mados - chown -R 1000:1000 /home/mados/Pictures - fi + } IMPERATIVE_DOTS_REPO="madkoding/theme-imperative-dots" @@ -801,6 +1728,7 @@ install_imperative_dots() { mkdir -p "$(dirname "$IMPERATIVE_DOTS_INSTALL_DIR")" mv "${build_dir}/imperative-dots" "$IMPERATIVE_DOTS_INSTALL_DIR" + strip_vcs_metadata "$IMPERATIVE_DOTS_INSTALL_DIR" rm -rf "$build_dir" chmod +x "${IMPERATIVE_DOTS_INSTALL_DIR}/scripts/start/start.sh" @@ -815,12 +1743,10 @@ install_imperative_dots() { if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then install_imperative_dots install_mados_apps - setup_music_assets setup_wallpaper_assets install_skwd_wall - restrict_wallpaper_desktop_to_hyprland + restrict_wallpaper_desktop_to_sway install_installer install_updater install_oh_my_zsh - install_nuclear fi diff --git a/airootfs/root/customize_airootfs.d/04-cleanup.sh b/airootfs/root/customize_airootfs.d/04-cleanup.sh index e4418b14..b222b03b 100644 --- a/airootfs/root/customize_airootfs.d/04-cleanup.sh +++ b/airootfs/root/customize_airootfs.d/04-cleanup.sh @@ -3,6 +3,18 @@ # Atomic module for cleanup operations set -euo pipefail +SUPPORTED_LOCALE_PREFIXES=( + en + es + fr + de + it + pt + ru + ja + zh +) + clean_pacman_cache() { echo "Cleaning pacman cache..." rm -rf /var/cache/pacman/pkg/* @@ -58,19 +70,21 @@ clean_unused_fonts_icons() { echo "✓ Fonts/icons cleaned" } +is_supported_locale_name() { + local value="$1" + local prefix + + for prefix in "${SUPPORTED_LOCALE_PREFIXES[@]}"; do + if [[ "$value" == "$prefix" || "$value" == "${prefix}_"* ]]; then + return 0 + fi + done + + return 1 +} + keep_only_supported_locales() { echo "Keeping locale support for 9 languages..." - local supported_prefixes=( - en - es - fr - de - it - pt - ru - ja - zh - ) for lang in /usr/share/locale/*; do local lang_name @@ -80,22 +94,98 @@ keep_only_supported_locales() { continue fi - local keep=false - local prefix - for prefix in "${supported_prefixes[@]}"; do - if [[ "$lang_name" == "$prefix" || "$lang_name" == "${prefix}_"* ]]; then - keep=true - break - fi - done - - if [[ "$keep" == false ]]; then + if ! is_supported_locale_name "$lang_name"; then rm -rf "$lang" fi done echo "✓ 9-language locale set applied" } +trim_qt_translations() { + echo "Trimming Qt translations to 9-language set..." + + local dir + for dir in /usr/share/qt/translations /usr/share/qt6/translations; do + [[ -d "$dir" ]] || continue + + local removed=0 + while IFS= read -r -d '' qm_file; do + local base + local keep=false + local prefix + + base=$(basename "$qm_file") + if [[ ! "$base" =~ _[A-Za-z]{2}([_@.-]|$) ]]; then + continue + fi + + for prefix in "${SUPPORTED_LOCALE_PREFIXES[@]}"; do + if [[ "$base" =~ _${prefix}([_@.-]|$) ]]; then + keep=true + break + fi + done + + if [[ "$keep" == false ]]; then + rm -f "$qm_file" + ((removed++)) || true + fi + done < <(find "$dir" -type f -name '*.qm' -print0) + + echo " → Trimmed ${removed} Qt translation files in ${dir}" + done + + echo "✓ Qt translations trimmed" +} + +clean_runtime_dev_artifacts() { + echo "Removing development-only runtime artifacts..." + + rm -rf /usr/include + rm -rf /usr/lib/cmake /usr/share/cmake + rm -rf /usr/lib/pkgconfig /usr/share/pkgconfig + find /usr/lib -type f \( -name "*.a" -o -name "*.la" \) -delete 2>/dev/null || true + rm -rf /usr/share/help/* + rm -rf /usr/share/info/* + + echo "✓ Development artifacts removed" +} + +trim_icon_themes_runtime() { + echo "Trimming icon themes for runtime footprint..." + + rm -rf /usr/share/icons/breeze /usr/share/icons/breeze-dark + rm -rf /usr/share/icons/Papirus-Light /usr/share/icons/Papirus-Dark + if [[ -d /usr/share/icons/Papirus ]]; then + find /usr/share/icons/Papirus -maxdepth 1 -type d -name "*@2x" -exec rm -rf {} + + fi + + echo "✓ Icon themes trimmed" +} + +dedupe_wallpaper_assets() { + echo "Deduplicating wallpaper assets..." + + local canonical_dir="/opt/mados/mados_wallpaper/wallpapers" + local backgrounds_dir="/usr/share/backgrounds" + local skel_mados_dir="/etc/skel/.local/share/mados" + local skel_wallpapers_dir="${skel_mados_dir}/wallpapers" + + if [[ ! -d "$canonical_dir" ]]; then + echo " → Canonical wallpaper dir not found, skipping dedupe" + return 0 + fi + + rm -rf "$backgrounds_dir" + ln -s "$canonical_dir" "$backgrounds_dir" + + mkdir -p "$skel_mados_dir" + rm -rf "$skel_wallpapers_dir" + ln -s "$backgrounds_dir" "$skel_wallpapers_dir" + + echo "✓ Wallpaper assets deduplicated" +} + clean_pacman_db() { echo "Cleaning pacman local database..." local pacman_local_db="/var/lib/pacman/local" @@ -210,6 +300,10 @@ cleanup_all() { clean_debug_symbols clean_unused_fonts_icons keep_only_supported_locales + trim_qt_translations + clean_runtime_dev_artifacts + trim_icon_themes_runtime + dedupe_wallpaper_assets clean_pacman_db set_executable_permissions hide_unwanted_desktop_entries diff --git a/airootfs/root/customize_airootfs.sh b/airootfs/root/customize_airootfs.sh index caa8b9c1..2dd39a08 100644 --- a/airootfs/root/customize_airootfs.sh +++ b/airootfs/root/customize_airootfs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # customize_airootfs.sh - madOS ISO customization -# +# # This script is executed by mkarchiso inside the chroot after packages are # installed. It delegates to atomic modules in customize_airootfs.d/ # @@ -14,22 +14,23 @@ echo "=== madOS: Running post-installation customizations ===" echo "Started at: $(date '+%Y-%m-%d %H:%M:%S')" echo "" +# Source all modules in order +for module in "${MODULES_DIR}"/[0-9][0-9]-*.sh; do + if [[ -f "$module" && -r "$module" ]]; then + echo "Loading: $(basename "$module")" + # shellcheck source=/dev/null + source "$module" + fi +done + +# Run modules in order run_module() { local module_name="$1" local func_name="$2" - local module_path="${MODULES_DIR}/${module_name}" - + echo "" echo "=== Running: $module_name ===" - - if [[ ! -f "$module_path" ]]; then - echo "ERROR: Module $module_name not found at $module_path" - return 1 - fi - - # shellcheck source=/dev/null - source "$module_path" - + if [[ "$(type -t "$func_name" 2>/dev/null)" == "function" ]]; then if ! "$func_name"; then echo "ERROR: $module_name failed" @@ -39,23 +40,13 @@ run_module() { echo "ERROR: Function $func_name not found in $module_name" return 1 fi - + echo "✓ $module_name completed" return 0 } # Execute modules in order - stop on first failure -# 00-kernel.sh - Install madOS kernel -if ! run_module "00-kernel.sh" "install_mados_kernel"; then - echo "FATAL: Kernel installation failed" - exit 1 -fi - -# 01-initramfs.sh - Generate initramfs -if ! run_module "01-initramfs.sh" "generate_initramfs"; then - echo "FATAL: Initramfs generation failed" - exit 1 -fi +# Kernel is provided by packages.x86_64 (linux-lts). # 02-themes.sh - Install themes if ! run_module "02-themes.sh" "install_themes"; then @@ -63,62 +54,44 @@ if ! run_module "02-themes.sh" "install_themes"; then exit 1 fi -# 03-lib.sh - Shared variables (must be sourced, not run as module) -echo "" -echo "=== Loading: 03-lib.sh ===" -source "${MODULES_DIR}/03-lib.sh" -echo "✓ 03-lib.sh loaded" - -# 10-imperative-dots.sh - Install imperative-dots theme -if ! run_module "10-imperative-dots.sh" "install_imperative_dots"; then +# 03-apps.sh - Install applications +if ! run_module "03-apps.sh" "install_imperative_dots"; then echo "FATAL: imperative-dots installation failed" exit 1 fi -# 11-mados-apps.sh - Install madOS native applications -if ! run_module "11-mados-apps.sh" "install_mados_apps"; then +# 03-apps.sh - Install applications +if ! run_module "03-apps.sh" "install_mados_apps"; then echo "FATAL: Apps installation failed" exit 1 fi -# 12-music-assets.sh - Setup music assets -if ! run_module "12-music-assets.sh" "setup_music_assets"; then - echo "FATAL: Music assets setup failed" - exit 1 -fi - -# 13-wallpaper-assets.sh - Setup wallpaper assets -if ! run_module "13-wallpaper-assets.sh" "setup_wallpaper_assets"; then +# 03-apps.sh - Setup wallpaper assets for legacy compatibility +if ! run_module "03-apps.sh" "setup_wallpaper_assets"; then echo "FATAL: Wallpaper assets setup failed" exit 1 fi -# 14-skwd-wall.sh - Install skwd-wall wallpaper selector -if ! run_module "14-skwd-wall.sh" "install_skwd_wall"; then +# 03-apps.sh - Install skwd-wall selector +if ! run_module "03-apps.sh" "install_skwd_wall"; then echo "FATAL: skwd-wall installation failed" exit 1 fi -# 15-updater.sh - Install mados-updater -if ! run_module "15-updater.sh" "install_updater"; then - echo "FATAL: Updater installation failed" - exit 1 -fi - -# 16-installer.sh - Install mados-installer -if ! run_module "16-installer.sh" "install_installer"; then +# Install mados-installer +if ! run_module "03-apps.sh" "install_installer"; then echo "FATAL: Installer installation failed" exit 1 fi -# 17-nuclear.sh - Install Nuclear music player -if ! run_module "17-nuclear.sh" "install_nuclear"; then - echo "FATAL: Nuclear installation failed" +# Install mados-updater +if ! run_module "03-apps.sh" "install_updater"; then + echo "FATAL: Updater installation failed" exit 1 fi -# 18-oh-my-zsh.sh - Install Oh My Zsh -if ! run_module "18-oh-my-zsh.sh" "install_oh_my_zsh"; then +# Install Oh My Zsh +if ! run_module "03-apps.sh" "install_oh_my_zsh"; then echo "FATAL: Oh My Zsh installation failed" exit 1 fi @@ -148,10 +121,12 @@ if ! run_module "07-pacman-runtime.sh" "configure_runtime_pacman"; then fi # 08-firefox-defaults.sh - Configure Firefox for low memory -if ! run_module "08-firefox-defaults.sh" "setup_firefox_policies"; then +if ! run_module "08-firefox-defaults.sh" "setup_skel_firefox_prefs"; then echo "FATAL: Firefox defaults configuration failed" exit 1 fi +setup_root_firefox_prefs +setup_firefox_wrapper # 09-audio-fix.sh - Fix audio "Dummy Output" and enable PipeWire services if ! run_module "09-audio-fix.sh" "main"; then @@ -165,10 +140,8 @@ if ! run_module "04-cleanup.sh" "cleanup_all"; then exit 1 fi -# 09-ai-tools.sh - Install AI tools (OpenClaw, ForgeCode) from AUR -if ! run_module "09-ai-tools.sh" "install_ai_tools"; then - echo "WARNING: AI tools installation had issues (non-fatal, tools may need manual install)" -fi +# 09-ai-tools.sh is intentionally skipped in default builds to keep ISO lean. +# Users can install optional tools (OpenClaw/Forge/Qwen) post-install if needed. echo "" echo "=== madOS: Post-installation complete ===" diff --git a/efiboot/loader/entries/01-archiso-linux.conf b/efiboot/loader/entries/01-archiso-linux.conf index 81f38bc0..247ea2d3 100644 --- a/efiboot/loader/entries/01-archiso-linux.conf +++ b/efiboot/loader/entries/01-archiso-linux.conf @@ -1,5 +1,5 @@ title madOS Live (%ARCH%, UEFI) sort-key 01 -linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-mados -initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-mados.img -options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% cow_spacesize=256M quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 +linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts +initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_spacesize=256M quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 diff --git a/efiboot/loader/entries/02-archiso-safe-graphics.conf b/efiboot/loader/entries/02-archiso-safe-graphics.conf new file mode 100644 index 00000000..474382a6 --- /dev/null +++ b/efiboot/loader/entries/02-archiso-safe-graphics.conf @@ -0,0 +1,5 @@ +title madOS Live (Software Rendering) (%ARCH%, UEFI) +sort-key 02 +linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts +initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_spacesize=256M mados.force_software=1 quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 diff --git a/efiboot/loader/entries/03-archiso-safe-compat.conf b/efiboot/loader/entries/03-archiso-safe-compat.conf new file mode 100644 index 00000000..41483751 --- /dev/null +++ b/efiboot/loader/entries/03-archiso-safe-compat.conf @@ -0,0 +1,5 @@ +title madOS Live (Safe Compat) (%ARCH%, UEFI) +sort-key 03 +linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts +initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_spacesize=256M nomodeset i915.enable_psr=0 i915.enable_dc=0 i915.enable_execlists=0 pcie_aspm=off nvme_core.default_ps_max_latency_us=0 ahci.mobile_lpm_policy=1 quiet splash diff --git a/grub/grub.cfg b/grub/grub.cfg new file mode 100644 index 00000000..5d8a9f58 --- /dev/null +++ b/grub/grub.cfg @@ -0,0 +1,65 @@ +# Load partition table and file system modules +insmod part_gpt +insmod part_msdos +insmod fat +insmod iso9660 +insmod ntfs +insmod ntfscomp +insmod exfat +insmod udf + +# Use graphics-mode output +if loadfont "${prefix}/fonts/unicode.pf2"; then + insmod all_video + set gfxmode="auto" + terminal_input console + terminal_output console +fi + +# Set default menu entry +default=arch +timeout=15 +timeout_style=menu + +# Get a human readable platform identifier +if [ "${grub_platform}" == 'efi' ]; then + archiso_platform='UEFI' +elif [ "${grub_platform}" == 'pc' ]; then + archiso_platform='BIOS' +else + archiso_platform="${grub_cpu}-${grub_platform}" +fi + +menuentry "madOS Live (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'arch' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} + +menuentry "madOS Live with Persistence (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'arch-persist' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_label=mados-persist quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} + +menuentry "madOS Live (Software Rendering) (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'archsafe' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% mados.force_software=1 quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} + +menuentry "madOS Live (Safe Compat) (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'archcompat' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% nomodeset i915.enable_psr=0 i915.enable_dc=0 i915.enable_execlists=0 pcie_aspm=off nvme_core.default_ps_max_latency_us=0 ahci.mobile_lpm_policy=1 quiet splash + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} + +menuentry 'System shutdown' --class shutdown --class poweroff { + echo 'System shutting down...' + halt +} + +menuentry 'System restart' --class reboot --class restart { + echo 'System rebooting...' + reboot +} diff --git a/grub/loopback.cfg b/grub/loopback.cfg new file mode 100644 index 00000000..bc420674 --- /dev/null +++ b/grub/loopback.cfg @@ -0,0 +1,41 @@ +# Search for the ISO volume +search --no-floppy --set=archiso_img_dev --file "${iso_path}" +probe --set archiso_img_dev_uuid --fs-uuid "${archiso_img_dev}" + +# Get a human readable platform identifier +if [ "${grub_platform}" == 'efi' ]; then + archiso_platform='UEFI' +elif [ "${grub_platform}" == 'pc' ]; then + archiso_platform='BIOS' +else + archiso_platform="${grub_cpu}-${grub_platform}" +fi + +# Set default menu entry +default=arch +timeout=15 +timeout_style=menu + +menuentry "madOS Live (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'arch' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} + +menuentry "madOS Live with Persistence (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'arch-persist' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" cow_label=mados-persist quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} + +menuentry "madOS Live (Software Rendering) (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'archsafe' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" mados.force_software=1 quiet splash intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} + +menuentry "madOS Live (Safe Compat) (%ARCH%, ${archiso_platform})" --class mados --class gnu-linux --class gnu --class os --id 'archcompat' { + set gfxpayload=keep + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" nomodeset i915.enable_psr=0 i915.enable_dc=0 i915.enable_execlists=0 pcie_aspm=off nvme_core.default_ps_max_latency_us=0 ahci.mobile_lpm_policy=1 quiet splash + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +} diff --git a/packages.x86_64 b/packages.x86_64 index 305f4f89..3ca17fff 100644 --- a/packages.x86_64 +++ b/packages.x86_64 @@ -1,6 +1,5 @@ # Sistema base base -linux-zen linux-lts linux-firmware nvme-cli @@ -17,26 +16,47 @@ syslinux sudo zsh -# Compositores Wayland (Hyprland para hardware moderno) +# Compositores Wayland (Hyprland para hardware moderno, Sway para software rendering) hyprland hypridle +sway +swaybg +swayidle +swaylock awww quickshell +swaync waybar wofi mako qt5ct qt6ct +xorg-xwayland +xorg-server +xorg-xinit +xorg-xrandr +xf86-video-dummy +xf86-video-vesa +xf86-video-fbdev xdg-desktop-portal-hyprland -xdg-desktop-portal-wlr + +# Additional Desktop Environments +# plasma-meta +# xfce4 +# xfce4-goodies +i3-wm +i3status +dmenu +polybar +feh +# i3lock +# qtile +# niri # Aplicaciones principales foot firefox -libreoffice-fresh -vim nano -code pcmanfm gvfs tumbler @@ -64,7 +84,6 @@ libadwaita bc gst-plugin-gtk gtk-layer-shell -noto-fonts-cjk noto-fonts-emoji gdk-pixbuf2 xdg-desktop-portal-gtk @@ -89,6 +108,7 @@ rfkill grim slurp wl-clipboard +xdg-desktop-portal-wlr brightnessctl matugen @@ -103,16 +123,15 @@ ttf-roboto-mono earlyoom zram-generator -# Drivers de video (multi-GPU moderno) +# Drivers de video (multi-GPU) intel-media-driver vulkan-intel +vulkan-swrast libva-intel-driver xf86-video-amdgpu +xf86-video-ati vulkan-radeon xf86-video-nouveau -nvidia-open -nvidia-settings -nvidia-utils mesa mesa-utils @@ -149,22 +168,18 @@ mpv cava # Build tools -make pkgconf pciutils usbutils # GTK themes (more preinstalled choices for LXAppearance/GTK apps) adw-gtk-theme -materia-gtk-theme -orchis-theme # Icon themes (Papirus as default + alternatives) papirus-icon-theme # Fuentes -ttf-jetbrains-mono-nerd -ttf-iosevka-nerd +ttf-hack-nerd ttf-carlito gnu-free-fonts @@ -187,6 +202,7 @@ e2fsprogs ntfs-3g btrfs-progs unzip +snapper # Soporte VM virtualbox-guest-utils @@ -211,14 +227,15 @@ ollama # OpenCode AI Assistant (~30MB) opencode -# AI tools dependencies -nodejs -npm +# Optional AI tooling helpers +# Keep bat/fd/fzf for terminal workflows and manual tooling installs bat fd fzf # madOS Native Apps Dependencies +# madOS Photo Viewer - image viewing and video playback +# GStreamer with gtksink support (from gst-plugin-gtk package) gstreamer gst-plugins-base gst-plugins-good @@ -226,6 +243,7 @@ gst-plugins-ugly gst-plugins-bad gst-libav gst-python +gst-plugin-gtk python-pillow librsvg libwebp @@ -244,6 +262,5 @@ rkhunter fail2ban zenity -# Optional profiles: -# - packages.optional-heavy.x86_64: CUDA, nvidia-cuda-toolkit, clamav -# - packages.i3-fallback.x86_64: i3-wm, polybar, dmenu (fallback compositor) +# Optional desktop extras moved to packages.optional-heavy.x86_64 +# This default profile stays aligned with low-RAM targets. diff --git a/profiledef.sh b/profiledef.sh index 3b0f4fc5..e1a7d67a 100644 --- a/profiledef.sh +++ b/profiledef.sh @@ -12,6 +12,9 @@ install_dir="arch" buildmodes=('iso') bootmodes=('bios.syslinux' 'uefi.systemd-boot') + +# Keep GRUB loopback config in the ISO for Ventoy/GRUB chainload scenarios. +search_filename="boot/grub/loopback.cfg" pacman_conf="pacman.conf" airootfs_image_type="squashfs" airootfs_image_tool_options=('-comp' 'zstd' '-Xcompression-level' '17') diff --git a/syslinux/archiso_pxe-linux.cfg b/syslinux/archiso_pxe-linux.cfg index 3da98a86..87ea03ea 100644 --- a/syslinux/archiso_pxe-linux.cfg +++ b/syslinux/archiso_pxe-linux.cfg @@ -4,9 +4,9 @@ Boot madOS Live environment using NBD. Includes the madOS installer for system installation. ENDTEXT MENU LABEL madOS Live (%ARCH%, NBD) -LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-mados -INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-mados.img -APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archiso_nbd_srv=${pxeserver} cow_spacesize=256M cms_verify=y quiet splash +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% archiso_nbd_srv=${pxeserver} cow_spacesize=256M cms_verify=y quiet splash SYSAPPEND 3 LABEL arch_nfs @@ -15,8 +15,8 @@ Boot madOS Live environment using NFS. Includes the madOS installer for system installation. ENDTEXT MENU LABEL madOS Live (%ARCH%, NFS) -LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-mados -INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-mados.img +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img APPEND archisobasedir=%INSTALL_DIR% archiso_nfs_srv=${pxeserver}:/run/archiso/bootmnt cow_spacesize=256M cms_verify=y quiet splash SYSAPPEND 3 @@ -26,7 +26,7 @@ Boot madOS Live environment using HTTP. Includes the madOS installer for system installation. ENDTEXT MENU LABEL madOS Live (%ARCH%, HTTP) -LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-mados -INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-mados.img +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=http://${pxeserver}/ cow_spacesize=256M cms_verify=y quiet splash SYSAPPEND 3 diff --git a/syslinux/archiso_sys-linux-compat.cfg b/syslinux/archiso_sys-linux-compat.cfg index 5e89cab5..86f79444 100644 --- a/syslinux/archiso_sys-linux-compat.cfg +++ b/syslinux/archiso_sys-linux-compat.cfg @@ -4,6 +4,6 @@ Uses conservative kernel parameters for difficult hardware. ENDTEXT MENU LABEL madOS Live (Safe Compat) (%ARCH%, BIOS) - LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-mados - INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-mados.img - APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% cow_spacesize=256M nomodeset i915.enable_psr=0 pcie_aspm=off nvme_core.default_ps_max_latency_us=0 quiet splash floppy=0 + LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts + INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img + APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_spacesize=256M nomodeset i915.enable_psr=0 pcie_aspm=off nvme_core.default_ps_max_latency_us=0 quiet splash floppy=0 diff --git a/syslinux/archiso_sys-linux.cfg b/syslinux/archiso_sys-linux.cfg index 36b1d106..3378ff6f 100644 --- a/syslinux/archiso_sys-linux.cfg +++ b/syslinux/archiso_sys-linux.cfg @@ -1,9 +1,29 @@ LABEL arch TEXT HELP - Boot madOS Live environment with Hyprland compositor. + Boot madOS Live environment with Sway compositor. Includes the madOS installer for system installation. ENDTEXT MENU LABEL madOS Live (%ARCH%, BIOS) - LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-mados - INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-mados.img - APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% cow_spacesize=256M quiet splash floppy=0 intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts + INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img + APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_spacesize=256M quiet splash floppy=0 intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + + LABEL arch-persist + TEXT HELP + Boot madOS Live with persistence. Requires a partition labeled 'mados-persist'. + ENDTEXT + MENU LABEL madOS Live with Persistence (%ARCH%, BIOS) + LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts + INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img + APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_label=mados-persist quiet splash floppy=0 intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 + + # Software rendering boot option (wlroots compatible) + LABEL archsafe + TEXT HELP + Boot madOS Live with forced software rendering (pixman/llvmpipe). + Keeps KMS/DRM enabled for Wayland compositors. + ENDTEXT + MENU LABEL madOS Live (Software Rendering) (%ARCH%, BIOS) + LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-lts + INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-lts.img + APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archisolabel=%ARCHISO_LABEL% cow_spacesize=256M mados.force_software=1 quiet splash floppy=0 intel_idle.max_cstate=1 i915.enable_dc=0 ahci.mobile_lpm_policy=1 diff --git a/tests/conftest.py b/tests/conftest.py index 573cc51e..1c62d358 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,62 +1,41 @@ #!/usr/bin/env python3 -"""pytest configuration and fixtures for mados tests""" +"""pytest configuration and fixtures for madOS tests.""" -import os import sys -import pytest from pathlib import Path +import pytest + # Add the project root to the path PROJECT_ROOT = Path(__file__).parent.parent sys.path.insert(0, str(PROJECT_ROOT)) @pytest.fixture -def mados_kernel_version(): - """Standard madOS kernel version for testing""" - return "6.19.10.zen1-34" - - -@pytest.fixture -def mados_kernel_pkgver(): - """Standard madOS kernel package version for testing""" - return "6.19.10.zen1-1" - - -@pytest.fixture -def mados_kernel_url(mados_kernel_version, mados_kernel_pkgver): - """Standard madOS kernel URL for testing""" - return f"https://github.com/madoslinux/mados-kernel/releases/download/v{mados_kernel_version}/linux-mados-{mados_kernel_pkgver}-x86_64.pkg.tar.zst" +def kernel_package_name(): + """Standard kernel package name for current ISO builds.""" + return "linux-lts" @pytest.fixture def mock_live_system(tmp_path): - """Mock a live system structure for testing""" + """Mock a live system structure for testing.""" boot_dir = tmp_path / "boot" boot_dir.mkdir() modules_dir = tmp_path / "lib/modules" - modules_dir.mkdir() - - # Create madOS kernel - (boot_dir / "vmlinuz-linux-mados").write_bytes(b"Linux kernel x86 boot executable vmlinuz") - (boot_dir / "initramfs-linux-mados.img").write_bytes(b"initramfs image") - - kver_dir = modules_dir / "6.19.10-zen1-mados" - kver_dir.mkdir() - (kver_dir / "modules.dep").write_text("") + modules_dir.mkdir(parents=True) - # Create Arch kernel (should be filtered out) - (boot_dir / "vmlinuz-linux").write_bytes(b"Arch Linux kernel") - (boot_dir / "initramfs-linux.img").write_bytes(b"Arch initramfs") + (boot_dir / "vmlinuz-linux-lts").write_bytes(b"Linux kernel x86 boot executable vmlinuz") + (boot_dir / "initramfs-linux-lts.img").write_bytes(b"initramfs image") - arch_modules = modules_dir / "6.19.10-arch1-1" - arch_modules.mkdir() + lts_modules = modules_dir / "6.6.84-1-lts" + lts_modules.mkdir() + (lts_modules / "modules.dep").write_text("", encoding="utf-8") return { "root": tmp_path, "boot": boot_dir, "modules": modules_dir, - "mados_kernel": "6.19.10-zen1-mados", - "arch_kernel": "6.19.10-arch1-1", + "kernel": "6.6.84-1-lts", } diff --git a/tests/test_installer_integration.py b/tests/test_installer_integration.py index 17d2c641..86898df1 100644 --- a/tests/test_installer_integration.py +++ b/tests/test_installer_integration.py @@ -130,6 +130,27 @@ def test_has_explicit_contract_assertions(self): ) self.assertIn("configure-grub.sh still forces rootflags=subvol=@", self.apps_script) self.assertIn("steps.py missing rsync metadata fallback", self.apps_script) + self.assertIn("steps.py missing linux-lts kernel fallback", self.apps_script) + self.assertIn( + "steps.py missing generic supported-kernel error path", + self.apps_script, + ) + self.assertIn( + "configure-grub.sh missing kernel fallback candidate detection", + self.apps_script, + ) + self.assertIn( + "rebuild-initramfs.sh missing kernel fallback candidate detection", + self.apps_script, + ) + self.assertIn( + "setup-bootloader.sh missing linux-lts signing/validation fallback", + self.apps_script, + ) + self.assertIn( + "configure-limine.sh missing dynamic kernel detection", + self.apps_script, + ) class TestInstallerRuntimePaths(unittest.TestCase): diff --git a/tests/test_mados_chwd.py b/tests/test_mados_chwd.py index 65d13be0..c5538b21 100644 --- a/tests/test_mados_chwd.py +++ b/tests/test_mados_chwd.py @@ -372,9 +372,9 @@ def setUp(self): line.strip() for line in f if line.strip() and not line.startswith("#") ] - def test_includes_linux_zen(self): - """packages.x86_64 must include linux-zen kernel.""" - self.assertIn("linux-zen", self.packages, "packages.x86_64 must include linux-zen") + def test_excludes_linux_zen(self): + """packages.x86_64 should not include linux-zen when LTS is selected.""" + self.assertNotIn("linux-zen", self.packages, "packages.x86_64 should not include linux-zen") def test_includes_linux_lts(self): """packages.x86_64 must include linux-lts kernel.""" diff --git a/tests/unit/test_kernel.py b/tests/unit/test_kernel.py index 625c98b8..8705ac63 100644 --- a/tests/unit/test_kernel.py +++ b/tests/unit/test_kernel.py @@ -1,187 +1,33 @@ #!/usr/bin/env python3 -"""Tests for kernel installation logic in 00-kernel.sh""" +"""Tests for linux-lts kernel boot artifact expectations.""" -import os -import stat import pytest -from pathlib import Path -from unittest.mock import patch, MagicMock, mock_open -TEST_KERNEL_VERSION = "6.19.10.zen1-34" -TEST_KERNEL_PKGVER = "6.19.10.zen1-1" -TEST_KERNEL_URL = f"https://github.com/madoslinux/mados-kernel/releases/download/v{TEST_KERNEL_VERSION}/linux-mados-{TEST_KERNEL_PKGVER}-x86_64.pkg.tar.zst" +class TestKernelBootArtifacts: + """Validate linux-lts boot file naming used by boot entries.""" + def test_expected_boot_filenames(self): + """Boot entries should reference linux-lts artifacts.""" + kernel = "vmlinuz-linux-lts" + initramfs = "initramfs-linux-lts.img" -class TestKernelVersionParsing: - """Test kernel version parsing from GitHub API response""" + assert kernel.startswith("vmlinuz-linux-") + assert initramfs.startswith("initramfs-linux-") + assert kernel.endswith("-lts") + assert initramfs.endswith("-lts.img") - def test_version_parsing_from_tag(self): - """Test extracting version from tag format vX.Y.Z""" - tag = "v6.19.10.zen1-34" - version = tag.lstrip("v") - assert version == "6.19.10.zen1-34" - - def test_pkgver_conversion(self): - """Test converting tag version to package version""" - version = "6.19.10.zen1-34" - import re - - pkgver = re.sub(r"^([0-9]+\.[0-9]+\.[0-9]+\.zen1)-[0-9]+$", r"\1-1", version) - assert pkgver == "6.19.10.zen1-1" - - def test_kernel_url_construction(self): - """Test kernel URL is correctly constructed""" - version = "6.19.10.zen1-34" - pkgver = "6.19.10.zen1-1" - url = f"https://github.com/madoslinux/mados-kernel/releases/download/v{version}/linux-mados-{pkgver}-x86_64.pkg.tar.zst" - assert "v6.19.10.zen1-34" in url - assert "linux-mados-6.19.10.zen1-1" in url - - -class TestKernelVerification: - """Test kernel verification functions""" - - def test_verify_kernel_exists(self, tmp_path): - """Test that verify detects existing kernel""" - boot_dir = tmp_path / "boot" - boot_dir.mkdir(parents=True) - modules_dir = tmp_path / "lib/modules" - modules_dir.mkdir(parents=True) - - # Create mock kernel files - vmlinuz = boot_dir / "vmlinuz-linux-mados" - vmlinuz.write_bytes(b"Linux kernel x86 boot executable") - - kver_dir = modules_dir / "6.19.10-zen1-mados" - kver_dir.mkdir() - - # Verify files exist - assert (boot_dir / "vmlinuz-linux-mados").exists() - assert (modules_dir / "6.19.10-zen1-mados").exists() - - def test_verify_kernel_missing(self, tmp_path): - """Test that verify detects missing kernel""" - boot_dir = tmp_path / "boot" - boot_dir.mkdir(parents=True) - modules_dir = tmp_path / "lib/modules" - modules_dir.mkdir(parents=True) - - # Only vmlinuz exists, not modules - vmlinuz = boot_dir / "vmlinuz-linux-mados" - vmlinuz.write_bytes(b"Linux kernel") - - assert (boot_dir / "vmlinuz-linux-mados").exists() - assert not (modules_dir / "6.19.10-zen1-mados").exists() - - -class TestKernelModuleFiltering: - """Test that only linux-mados kernels are detected""" - - def test_filter_mados_modules(self): - """Test filtering modules directories for mados""" - modules = [ - "6.19.10-zen1-mados", - "6.18.20-1-cachyos-lts", - "6.19.10-1-cachyos", - "5.15.100ARCH1-1", # should be filtered - ] - - mados_modules = [m for m in modules if "mados" in m] - assert mados_modules == ["6.19.10-zen1-mados"] - - def test_filter_non_arch_modules(self): - """Test filtering out non-mados kernels""" - modules = [ - "6.19.10-zen1-mados", - "6.19.10-arch1-1", # Arch kernel - should be filtered - "5.10.100-1-lts", # LTS - should be filtered - ] - - # Filter modules that are NOT Arch or other distros - mad_kernel_modules = [m for m in modules if "arch1-1" not in m and "-lts" not in m] - assert "6.19.10-arch1-1" not in mad_kernel_modules - assert "5.10.100-1-lts" not in mad_kernel_modules - - -class TestKernelDownload: - """Test kernel download functionality""" - - def test_fetch_version_network_error(self): - """Test handling network errors when fetching version""" - # Simulate a network error - with pytest.raises(Exception, match="Network error"): - raise Exception("Network error") - - -class TestKernelInstallation: - """Test kernel installation steps""" - - def test_remove_arch_kernel(self, tmp_path): - """Test that Arch kernel files are correctly identified for removal""" - boot_dir = tmp_path / "boot" - boot_dir.mkdir(parents=True) - - # Arch kernel files - (boot_dir / "vmlinuz-linux").write_bytes(b"arch kernel") - (boot_dir / "initramfs-linux.img").write_bytes(b"initramfs") - (boot_dir / "System.map-linux").write_bytes(b"system map") - - # madOS kernel files - (boot_dir / "vmlinuz-linux-mados").write_bytes(b"mados kernel") - (boot_dir / "initramfs-linux-mados.img").write_bytes(b"mados initramfs") - - arch_files = [ + def test_arch_kernel_is_not_target(self): + """Plain linux artifacts are not the selected target.""" + boot_files = { "vmlinuz-linux", "initramfs-linux.img", - "System.map-linux", - ] - - mados_files = [ - "vmlinuz-linux-mados", - "initramfs-linux-mados.img", - ] - - for f in arch_files: - assert (boot_dir / f).exists() - - for f in mados_files: - assert (boot_dir / f).exists() - - -class TestKernelVersionDetection: - """Test kernel version detection from /lib/modules""" - - def test_detect_mados_kernel_version(self): - """Test detection of mados kernel version""" - import re - - modules_list = [ - "6.19.10-zen1-mados", - "6.18.20-1-cachyos-lts", - "6.19.10-1-cachyos", - ] - - # Find mados kernel - pattern = re.compile(r"^6\.[0-9]+\.[0-9]+-zen1-mados$") - mados_modules = [m for m in modules_list if pattern.match(m)] - - assert mados_modules == ["6.19.10-zen1-mados"] - - def test_exclude_arch_kernel(self): - """Test that arch kernel version is excluded""" - import re - - modules_list = [ - "6.19.10-zen1-mados", - "6.19.10-arch1-1", # Should NOT match - ] - - pattern = re.compile(r"^6\.[0-9]+\.[0-9]+-zen1-mados$") - mados_modules = [m for m in modules_list if pattern.match(m)] + "vmlinuz-linux-lts", + "initramfs-linux-lts.img", + } - assert mados_modules == ["6.19.10-zen1-mados"] - assert "6.19.10-arch1-1" not in mados_modules + assert "vmlinuz-linux-lts" in boot_files + assert "initramfs-linux-lts.img" in boot_files if __name__ == "__main__": diff --git a/tests/unit/test_kernel_flow.py b/tests/unit/test_kernel_flow.py index 9deeb320..3d157d56 100644 --- a/tests/unit/test_kernel_flow.py +++ b/tests/unit/test_kernel_flow.py @@ -1,112 +1,107 @@ #!/usr/bin/env python3 -"""Tests for kernel installation flow - validates 00-kernel.sh logic""" +"""Tests for boot configuration flow with linux-lts and archiso label lookup.""" -import os -import pytest from pathlib import Path -import urllib.request -import json - - -class TestKernelInstallationFlow: - """Validate that the kernel installation flow works correctly""" - - def test_fetches_latest_kernel_version(self): - """Verify we can fetch latest kernel version from GitHub API""" - url = "https://api.github.com/repos/madoslinux/mados-kernel/releases/latest" - response = urllib.request.urlopen(url) - data = json.loads(response.read().decode()) - tag = data["tag_name"] - - assert tag.startswith("v"), f"Tag should start with v, got: {tag}" - assert "zen1" in tag, f"Tag should contain zen1, got: {tag}" - - def test_kernel_url_construction(self): - """Test kernel URL is correctly constructed""" - version = "6.19.10.zen1-34" - import re - - pkgver = re.sub(r"^([0-9]+\.[0-9]+\.[0-9]+\.zen1)-[0-9]+$", r"\1-1", version) - assert pkgver == "6.19.10.zen1-1" - - url = f"https://github.com/madoslinux/mados-kernel/releases/download/v{version}/linux-mados-{pkgver}-x86_64.pkg.tar.zst" - assert "v6.19.10.zen1-34" in url - assert "linux-mados-6.19.10.zen1-1" in url - - def test_kernel_package_contents(self): - """Verify kernel package contains expected files""" - version = "6.19.10.zen1-34" - pkgver = "6.19.10.zen1-1" - url = f"https://github.com/madoslinux/mados-kernel/releases/download/v{version}/linux-mados-{pkgver}-x86_64.pkg.tar.zst" - - # Just verify URL is valid (don't actually download) - assert url.endswith(".pkg.tar.zst") - assert "linux-mados-" in url - - def test_headers_package_contents(self): - """Verify headers package contains expected files""" - version = "6.19.10.zen1-34" - pkgver = "6.19.10.zen1-1" - url = f"https://github.com/madoslinux/mados-kernel/releases/download/v{version}/linux-mados-headers-{pkgver}-x86_64.pkg.tar.zst" - - assert url.endswith(".pkg.tar.zst") - assert "linux-mados-headers" in url +import pytest -class TestKernelPathFiltering: - """Test that kernel filtering logic correctly identifies madOS kernel""" - - def test_mados_module_filter(self): - """Modules directory should filter for mados only""" - modules = [ - "6.19.10-zen1-mados", # Should be included - "6.19.10-arch1-1", # Should be excluded - "5.15.100-1-ARCH", # Should be excluded - "6.18.20-1-cachyos-lts", # Should be excluded - ] - - import re - - pattern = re.compile(r"^6\.[0-9]+\.[0-9]+-zen1-mados$") - - for mod in modules: - if pattern.match(mod): - assert "mados" in mod - - def test_arch_kernel_excluded(self): - """Arch kernel should be identified for removal""" - boot_files = [ - "/boot/vmlinuz-linux", # Arch - should be removed - "/boot/vmlinuz-linux-mados", # madOS - should be kept - "/boot/initramfs-linux.img", # Arch - should be removed - "/boot/initramfs-linux-mados.img", # madOS - should be kept - ] - - arch_kernel_files = [ - f for f in boot_files if f == "/boot/vmlinuz-linux" or f == "/boot/initramfs-linux.img" - ] - mados_kernel_files = [f for f in boot_files if "mados" in f] - - assert "/boot/vmlinuz-linux" in arch_kernel_files - assert "/boot/vmlinuz-linux-mados" in mados_kernel_files - - -class TestKernelVersionParsing: - """Test kernel version parsing from GitHub tag""" - - def test_version_stripping(self): - """Tag v6.19.10.zen1-34 should become 6.19.10.zen1-34""" - tag = "v6.19.10.zen1-34" - version = tag.lstrip("v") - assert version == "6.19.10.zen1-34" - - def test_pkgver_conversion(self): - """6.19.10.zen1-34 should become 6.19.10-zen1""" - import re - version = "6.19.10.zen1-34" - pkgver = re.sub(r"^([0-9]+\.[0-9]+\.[0-9]+\.zen1)-[0-9]+$", r"\1-1", version) - assert pkgver == "6.19.10.zen1-1" +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _read(path: str) -> str: + return (REPO_ROOT / path).read_text(encoding="utf-8") + + +class TestBootEntriesUseLtsKernel: + """Boot entries should point to linux-lts kernel artifacts.""" + + @pytest.mark.parametrize( + "path", + [ + "syslinux/archiso_sys-linux.cfg", + "syslinux/archiso_sys-linux-compat.cfg", + "syslinux/archiso_pxe-linux.cfg", + "efiboot/loader/entries/01-archiso-linux.conf", + "efiboot/loader/entries/02-archiso-safe-graphics.conf", + "efiboot/loader/entries/03-archiso-safe-compat.conf", + ], + ) + def test_uses_linux_lts_paths(self, path): + content = _read(path) + assert "vmlinuz-linux-lts" in content + assert "initramfs-linux-lts.img" in content + assert "vmlinuz-linux-mados" not in content + assert "initramfs-linux-mados.img" not in content + + +class TestArchisoLookupCompatibility: + """Boot entries should use label lookup for Ventoy compatibility.""" + + @pytest.mark.parametrize( + "path", + [ + "syslinux/archiso_sys-linux.cfg", + "syslinux/archiso_sys-linux-compat.cfg", + "syslinux/archiso_pxe-linux.cfg", + "efiboot/loader/entries/01-archiso-linux.conf", + "efiboot/loader/entries/02-archiso-safe-graphics.conf", + "efiboot/loader/entries/03-archiso-safe-compat.conf", + ], + ) + def test_uses_archisolabel(self, path): + content = _read(path) + assert "archisolabel=%ARCHISO_LABEL%" in content + + @pytest.mark.parametrize( + "path", + [ + "syslinux/archiso_sys-linux.cfg", + "syslinux/archiso_sys-linux-compat.cfg", + "syslinux/archiso_pxe-linux.cfg", + "efiboot/loader/entries/01-archiso-linux.conf", + "efiboot/loader/entries/02-archiso-safe-graphics.conf", + "efiboot/loader/entries/03-archiso-safe-compat.conf", + ], + ) + def test_keeps_archisosearchuuid(self, path): + content = _read(path) + assert "archisosearchuuid=%ARCHISO_UUID%" in content + + +class TestKernelCustomizationFlow: + """The build flow should not force custom kernel install.""" + + def test_customize_script_does_not_run_custom_kernel_modules(self): + content = _read("airootfs/root/customize_airootfs.sh") + assert 'run_module "00-kernel.sh" "install_mados_kernel"' not in content + assert 'run_module "01-initramfs.sh" "generate_initramfs"' not in content + + +class TestGrubLoopbackCompatibility: + """GRUB loopback config should support ISO loop boot (e.g. Ventoy).""" + + def test_loopback_cfg_exists_and_uses_img_loop(self): + content = _read("grub/loopback.cfg") + assert "img_dev=UUID=${archiso_img_dev_uuid}" in content + assert 'img_loop="${iso_path}"' in content + assert "vmlinuz-linux-lts" in content + assert "initramfs-linux-lts.img" in content + + def test_grub_cfg_exists_with_lts_entries(self): + content = _read("grub/grub.cfg") + assert "vmlinuz-linux-lts" in content + assert "initramfs-linux-lts.img" in content + assert "cow_label=mados-persist" in content + assert "archisolabel=%ARCHISO_LABEL%" in content + + +class TestProfileSearchFilename: + """Ensure profile exports GRUB search filename for loopback support.""" + + def test_profiledef_sets_search_filename_to_loopback(self): + content = _read("profiledef.sh") + assert 'search_filename="boot/grub/loopback.cfg"' in content if __name__ == "__main__":