This commit is contained in:
2025-08-09 14:41:44 +02:00
parent b57943ee4b
commit d23df8df84
25 changed files with 463 additions and 159 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
__pycache__/

172
build.sh
View File

@@ -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."

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 27 MiB

After

Width:  |  Height:  |  Size: 27 MiB

View File

@@ -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
View 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
View 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()

View 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

View File

@@ -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
View 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)