From d23df8df84afaf058dcfb24a5afe5f351d689c34 Mon Sep 17 00:00:00 2001 From: ciomek Date: Sat, 9 Aug 2025 14:41:44 +0200 Subject: [PATCH] More --- .gitignore | 1 + build.sh | 188 ++++++++++-------- {src => home}/.bashrc | 0 {src => home}/.config/gtk-3.0/settings.ini | 0 {src => home}/.config/hypr/hyprland.conf | 2 +- {src => home}/.config/hypr/hyprpaper.conf | 0 {src => home}/.config/hypr/monitors-1.conf | 0 {src => home}/.config/hypr/monitors-2.conf | 0 {src => home}/.config/kitty/kitty.conf | 0 {src => home}/.config/mako/config | 0 {src => home}/.config/mimeapps.list | 0 {src => home}/.config/nvim/init.vim | 0 {src => home}/.config/waybar/config.jsonc | 0 {src => home}/.config/waybar/power_menu.xml | 0 {src => home}/.config/waybar/style.css | 0 {src => home}/.config/wofi/style.css | 0 {src => home}/.profile | 0 home/Documents/bookmarks.html | 25 +++ {src => home}/Pictures/wallpaper.png | Bin setup.py | 77 ------- setup_system.py | 17 ++ setup_user.py | 22 ++ .../usr/lib/sddm/sddm.conf.d/default.conf | 139 +++++++++++++ .../themes/where_is_my_sddm_theme/theme.conf | 68 +++++++ utils.py | 83 ++++++++ 25 files changed, 463 insertions(+), 159 deletions(-) create mode 100644 .gitignore rename {src => home}/.bashrc (100%) rename {src => home}/.config/gtk-3.0/settings.ini (100%) rename {src => home}/.config/hypr/hyprland.conf (99%) rename {src => home}/.config/hypr/hyprpaper.conf (100%) rename {src => home}/.config/hypr/monitors-1.conf (100%) rename {src => home}/.config/hypr/monitors-2.conf (100%) rename {src => home}/.config/kitty/kitty.conf (100%) rename {src => home}/.config/mako/config (100%) rename {src => home}/.config/mimeapps.list (100%) rename {src => home}/.config/nvim/init.vim (100%) rename {src => home}/.config/waybar/config.jsonc (100%) rename {src => home}/.config/waybar/power_menu.xml (100%) rename {src => home}/.config/waybar/style.css (100%) rename {src => home}/.config/wofi/style.css (100%) rename {src => home}/.profile (100%) create mode 100644 home/Documents/bookmarks.html rename {src => home}/Pictures/wallpaper.png (100%) delete mode 100644 setup.py create mode 100644 setup_system.py create mode 100644 setup_user.py create mode 100644 system_files/usr/lib/sddm/sddm.conf.d/default.conf create mode 100644 system_files/usr/share/sddm/themes/where_is_my_sddm_theme/theme.conf create mode 100644 utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/build.sh b/build.sh index 6d9fe78..c8a092f 100755 --- a/build.sh +++ b/build.sh @@ -1,115 +1,141 @@ #!/bin/sh - set -e -ORIG_PWD=$(pwd) -# Elevate to root if needed +# === Colors === +RED="\033[1;31m" +GREEN="\033[1;32m" +YELLOW="\033[1;33m" +BLUE="\033[1;34m" +MAGENTA="\033[1;35m" +CYAN="\033[1;36m" +RESET="\033[0m" + +log_info() { printf "${CYAN}[*]${RESET} %s\n" "$1"; } +log_success() { printf "${GREEN}[✓]${RESET} %s\n" "$1"; } +log_warn() { printf "${YELLOW}[!]${RESET} %s\n" "$1"; } +log_error() { printf "${RED}[✗]${RESET} %s\n" "$1"; } + +ORIG_PWD=$(pwd) +ORIG_USER=$(logname) +ORIG_HOME=$(getent passwd "$ORIG_USER" | cut -d: -f6) + +# === Elevate to root if needed === if [ "$(id -u)" -ne 0 ]; then exec sudo "$0" "$@" fi -ORIG_USER=$(logname) -ORIG_HOME=$(getent passwd "$ORIG_USER" | cut -d: -f6) - -# === Update system === -echo "Updating system..." -pacman -Syu --noconfirm - -# === Essential tools === +# === Package groups === ESSENTIAL_PACKAGES=" - python3 - vim - neovim - ranger - firefox + base-devel python3 vim neovim ranger firefox " -# === Display manager === DISPLAY_MANAGER=" - sddm + sddm qt5-graphicaleffects " -# === Wayland & Hyprland environment === WAYLAND_ENVIRONMENT=" - hyprland - hyprpaper - waybar - kitty - wofi - mako - libnotify - grim - slurp - wl-clipboard - wayland-utils - xorg-xwayland + hyprland hyprpaper waybar kitty wofi mako libnotify grim slurp + wl-clipboard wl-clip-persist wayland-utils xorg-xwayland " -# === Audio === AUDIO_PACKAGES=" - pipewire - wireplumber - pipewire-pulse - pavucontrol + pipewire wireplumber pipewire-pulse pavucontrol " -# === Fonts & theming === FONTS_AND_THEME=" - otf-font-awesome - noto-fonts - noto-fonts-cjk - ttf-jetbrains-mono - orchis-theme + otf-font-awesome noto-fonts noto-fonts-cjk ttf-jetbrains-mono orchis-theme " -# === Multimedia === MEDIA_PACKAGES=" - mpv - eog - ffmpeg + mpv eog ffmpeg " -# === Install all packages === -echo "Installing packages..." -pacman -S --noconfirm --needed \ - $ESSENTIAL_PACKAGES \ - $DISPLAY_MANAGER \ - $WAYLAND_ENVIRONMENT \ - $AUDIO_PACKAGES \ - $FONTS_AND_THEME \ - $MEDIA_PACKAGES +# === Functions === +update_system() { + log_info "Updating system..." + pacman -Syu --noconfirm +} -# === Enable display manager === -echo "Enabling SDDM..." -systemctl enable sddm -# === Apply GTK settings for theme and color scheme === -echo "Applying GTK settings..." +install_packages() { + log_info "Installing packages..." + pacman -S --noconfirm --needed \ + $ESSENTIAL_PACKAGES \ + $DISPLAY_MANAGER \ + $WAYLAND_ENVIRONMENT \ + $AUDIO_PACKAGES \ + $FONTS_AND_THEME \ + $MEDIA_PACKAGES +} -# Extract DBus address from the user's environment -USER_DBUS_ENV=$(sudo -u "$ORIG_USER" -- dbus-launch) -eval "$USER_DBUS_ENV" +apply_gtk_settings() { + log_info "Applying GTK settings..." + export XDG_RUNTIME_DIR="/run/user/$(id -u "$ORIG_USER")" + DBUS_ADDR="unix:path=${XDG_RUNTIME_DIR}/bus" -export DBUS_SESSION_BUS_ADDRESS -export XDG_RUNTIME_DIR="/run/user/$(id -u $ORIG_USER)" + for setting in \ + "org.gnome.desktop.interface gtk-theme Orchis-Dark-Compact" \ + "org.gnome.desktop.interface icon-theme Orchis-Dark-Compact" \ + "org.gnome.desktop.interface color-scheme prefer-dark" + do + if sudo -u "$ORIG_USER" \ + DBUS_SESSION_BUS_ADDRESS="$DBUS_ADDR" \ + gsettings set $setting; then + log_success "Set $setting" + else + log_warn "Failed to set $setting" + fi + done +} -sudo -u "$ORIG_USER" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \ - XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \ - gsettings set org.gnome.desktop.interface gtk-theme "Orchis-Dark-Compact" +enable_sddm() { + log_info "Enabling SDDM..." + systemctl enable sddm +} -sudo -u "$ORIG_USER" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \ - XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \ - gsettings set org.gnome.desktop.interface icon-theme "Orchis-Dark-Compact" +install_sddm_theme() { + log_info "Installing SDDM theme..." + if pacman -Q where-is-my-sddm-theme-git >/dev/null 2>&1; then + log_success "SDDM theme already installed." + return + fi -sudo -u "$ORIG_USER" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \ - XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \ - gsettings set org.gnome.desktop.interface color-scheme "prefer-dark" + sudo -u "$ORIG_USER" mkdir -p "$ORIG_HOME/Repositories" + cd "$ORIG_HOME/Repositories" -echo "GTK settings applied successfully." + if [ ! -d "where-is-my-sddm-theme" ]; then + sudo -u "$ORIG_USER" git clone https://aur.archlinux.org/where-is-my-sddm-theme-git.git where-is-my-sddm-theme + else + log_warn "Theme repo already exists, skipping clone." + fi -# === Run dotfiles setup === -echo "Running dotfiles setup as $ORIG_USER..." -cd "$ORIG_PWD" || exit 1 -sudo -u "$ORIG_USER" HOME="$ORIG_HOME" python3 setup.py -sudo -E -u "$ORIG_USER" HOME="$ORIG_HOME" mkdir -p "$ORIG_HOME/Downloads" "$ORIG_HOME/Documents" "$ORIG_HOME/Pictures" "$ORIG_HOME/Videos" "$ORIG_HOME/Repositories" + cd "$ORIG_HOME/Repositories/where-is-my-sddm-theme" + sudo -u "$ORIG_USER" makepkg -si --noconfirm +} -echo "Dotfiles installed successfully." +run_dotfiles_setup() { + log_info "Running dotfiles setup..." + cd "$ORIG_PWD" + + [ -f "setup_user.py" ] && sudo -u "$ORIG_USER" HOME="$ORIG_HOME" python3 setup_user.py \ + || log_warn "setup_user.py not found." + + [ -f "setup_system.py" ] && python3 setup_system.py \ + || log_warn "setup_system.py not found." + + sudo -u "$ORIG_USER" mkdir -p \ + "$ORIG_HOME/Downloads" \ + "$ORIG_HOME/Documents" \ + "$ORIG_HOME/Pictures" \ + "$ORIG_HOME/Videos" \ + "$ORIG_HOME/Repositories" +} + +# === Main flow === +update_system +install_packages +apply_gtk_settings +enable_sddm +install_sddm_theme +run_dotfiles_setup + +log_success "Setup completed successfully." diff --git a/src/.bashrc b/home/.bashrc similarity index 100% rename from src/.bashrc rename to home/.bashrc diff --git a/src/.config/gtk-3.0/settings.ini b/home/.config/gtk-3.0/settings.ini similarity index 100% rename from src/.config/gtk-3.0/settings.ini rename to home/.config/gtk-3.0/settings.ini diff --git a/src/.config/hypr/hyprland.conf b/home/.config/hypr/hyprland.conf similarity index 99% rename from src/.config/hypr/hyprland.conf rename to home/.config/hypr/hyprland.conf index 6a357b0..0740b9a 100644 --- a/src/.config/hypr/hyprland.conf +++ b/home/.config/hypr/hyprland.conf @@ -24,7 +24,7 @@ $screenshot = grim -g "$(slurp -d)" - | wl-copy ################# # Autostart necessary processes (like notifications daemons, status bars, etc.) -exec-once=waybar & hyprpaper +exec-once=waybar & hyprpaper & wl-clip-persist --clipboard both & mako # & mako --config=$HOME/.config/mako/config diff --git a/src/.config/hypr/hyprpaper.conf b/home/.config/hypr/hyprpaper.conf similarity index 100% rename from src/.config/hypr/hyprpaper.conf rename to home/.config/hypr/hyprpaper.conf diff --git a/src/.config/hypr/monitors-1.conf b/home/.config/hypr/monitors-1.conf similarity index 100% rename from src/.config/hypr/monitors-1.conf rename to home/.config/hypr/monitors-1.conf diff --git a/src/.config/hypr/monitors-2.conf b/home/.config/hypr/monitors-2.conf similarity index 100% rename from src/.config/hypr/monitors-2.conf rename to home/.config/hypr/monitors-2.conf diff --git a/src/.config/kitty/kitty.conf b/home/.config/kitty/kitty.conf similarity index 100% rename from src/.config/kitty/kitty.conf rename to home/.config/kitty/kitty.conf diff --git a/src/.config/mako/config b/home/.config/mako/config similarity index 100% rename from src/.config/mako/config rename to home/.config/mako/config diff --git a/src/.config/mimeapps.list b/home/.config/mimeapps.list similarity index 100% rename from src/.config/mimeapps.list rename to home/.config/mimeapps.list diff --git a/src/.config/nvim/init.vim b/home/.config/nvim/init.vim similarity index 100% rename from src/.config/nvim/init.vim rename to home/.config/nvim/init.vim diff --git a/src/.config/waybar/config.jsonc b/home/.config/waybar/config.jsonc similarity index 100% rename from src/.config/waybar/config.jsonc rename to home/.config/waybar/config.jsonc diff --git a/src/.config/waybar/power_menu.xml b/home/.config/waybar/power_menu.xml similarity index 100% rename from src/.config/waybar/power_menu.xml rename to home/.config/waybar/power_menu.xml diff --git a/src/.config/waybar/style.css b/home/.config/waybar/style.css similarity index 100% rename from src/.config/waybar/style.css rename to home/.config/waybar/style.css diff --git a/src/.config/wofi/style.css b/home/.config/wofi/style.css similarity index 100% rename from src/.config/wofi/style.css rename to home/.config/wofi/style.css diff --git a/src/.profile b/home/.profile similarity index 100% rename from src/.profile rename to home/.profile diff --git a/home/Documents/bookmarks.html b/home/Documents/bookmarks.html new file mode 100644 index 0000000..6ffce9b --- /dev/null +++ b/home/Documents/bookmarks.html @@ -0,0 +1,25 @@ + + + + +Bookmarks +

