diff --git a/src/scripts/dev.sh b/src/scripts/dev.sh index dabd9ff..c969cdb 100644 --- a/src/scripts/dev.sh +++ b/src/scripts/dev.sh @@ -6,6 +6,10 @@ source "$SCRIPT_DIR/utils.sh" update_apt_cache +dotfiles_root="$PROJECT_ROOT/src/dotfiles" +dotfiles_config_root="$dotfiles_root/config" +dotfiles_home_root="$dotfiles_root/home" + # Node.js + npm from NodeSource (https://github.com/nodesource/distributions) NODE_MAJOR=24 nodesource_key="/etc/apt/keyrings/nodesource.gpg" @@ -84,10 +88,10 @@ neovim_packages=( install_apt_packages "${neovim_packages[@]}" # Dotfiles: XDG app configs (Neovim lazy.nvim bootstraps in config/nvim/init.lua — no Packer) -dotfiles_config_root="$PROJECT_ROOT/src/dotfiles/config" xdg_config_home="${XDG_CONFIG_HOME:-$HOME/.config}" ensure_directory "$xdg_config_home" if [[ -d "$dotfiles_config_root" ]]; then + shopt -s dotglob nullglob for entry in "$dotfiles_config_root"/*; do [[ -e "$entry" ]] || continue name=$(basename "$entry") @@ -96,6 +100,7 @@ if [[ -d "$dotfiles_config_root" ]]; then cp -r "$entry" "$dest" 2>>"$ERROR_LOG_FILE" || true fi done + shopt -u dotglob nullglob fi dev_tools=( @@ -129,7 +134,7 @@ if [[ ! -f "$HOME/.gitconfig" ]]; then fi vim_config_file="$HOME/.vimrc" -vim_source_file="$PROJECT_ROOT/src/dotfiles/home/.vimrc" +vim_source_file="$dotfiles_home_root/.vimrc" if [[ ! -f "$vim_config_file" && -f "$vim_source_file" ]]; then -copy_file_safe "$vim_source_file" "$vim_config_file" + copy_file_safe "$vim_source_file" "$vim_config_file" fi diff --git a/src/scripts/security.sh b/src/scripts/security.sh index f75b769..4f62834 100644 --- a/src/scripts/security.sh +++ b/src/scripts/security.sh @@ -34,13 +34,88 @@ if [[ -f "$protonvpn_deb" ]] && [[ -s "$protonvpn_deb" ]]; then fi fi +# Proton Pass desktop .deb — canonical URLs live in version.json (linked from +# https://www.proton.me/support/set-up-proton-pass-linux). Legacy ProtonPass.deb +# URLs often redirect or no longer serve the package bytes reliably. proton_pass_deb="$TEMP_DIR/proton-pass.deb" -download_file_safe "https://proton.me/download/PassDesktop/linux/x64/ProtonPass.deb" "$proton_pass_deb" -if [[ -f "$proton_pass_deb" ]] && [[ -s "$proton_pass_deb" ]]; then - if file "$proton_pass_deb" 2>/dev/null | grep -q "Debian binary"; then - sudo dpkg -i "$proton_pass_deb" 2>>"$ERROR_LOG_FILE" || true - sudo apt-get install -f -y 2>>"$ERROR_LOG_FILE" || true +proton_pass_version_json_urls=( + "https://www.proton.me/download/PassDesktop/linux/x64/version.json" + "https://proton.me/download/PassDesktop/linux/x64/version.json" +) + +proton_pass_is_valid_deb() { + local candidate="$1" + [[ -s "$candidate" ]] || return 1 + if command -v dpkg-deb >/dev/null 2>&1 && dpkg-deb -I "$candidate" >/dev/null 2>&1; then + return 0 fi + # Fallback when dpkg-deb is unavailable: Debian packages are usually ar(5) archives. + [[ "$(head -c 8 "$candidate" 2>/dev/null | tr -d '\0')" == $'!\n' ]] || \ + [[ "$(head -c 7 "$candidate" 2>/dev/null | tr -d '\0')" == '!' ]] +} + +proton_pass_resolve_latest_stable_deb_url() { + local json_path="$TEMP_DIR/proton-pass-version.json" + local base_url deb_url="" + for base_url in "${proton_pass_version_json_urls[@]}"; do + rm -f "$json_path" 2>/dev/null || true + if ! curl -fsSL --connect-timeout 30 --max-time 120 --retry 3 --retry-delay 2 \ + -A "Mozilla/5.0 (X11; Linux x86_64)" \ + "$base_url" -o "$json_path" 2>>"$ERROR_LOG_FILE"; then + continue + fi + [[ -s "$json_path" ]] || continue + if command -v python3 >/dev/null 2>&1; then + deb_url=$(python3 -c ' +import json, sys +path = sys.argv[1] +with open(path, encoding="utf-8") as fp: + data = json.load(fp) +for rel in data.get("Releases", []): + if rel.get("CategoryName") != "Stable": + continue + for item in rel.get("File", []): + url = (item.get("Url") or "").strip() + if not url.endswith(".deb"): + continue + ident = item.get("Identifier") or "" + if "Debian" in ident or ident.startswith(".deb"): + print(url) + sys.exit(0) +sys.exit(1) +' "$json_path" 2>>"$ERROR_LOG_FILE") || deb_url="" + [[ -n "$deb_url" ]] && printf '%s' "$deb_url" && return 0 + fi + done + return 1 +} + +proton_pass_urls=() +if resolved=$(proton_pass_resolve_latest_stable_deb_url); then + proton_pass_urls+=("$resolved") +fi +proton_pass_urls+=( + "https://proton.me/download/PassDesktop/linux/x64/ProtonPass.deb" + "https://www.proton.me/download/PassDesktop/linux/x64/ProtonPass.deb" +) + +proton_pass_downloaded=0 +for proton_pass_url in "${proton_pass_urls[@]}"; do + [[ -n "$proton_pass_url" ]] || continue + rm -f "$proton_pass_deb" 2>/dev/null || true + if curl -fsSL --connect-timeout 30 --max-time 600 --retry 3 --retry-delay 2 --retry-all-errors \ + -A "Mozilla/5.0 (X11; Linux x86_64)" \ + "$proton_pass_url" -o "$proton_pass_deb" 2>>"$ERROR_LOG_FILE" && proton_pass_is_valid_deb "$proton_pass_deb"; then + proton_pass_downloaded=1 + break + fi +done + +if [[ "$proton_pass_downloaded" -eq 1 ]]; then + sudo dpkg -i "$proton_pass_deb" 2>>"$ERROR_LOG_FILE" || true + sudo apt-get install -f -y 2>>"$ERROR_LOG_FILE" || true +else + log_error "Failed to download a valid Proton Pass Debian package" fi proton_pass_cli="$TEMP_DIR/proton-pass-cli" diff --git a/src/scripts/shell.sh b/src/scripts/shell.sh index 077bc12..110a030 100644 --- a/src/scripts/shell.sh +++ b/src/scripts/shell.sh @@ -6,6 +6,9 @@ source "$SCRIPT_DIR/utils.sh" update_apt_cache +dotfiles_root="$PROJECT_ROOT/src/dotfiles" +dotfiles_home_root="$dotfiles_root/home" + shell_packages=( "zsh" "tmux" @@ -76,22 +79,32 @@ fi # Ghostty and other XDG configs under ~/.config are installed from src/dotfiles/config/ in dev.sh tmux_config_file="$HOME/.tmux.conf" -tmux_source_file="$PROJECT_ROOT/src/dotfiles/home/.tmux.conf" +tmux_source_file="$dotfiles_home_root/.tmux.conf" if [[ ! -f "$tmux_config_file" && -f "$tmux_source_file" ]]; then copy_file_safe "$tmux_source_file" "$tmux_config_file" fi zsh_config_file="$HOME/.zshrc" -zsh_source_file="$PROJECT_ROOT/src/dotfiles/home/.zshrc" +zsh_source_file="$dotfiles_home_root/.zshrc" if [[ ! -f "$zsh_config_file" && -f "$zsh_source_file" ]]; then copy_file_safe "$zsh_source_file" "$zsh_config_file" - printf '%s\n' "$PROJECT_ROOT/src/dotfiles" >"$HOME/.dotfiles_path" 2>>"$ERROR_LOG_FILE" || true +fi + +# Keep the zsh DOTFILES cache in sync so home/.zshrc can source home/zsh/.zsh. +if [[ -d "$dotfiles_home_root/zsh" ]]; then + current_dotfiles_path="" + if [[ -f "$HOME/.dotfiles_path" ]]; then + IFS= read -r current_dotfiles_path <"$HOME/.dotfiles_path" || true + fi + if [[ "$current_dotfiles_path" != "$dotfiles_root" ]]; then + printf '%s\n' "$dotfiles_root" >"$HOME/.dotfiles_path" 2>>"$ERROR_LOG_FILE" || true + fi fi bashrc_config_file="$HOME/.bashrc" -bashrc_source_file="$PROJECT_ROOT/src/dotfiles/home/.bashrc" +bashrc_source_file="$dotfiles_home_root/.bashrc" if [[ ! -f "$bashrc_config_file" && -f "$bashrc_source_file" ]]; then copy_file_safe "$bashrc_source_file" "$bashrc_config_file" fi