More
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
172
build.sh
172
build.sh
@@ -1,76 +1,63 @@
|
||||
#!/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..."
|
||||
# === Functions ===
|
||||
update_system() {
|
||||
log_info "Updating system..."
|
||||
pacman -Syu --noconfirm
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
log_info "Installing packages..."
|
||||
pacman -S --noconfirm --needed \
|
||||
$ESSENTIAL_PACKAGES \
|
||||
$DISPLAY_MANAGER \
|
||||
@@ -78,38 +65,77 @@ pacman -S --noconfirm --needed \
|
||||
$AUDIO_PACKAGES \
|
||||
$FONTS_AND_THEME \
|
||||
$MEDIA_PACKAGES
|
||||
}
|
||||
|
||||
# === Enable display manager ===
|
||||
echo "Enabling SDDM..."
|
||||
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"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
enable_sddm() {
|
||||
log_info "Enabling SDDM..."
|
||||
systemctl enable sddm
|
||||
# === Apply GTK settings for theme and color scheme ===
|
||||
echo "Applying GTK settings..."
|
||||
}
|
||||
|
||||
# Extract DBus address from the user's environment
|
||||
USER_DBUS_ENV=$(sudo -u "$ORIG_USER" -- dbus-launch)
|
||||
eval "$USER_DBUS_ENV"
|
||||
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
|
||||
|
||||
export DBUS_SESSION_BUS_ADDRESS
|
||||
export XDG_RUNTIME_DIR="/run/user/$(id -u $ORIG_USER)"
|
||||
sudo -u "$ORIG_USER" mkdir -p "$ORIG_HOME/Repositories"
|
||||
cd "$ORIG_HOME/Repositories"
|
||||
|
||||
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"
|
||||
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
|
||||
|
||||
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"
|
||||
cd "$ORIG_HOME/Repositories/where-is-my-sddm-theme"
|
||||
sudo -u "$ORIG_USER" makepkg -si --noconfirm
|
||||
}
|
||||
|
||||
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"
|
||||
run_dotfiles_setup() {
|
||||
log_info "Running dotfiles setup..."
|
||||
cd "$ORIG_PWD"
|
||||
|
||||
echo "GTK settings applied successfully."
|
||||
[ -f "setup_user.py" ] && sudo -u "$ORIG_USER" HOME="$ORIG_HOME" python3 setup_user.py \
|
||||
|| log_warn "setup_user.py not found."
|
||||
|
||||
# === 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"
|
||||
[ -f "setup_system.py" ] && python3 setup_system.py \
|
||||
|| log_warn "setup_system.py not found."
|
||||
|
||||
echo "Dotfiles installed successfully."
|
||||
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."
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
25
home/Documents/bookmarks.html
Normal file
25
home/Documents/bookmarks.html
Normal file
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 27 MiB After Width: | Height: | Size: 27 MiB |
77
setup.py
77
setup.py
@@ -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()
|
||||
17
setup_system.py
Normal file
17
setup_system.py
Normal file
@@ -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()
|
||||
22
setup_user.py
Normal file
22
setup_user.py
Normal file
@@ -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()
|
||||
139
system_files/usr/lib/sddm/sddm.conf.d/default.conf
Normal file
139
system_files/usr/lib/sddm/sddm.conf.d/default.conf
Normal file
@@ -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 <username>.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
|
||||
|
||||
|
||||
@@ -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
|
||||
83
utils.py
Normal file
83
utils.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user