Bookmarks Menu

+ +

+

Mozilla Firefox

+

+

Get Help +
Customize Firefox +
Get Involved +
About Us +

+

Bookmarks Toolbar

+

+

+
+
+

+

diff --git a/src/Pictures/wallpaper.png b/home/Pictures/wallpaper.png similarity index 100% rename from src/Pictures/wallpaper.png rename to home/Pictures/wallpaper.png diff --git a/setup.py b/setup.py deleted file mode 100644 index 7370873..0000000 --- a/setup.py +++ /dev/null @@ -1,77 +0,0 @@ -import os -import shutil - -SRC_DIR = "src" -HOME_DIR = os.path.expanduser("~") - -CHOICES = { - '.config/hypr/monitors.conf': ['.config/hypr/monitors-1.conf', '.config/hypr/monitors-2.conf'] -} - - -def ask_override(path): - resp = input(f"'{path}' already exists. Override? (Y/N): ").strip().lower() - return resp == 'y' - - -def choice_files(src_root, dst_root, choices_map, override_all=False): - for final_name, options in choices_map.items(): - existing_options = [f for f in options if os.path.exists(os.path.join(src_root, f))] - if not existing_options: - print(f"No source file found among options for {final_name}. Skipping.") - continue - - if len(existing_options) == 1: - chosen = existing_options[0] - else: - print(f"Choose which file to copy as '{final_name}':") - for idx, opt in enumerate(existing_options, 1): - print(f"{idx}: {opt}") - while True: - try: - choice_idx = int(input(f"Enter number (1-{len(existing_options)}): ")) - if 1 <= choice_idx <= len(existing_options): - chosen = existing_options[choice_idx - 1] - break - except ValueError: - pass - print("Invalid choice, try again.") - - src_file = os.path.join(src_root, chosen) - dst_file = os.path.join(dst_root, final_name) - - if os.path.exists(dst_file): - if not override_all and not ask_override(dst_file): - print(f"Skipped overriding {dst_file}") - continue - - shutil.copy2(src_file, dst_file) - print(f"Copied {chosen} -> {final_name}") - -def copy_with_structure(src_root, dst_root, override_all=False): - for root, dirs, files in os.walk(src_root): - rel_path = os.path.relpath(root, src_root) - target_dir = os.path.join(dst_root, rel_path) - - os.makedirs(target_dir, exist_ok=True) - - for file in files: - src_file = os.path.join(root, file) - dst_file = os.path.join(target_dir, file) - - if os.path.exists(dst_file): - if not override_all and not ask_override(dst_file): - continue - - shutil.copy2(src_file, dst_file) - print(f"Copied: {src_file} -> {dst_file}") - -def main(): - override_all = input('Override all existing files? (Y/N): ').strip().lower() == 'y' - - choice_files(SRC_DIR, HOME_DIR, CHOICES, override_all) - - copy_with_structure(SRC_DIR, HOME_DIR, override_all) - -if __name__ == '__main__': - main() diff --git a/setup_system.py b/setup_system.py new file mode 100644 index 0000000..c5d6a2b --- /dev/null +++ b/setup_system.py @@ -0,0 +1,17 @@ +import sys +from utils import choice_files, copy_with_structure, require_root, log_info, log_error, CYAN, RESET + +SRC_DIR = "system_files" +DST_DIR = "/" +CHOICES = {} + + +def main(): + require_root() + override_all = input(f"{CYAN}Override all existing system files? (Y/N): {RESET}").strip().lower() == 'y' + choice_files(SRC_DIR, DST_DIR, CHOICES, override_all) + copy_with_structure(SRC_DIR, DST_DIR, override_all) + + +if __name__ == '__main__': + main() diff --git a/setup_user.py b/setup_user.py new file mode 100644 index 0000000..78210de --- /dev/null +++ b/setup_user.py @@ -0,0 +1,22 @@ +import os +from utils import choice_files, copy_with_structure, CYAN, RESET + + +SRC_DIR = "home" +HOME_DIR = os.path.expanduser("~") +CHOICES = { + '.config/hypr/monitors.conf': [ + '.config/hypr/monitors-1.conf', + '.config/hypr/monitors-2.conf' + ] +} + + +def main(): + override_all = input(f"{CYAN}Override all existing files for user (mainly .config)? (Y/N): {RESET}").strip().lower() == 'y' + choice_files(SRC_DIR, HOME_DIR, CHOICES, override_all) + copy_with_structure(SRC_DIR, HOME_DIR, override_all) + + +if __name__ == '__main__': + main() diff --git a/system_files/usr/lib/sddm/sddm.conf.d/default.conf b/system_files/usr/lib/sddm/sddm.conf.d/default.conf new file mode 100644 index 0000000..13d51d6 --- /dev/null +++ b/system_files/usr/lib/sddm/sddm.conf.d/default.conf @@ -0,0 +1,139 @@ +[Autologin] +# Whether sddm should automatically log back into sessions when they exit +Relogin=false + +# Name of session file for autologin session (if empty try last logged in) +Session= + +# Username for autologin session +User= + + +[General] +# Which display server should be used. +# Valid values are: x11, x11-user, wayland. Wayland support is experimental +DisplayServer=x11 + +# Comma-separated list of environment variables to be set +GreeterEnvironment= + +# Halt command +HaltCommand=/usr/bin/systemctl poweroff + +# Input method module +InputMethod= + +# Comma-separated list of Linux namespaces for user session to enter +Namespaces= + +# Initial NumLock state. Can be on, off or none. +# If property is set to none, numlock won't be changed +# NOTE: Currently ignored if autologin is enabled. +Numlock=none + +# Reboot command +RebootCommand=/usr/bin/systemctl reboot + + +[Theme] +# Current theme name +Current=where_is_my_sddm_theme + +# Cursor size used in the greeter +CursorSize= + +# Cursor theme used in the greeter +CursorTheme= + +# Number of users to use as threshold +# above which avatars are disabled +# unless explicitly enabled with EnableAvatars +DisableAvatarsThreshold=7 + +# Enable display of custom user avatars +EnableAvatars=true + +# Global directory for user avatars +# The files should be named .face.icon +FacesDir=/usr/share/sddm/faces + +# Font used in the greeter +Font= + +# Theme directory path +ThemeDir=/usr/share/sddm/themes + + +[Users] +# Default $PATH for logged in users +DefaultPath=/usr/local/sbin:/usr/local/bin:/usr/bin + +# Comma-separated list of shells. +# Users with these shells as their default won't be listed +HideShells= + +# Comma-separated list of users that should not be listed +HideUsers= + +# Maximum user id for displayed users +MaximumUid=60513 + +# Minimum user id for displayed users +MinimumUid=1000 + +# Remember the session of the last successfully logged in user +RememberLastSession=true + +# Remember the last successfully logged in user +RememberLastUser=true + +# When logging in as the same user twice, restore the original session, rather than create a new one +ReuseSession=true + + +[Wayland] +# Path of the Wayland compositor to execute when starting the greeter +CompositorCommand=weston --shell=kiosk + +# Enable Qt's automatic high-DPI scaling +EnableHiDPI=true + +# Path to a script to execute when starting the desktop session +SessionCommand=/usr/share/sddm/scripts/wayland-session + +# Comma-separated list of directories containing available Wayland sessions +SessionDir=/usr/local/share/wayland-sessions,/usr/share/wayland-sessions + +# Path to the user session log file +SessionLogFile=.local/share/sddm/wayland-session.log + + +[X11] +# Path to a script to execute when starting the display server +DisplayCommand=/usr/share/sddm/scripts/Xsetup + +# Path to a script to execute when stopping the display server +DisplayStopCommand=/usr/share/sddm/scripts/Xstop + +# Enable Qt's automatic high-DPI scaling +EnableHiDPI=true + +# Arguments passed to the X server invocation +ServerArguments=-nolisten tcp + +# Path to X server binary +ServerPath=/usr/bin/X + +# Path to a script to execute when starting the desktop session +SessionCommand=/usr/share/sddm/scripts/Xsession + +# Comma-separated list of directories containing available X sessions +SessionDir=/usr/local/share/xsessions,/usr/share/xsessions + +# Path to the user session log file +SessionLogFile=.local/share/sddm/xorg-session.log + +# Path to Xephyr binary +XephyrPath=/usr/bin/Xephyr + + diff --git a/system_files/usr/share/sddm/themes/where_is_my_sddm_theme/theme.conf b/system_files/usr/share/sddm/themes/where_is_my_sddm_theme/theme.conf new file mode 100644 index 0000000..554724d --- /dev/null +++ b/system_files/usr/share/sddm/themes/where_is_my_sddm_theme/theme.conf @@ -0,0 +1,68 @@ +[General] +# Password mask character +passwordCharacter=* +# Mask password characters or not ("true" or "false") +passwordMask=true +# value "1" is all display width, "0.5" is a half of display width etc. +passwordInputWidth=0.5 +# Background color of password input +passwordInputBackground= +# Radius of password input corners +passwordInputRadius= +# Width of the border for the password input +passwordInputBorderWidth=0 +# Border color for the password input +passwordInputBorderColor= +# "true" for visible cursor, "false" +passwordInputCursorVisible=true +# Font size of password (in points) +passwordFontSize=96 +passwordCursorColor=random +passwordTextColor= +# Allow blank password (e.g., if authentication is done by another PAM module) +passwordAllowEmpty=false + +# Radius of the border which is displayed upon wrong authentication attempt +wrongPasswordBorderRadius= +# Color of the border which is displayed upon wrong authentication attempt +wrongPasswordBorderColor= + +# Enable or disable cursor blink animation ("true" or "false") +cursorBlinkAnimation=true + +# Show or not sessions choose label +showSessionsByDefault=false +# Font size of sessions choose label (in points). +sessionsFontSize=24 + +# Show or not users choose label +showUsersByDefault=false +# Font size of users choose label (in points) +usersFontSize=48 +# Show user real name on label by default +showUserRealNameByDefault=true + +# Path to background image +background= +# Or use just one color +backgroundFill=#000000 +# Fill mode for image background +# Value must be on of: aspect, fill, tile, pad +backgroundFillMode=aspect + +# Default text color for all labels +basicTextColor=#ffffff + +# Blur radius for background image +blurRadius=0 + +# Hide cursor +hideCursor=false + +# Default font +font=monospace + +# Font of help message +helpFont=monospace +# Font size of help message (in points) +helpFontSize=18 diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..9578cf7 --- /dev/null +++ b/utils.py @@ -0,0 +1,83 @@ +import os +import shutil +import sys + + +RED = "\033[1;31m" +GREEN = "\033[1;32m" +YELLOW = "\033[1;33m" +CYAN = "\033[1;36m" +RESET = "\033[0m" + + +def log_info(msg): print(f"{CYAN}[*]{RESET} {msg}") +def log_success(msg): print(f"{GREEN}[✓]{RESET} {msg}") +def log_warn(msg): print(f"{YELLOW}[!]{RESET} {msg}") +def log_error(msg): print(f"{RED}[✗]{RESET} {msg}") + + +def ask_override(path): + resp = input(f"{YELLOW}'{path}' already exists. Override? (Y/N): {RESET}").strip().lower() + return resp == 'y' + + +def choice_files(src_root, dst_root, choices_map, override_all=False): + for final_name, options in choices_map.items(): + existing_options = [ + f for f in options + if os.path.exists(os.path.join(src_root, f.lstrip('/'))) + ] + if not existing_options: + log_warn(f"No source file found among options for {final_name}. Skipping.") + continue + + if len(existing_options) == 1: + chosen = existing_options[0] + else: + log_info(f"Choose which file to copy as '{final_name}':") + for idx, opt in enumerate(existing_options, 1): + print(f"{idx}: {opt}") + while True: + try: + choice_idx = int(input(f"{CYAN}Enter number (1-{len(existing_options)}): {RESET}")) + if 1 <= choice_idx <= len(existing_options): + chosen = existing_options[choice_idx - 1] + break + except ValueError: + pass + log_warn("Invalid choice, try again.") + + src_file = os.path.join(src_root, chosen.lstrip('/')) + dst_file = os.path.join(dst_root, final_name.lstrip('/')) + + if os.path.exists(dst_file): + if not override_all and not ask_override(dst_file): + log_warn(f"Skipped overriding {dst_file}") + continue + + shutil.copy2(src_file, dst_file) + log_success(f"Copied {chosen} -> {final_name}") + +def copy_with_structure(src_root, dst_root, override_all=False): + for root, dirs, files in os.walk(src_root): + rel_path = os.path.relpath(root, src_root) + target_dir = os.path.join(dst_root, rel_path) if rel_path != '.' else dst_root + + os.makedirs(target_dir, exist_ok=True) + + for file in files: + src_file = os.path.join(root, file) + dst_file = os.path.join(target_dir, file) + + if os.path.exists(dst_file): + if not override_all and not ask_override(dst_file): + log_warn(f"Skipped overriding {dst_file}") + continue + + shutil.copy2(src_file, dst_file) + log_success(f"Copied: {src_file} -> {dst_file}") + +def require_root(): + if os.geteuid() != 0: + log_error("This script must be run as root (sudo). Exiting.") + sys.exit(1)