From e50d9d23aa307a07da68f0b8598812abcd1e00a4 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Fri, 1 May 2026 15:59:20 +0200 Subject: [PATCH 01/15] Document bash error handling in README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 779604ba..8517b944 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,18 @@ All scripts use `curl -fsSL "$REPO_BASE_URL/..."` to reference other files in th - **Orchestration scripts** default `REPO_BASE_URL` to the GitHub raw URL and export it so child scripts inherit it. - **Child scripts** expect `REPO_BASE_URL` to be set in the environment. - For local testing, `export REPO_BASE_URL="file://$PWD"` makes `curl` read from the local filesystem instead. + +### Error handling + +All scripts use `set -euo pipefail`. + +- `-e`: Exit immediately if a command fails. +- `-u`: Using an unset variable yields an error. +- `-o pipefail`: Pipes return the error code of the first failure. + +### TODO + +- Directory structure too complex, i.e. do we need separate install uninstall dirs? +- Should we prefix all env vars with `PS_`? + * Use lowercase for local script variables? +- Should we merge/warn/overwrite `settings.json` in config installer? From ab158c909bc884d465a0c7498dba4dd8144b47d9 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Fri, 1 May 2026 16:04:02 +0200 Subject: [PATCH 02/15] Fix signal trap --- Core/Conda/install/install_macOS.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Conda/install/install_macOS.sh b/Core/Conda/install/install_macOS.sh index 92101ccc..322e55a1 100755 --- a/Core/Conda/install/install_macOS.sh +++ b/Core/Conda/install/install_macOS.sh @@ -25,7 +25,7 @@ if [ -d "$INSTALL_DIR" ] && [ -x "$INSTALL_DIR/bin/conda" ]; then else # Download TMPDIR_PATH="$(mktemp -d)" - trap 'rm -rf "$TMPDIR_PATH"' EXIT + trap "rm -rf '$TMPDIR_PATH'" EXIT KILL INT echo " Downloading ${INSTALLER_NAME}..." curl -fSL "${BASE_URL}/${INSTALLER_NAME}" -o "$TMPDIR_PATH/${INSTALLER_NAME}" From 141ddc6e01c25e1d7e57ddfef8b0bb2b24563ce7 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Fri, 1 May 2026 16:04:43 +0200 Subject: [PATCH 03/15] Misc macos installer changes --- Core/Conda/install/install_macOS.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/Conda/install/install_macOS.sh b/Core/Conda/install/install_macOS.sh index 322e55a1..3318b0ff 100755 --- a/Core/Conda/install/install_macOS.sh +++ b/Core/Conda/install/install_macOS.sh @@ -33,13 +33,17 @@ else # Run installer in batch mode (no prompts, no PATH modification) echo " Running installer..." + # Installer options: + # '-b': run non interactively + # '-u': update an existing installation if there is one + # '-c': don't modify shell rc files bash "$TMPDIR_PATH/${INSTALLER_NAME}" -buc -p "$INSTALL_DIR" echo " [OK] Miniforge installed to $INSTALL_DIR" fi # Load conda shell functions and activate the base environment echo " Initializing conda..." -. "${INSTALL_DIR}/etc/profile.d/conda.sh" && conda activate "${INSTALL_DIR}" +source "${INSTALL_DIR}/etc/profile.d/conda.sh" && conda activate "${INSTALL_DIR}" # Initialize conda for all supported shells on this machine conda init --all From 38a541eeecbaec8c5f41ac51c6dfa76420aa9cc7 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Fri, 1 May 2026 16:11:23 +0200 Subject: [PATCH 04/15] Document extensions file --- Core/VsCode/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/VsCode/README.md b/Core/VsCode/README.md index d61fd4dc..23162951 100644 --- a/Core/VsCode/README.md +++ b/Core/VsCode/README.md @@ -22,3 +22,7 @@ curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Ut - Disable chat agent - Set Python locator to JS - Disable telemetry + +## Extensions + +Listed and commented in `config/extensions.txt`. From c887e39e124e5ed69111d0499a20eec5cb8d4e4e Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Fri, 1 May 2026 16:16:56 +0200 Subject: [PATCH 05/15] More robust VSC executable detection --- Core/VsCode/config/extensions_macOS.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Core/VsCode/config/extensions_macOS.sh b/Core/VsCode/config/extensions_macOS.sh index 6dcc5c86..2e5b8c4f 100755 --- a/Core/VsCode/config/extensions_macOS.sh +++ b/Core/VsCode/config/extensions_macOS.sh @@ -10,14 +10,20 @@ set -euo pipefail -CODE_CLI="/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" +STD_CODE_CLI="/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" echo "=== Installing VS Code Extensions ===" echo "" -if [ ! -x "$CODE_CLI" ]; then - echo " ERROR: VS Code not found at $CODE_CLI" - exit 1 +if [ ! -x "$STD_CODE_CLI" ]; then + CODE_CLI=$(command -v code 2>/dev/null || true) + + if [ ! -x "$CODE_CLI" ]; then + echo " ERROR: VS Code not found at $STD_CODE_CLI" + exit 1 + fi +else + CODE_CLI="$STD_CODE_CLI" fi while IFS= read -r line || [ -n "$line" ]; do From e038c85a9f72b0d41bb3f8d383ff5a8ce5182816 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Fri, 1 May 2026 16:41:05 +0200 Subject: [PATCH 06/15] Don't overwrite settings.json if it already exists --- Core/VsCode/config/settings_macOS.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/VsCode/config/settings_macOS.sh b/Core/VsCode/config/settings_macOS.sh index 1b9ff6f1..7484da44 100755 --- a/Core/VsCode/config/settings_macOS.sh +++ b/Core/VsCode/config/settings_macOS.sh @@ -16,6 +16,11 @@ SETTINGS_FILE="$SETTINGS_DIR/settings.json" echo "=== Applying VS Code Settings ===" echo "" +if [[ -e "$SETTINGS_FILE" ]]; then + echo " [ERROR] $SETTINGS_FILE already exists. Aborting to avoid overwrite." >&2 + exit 1 +fi + mkdir -p "$SETTINGS_DIR" curl -fsSL "$REPO_BASE_URL/Core/VsCode/config/default_settings_MacOS.json" > "$SETTINGS_FILE" echo " [OK] Settings applied to $SETTINGS_FILE" From aaef9053592f7435e0b15ab29260c193be88d425 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Mon, 4 May 2026 14:24:21 +0200 Subject: [PATCH 07/15] Improve varname consistency - REPO_BASE_URL -> PS_REPO_URL - All script-local variables are lowercase --- Core/Conda/install/install_macOS.sh | 26 ++++++++++----------- Core/Orchestration/install_all_macOS.sh | 8 +++---- Core/Orchestration/uninstall_all_macOS.sh | 8 +++---- Core/VsCode/config/extensions_macOS.sh | 16 ++++++------- Core/VsCode/config/extensions_windows.ps1 | 2 +- Core/VsCode/config/settings_macOS.sh | 14 ++++++------ Core/VsCode/install/install_macOS.sh | 28 +++++++++++------------ Core/VsCode/install/install_windows.ps1 | 4 ++-- README.md | 15 +++++------- Utils/Conda/uninstall_macOS.sh | 22 +++++++++--------- Utils/Python/uninstall_macOS.sh | 18 +++++++-------- Utils/VsCode/uninstall_macOS.sh | 24 +++++++++---------- 12 files changed, 91 insertions(+), 94 deletions(-) diff --git a/Core/Conda/install/install_macOS.sh b/Core/Conda/install/install_macOS.sh index 3318b0ff..49a401be 100755 --- a/Core/Conda/install/install_macOS.sh +++ b/Core/Conda/install/install_macOS.sh @@ -10,25 +10,25 @@ set -euo pipefail -BASE_URL="https://github.com/philipnickel/miniforge-PIS/releases/latest/download" -ARCH="$(uname -m)" -INSTALLER_NAME="Miniforge3-MacOSX-${ARCH}.sh" -INSTALL_DIR="$HOME/miniforge3-dtu" +base_url="https://github.com/philipnickel/miniforge-PIS/releases/latest/download" +arch="$(uname -m)" +installer_name="Miniforge3-MacOSX-${arch}.sh" +install_dir="$HOME/miniforge3-dtu" echo "=== Installing Miniforge ===" echo "" # Check if already installed -if [ -d "$INSTALL_DIR" ] && [ -x "$INSTALL_DIR/bin/conda" ]; then - echo " Miniforge is already installed at $INSTALL_DIR" +if [ -d "$install_dir" ] && [ -x "$install_dir/bin/conda" ]; then + echo " Miniforge is already installed at $install_dir" echo " [OK] Skipping download" else # Download - TMPDIR_PATH="$(mktemp -d)" - trap "rm -rf '$TMPDIR_PATH'" EXIT KILL INT + tmpdir_path="$(mktemp -d)" + trap "rm -rf '$tmpdir_path'" EXIT KILL INT - echo " Downloading ${INSTALLER_NAME}..." - curl -fSL "${BASE_URL}/${INSTALLER_NAME}" -o "$TMPDIR_PATH/${INSTALLER_NAME}" + echo " Downloading ${installer_name}..." + curl -fSL "${base_url}/${installer_name}" -o "$tmpdir_path/${installer_name}" echo " [OK] Download complete" # Run installer in batch mode (no prompts, no PATH modification) @@ -37,13 +37,13 @@ else # '-b': run non interactively # '-u': update an existing installation if there is one # '-c': don't modify shell rc files - bash "$TMPDIR_PATH/${INSTALLER_NAME}" -buc -p "$INSTALL_DIR" - echo " [OK] Miniforge installed to $INSTALL_DIR" + bash "$tmpdir_path/${installer_name}" -buc -p "$install_dir" + echo " [OK] Miniforge installed to $install_dir" fi # Load conda shell functions and activate the base environment echo " Initializing conda..." -source "${INSTALL_DIR}/etc/profile.d/conda.sh" && conda activate "${INSTALL_DIR}" +source "${install_dir}/etc/profile.d/conda.sh" && conda activate "${install_dir}" # Initialize conda for all supported shells on this machine conda init --all diff --git a/Core/Orchestration/install_all_macOS.sh b/Core/Orchestration/install_all_macOS.sh index 4acf88e2..4d0c43e6 100755 --- a/Core/Orchestration/install_all_macOS.sh +++ b/Core/Orchestration/install_all_macOS.sh @@ -10,8 +10,8 @@ set -euo pipefail -REPO_BASE_URL="${REPO_BASE_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev}" -export REPO_BASE_URL +PS_REPO_URL="${PS_REPO_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev}" +export PS_REPO_URL echo "=========================================" echo " DTU Python Support - Full Installation" @@ -20,12 +20,12 @@ echo "" # Step 1: Install Miniforge/Conda echo "--- Step 1/2: Miniforge ---" -bash <(curl -fsSL "$REPO_BASE_URL/Core/Conda/install/install_macOS.sh") +bash <(curl -fsSL "$PS_REPO_URL/Core/Conda/install/install_macOS.sh") echo "" # Step 2: Install VS Code (includes extensions and settings) echo "--- Step 2/2: VS Code ---" -bash <(curl -fsSL "$REPO_BASE_URL/Core/VsCode/install/install_macOS.sh") +bash <(curl -fsSL "$PS_REPO_URL/Core/VsCode/install/install_macOS.sh") echo "" echo "=========================================" diff --git a/Core/Orchestration/uninstall_all_macOS.sh b/Core/Orchestration/uninstall_all_macOS.sh index 8947c706..e5c3b441 100755 --- a/Core/Orchestration/uninstall_all_macOS.sh +++ b/Core/Orchestration/uninstall_all_macOS.sh @@ -10,8 +10,8 @@ set -euo pipefail -REPO_BASE_URL="${REPO_BASE_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev}" -export REPO_BASE_URL +PS_REPO_URL="${PS_REPO_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev}" +export PS_REPO_URL echo "=========================================" echo " DTU Python Support - Full Uninstall" @@ -20,12 +20,12 @@ echo "" # Step 1: Uninstall VS Code echo "--- Step 1/2: VS Code ---" -bash <(curl -fsSL "$REPO_BASE_URL/Utils/VsCode/uninstall_macOS.sh") +bash <(curl -fsSL "$PS_REPO_URL/Utils/VsCode/uninstall_macOS.sh") echo "" # Step 2: Uninstall Conda echo "--- Step 2/2: Conda ---" -bash <(curl -fsSL "$REPO_BASE_URL/Utils/Conda/uninstall_macOS.sh") +bash <(curl -fsSL "$PS_REPO_URL/Utils/Conda/uninstall_macOS.sh") echo "" echo "=========================================" diff --git a/Core/VsCode/config/extensions_macOS.sh b/Core/VsCode/config/extensions_macOS.sh index 2e5b8c4f..4446284e 100755 --- a/Core/VsCode/config/extensions_macOS.sh +++ b/Core/VsCode/config/extensions_macOS.sh @@ -10,31 +10,31 @@ set -euo pipefail -STD_CODE_CLI="/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" +std_code_cli="/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" echo "=== Installing VS Code Extensions ===" echo "" -if [ ! -x "$STD_CODE_CLI" ]; then - CODE_CLI=$(command -v code 2>/dev/null || true) +if [ ! -x "$std_code_cli" ]; then + code_cli=$(command -v code 2>/dev/null || true) - if [ ! -x "$CODE_CLI" ]; then - echo " ERROR: VS Code not found at $STD_CODE_CLI" + if [ ! -x "$code_cli" ]; then + echo " ERROR: VS Code not found at $std_code_cli" exit 1 fi else - CODE_CLI="$STD_CODE_CLI" + code_cli="$std_code_cli" fi while IFS= read -r line || [ -n "$line" ]; do [[ -z "$line" || "$line" == \#* ]] && continue - if "$CODE_CLI" --install-extension "$line" --force 2>/dev/null; then + if "$code_cli" --install-extension "$line" --force 2>/dev/null; then echo " [OK] $line" else echo " [FAIL] $line" fi -done < <(curl -fsSL "$REPO_BASE_URL/Core/VsCode/config/extensions.txt") +done < <(curl -fsSL "$PS_REPO_URL/Core/VsCode/config/extensions.txt") echo "" echo "=== Extensions complete! ===" diff --git a/Core/VsCode/config/extensions_windows.ps1 b/Core/VsCode/config/extensions_windows.ps1 index 700311f5..0752290d 100644 --- a/Core/VsCode/config/extensions_windows.ps1 +++ b/Core/VsCode/config/extensions_windows.ps1 @@ -13,7 +13,7 @@ $codeCli = "$env:LOCALAPPDATA\Programs\Microsoft VS Code\bin\code.cmd" Write-Host "=== Installing VS Code Extensions ===`n" -$extensionsUrl = "$env:REPO_BASE_URL/Core/VsCode/config/extensions.txt" +$extensionsUrl = "$env:PS_REPO_URL/Core/VsCode/config/extensions.txt" $lines = (Invoke-WebRequest -Uri $extensionsUrl -UseBasicParsing).Content -split "`n" foreach ($line in $lines) { diff --git a/Core/VsCode/config/settings_macOS.sh b/Core/VsCode/config/settings_macOS.sh index 7484da44..b92084b5 100755 --- a/Core/VsCode/config/settings_macOS.sh +++ b/Core/VsCode/config/settings_macOS.sh @@ -10,20 +10,20 @@ set -euo pipefail -SETTINGS_DIR="$HOME/Library/Application Support/Code/User" -SETTINGS_FILE="$SETTINGS_DIR/settings.json" +settings_dir="$HOME/Library/Application Support/Code/User" +settings_file="$settings_dir/settings.json" echo "=== Applying VS Code Settings ===" echo "" -if [[ -e "$SETTINGS_FILE" ]]; then - echo " [ERROR] $SETTINGS_FILE already exists. Aborting to avoid overwrite." >&2 +if [[ -e "$settings_file" ]]; then + echo " [ERROR] $settings_file already exists. Aborting to avoid overwrite." >&2 exit 1 fi -mkdir -p "$SETTINGS_DIR" -curl -fsSL "$REPO_BASE_URL/Core/VsCode/config/default_settings_MacOS.json" > "$SETTINGS_FILE" -echo " [OK] Settings applied to $SETTINGS_FILE" +mkdir -p "$settings_dir" +curl -fsSL "$PS_REPO_URL/Core/VsCode/config/default_settings_MacOS.json" > "$settings_file" +echo " [OK] Settings applied to $settings_file" echo "" echo "=== VS Code settings complete! ===" diff --git a/Core/VsCode/install/install_macOS.sh b/Core/VsCode/install/install_macOS.sh index 423d9407..f730c7bb 100755 --- a/Core/VsCode/install/install_macOS.sh +++ b/Core/VsCode/install/install_macOS.sh @@ -10,51 +10,51 @@ set -euo pipefail -APP_PATH="/Applications/Visual Studio Code.app" -DOWNLOAD_URL="https://update.code.visualstudio.com/latest/darwin-universal/stable" +app_path="/Applications/Visual Studio Code.app" +download_url="https://update.code.visualstudio.com/latest/darwin-universal/stable" echo "=== Installing VS Code ===" echo "" # Check if already installed -if command -v code &>/dev/null || [ -d "$APP_PATH" ]; then +if command -v code &>/dev/null || [ -d "$app_path" ]; then echo " VS Code is already installed." echo " [OK] Skipping download" else # Download - TMPDIR_PATH="$(mktemp -d)" - trap 'rm -rf "$TMPDIR_PATH"' EXIT + tmpdir_path="$(mktemp -d)" + trap 'rm -rf "$tmpdir_path"' EXIT echo " Downloading VS Code..." - curl -fSL "$DOWNLOAD_URL" -o "$TMPDIR_PATH/VSCode.zip" + curl -fSL "$download_url" -o "$tmpdir_path/VSCode.zip" echo " [OK] Download complete" # Extract echo " Extracting..." - unzip -q "$TMPDIR_PATH/VSCode.zip" -d "$TMPDIR_PATH/vscode_extracted" + unzip -q "$tmpdir_path/VSCode.zip" -d "$tmpdir_path/vscode_extracted" echo " [OK] Extraction complete" # Remove existing installation if present - if [ -d "$APP_PATH" ]; then + if [ -d "$app_path" ]; then echo " Removing existing installation..." - if [ -w "$APP_PATH" ]; then - rm -rf "$APP_PATH" + if [ -w "$app_path" ]; then + rm -rf "$app_path" else - sudo rm -rf "$APP_PATH" + sudo rm -rf "$app_path" fi fi # Move to /Applications echo " Moving to /Applications..." - mv "$TMPDIR_PATH/vscode_extracted/Visual Studio Code.app" "$APP_PATH" + mv "$tmpdir_path/vscode_extracted/Visual Studio Code.app" "$app_path" echo " [OK] VS Code installed" fi # Apply settings -bash <(curl -fsSL "$REPO_BASE_URL/Core/VsCode/config/settings_macOS.sh") +bash <(curl -fsSL "$PS_REPO_URL/Core/VsCode/config/settings_macOS.sh") # Install extensions -bash <(curl -fsSL "$REPO_BASE_URL/Core/VsCode/config/extensions_macOS.sh") +bash <(curl -fsSL "$PS_REPO_URL/Core/VsCode/config/extensions_macOS.sh") echo "" echo "=== VS Code installation complete! ===" diff --git a/Core/VsCode/install/install_windows.ps1 b/Core/VsCode/install/install_windows.ps1 index 68ba1ddc..723136ed 100644 --- a/Core/VsCode/install/install_windows.ps1 +++ b/Core/VsCode/install/install_windows.ps1 @@ -45,9 +45,9 @@ if (Get-Command code -ErrorAction SilentlyContinue -or (Test-Path $AppPath)) { } # Apply settings -Invoke-Expression (Invoke-WebRequest -Uri "$REPO_BASE_URL/Core/VsCode/config/settings_windows.ps1" -UseBasicParsing).Content +Invoke-Expression (Invoke-WebRequest -Uri "$PS_REPO_URL/Core/VsCode/config/settings_windows.ps1" -UseBasicParsing).Content # Install extensions -Invoke-Expression (Invoke-WebRequest -Uri "$REPO_BASE_URL/Core/VsCode/config/extensions_windows.ps1" -UseBasicParsing).Content +Invoke-Expression (Invoke-WebRequest -Uri "$PS_REPO_URL/Core/VsCode/config/extensions_windows.ps1" -UseBasicParsing).Content Write-Host "`n=== VS Code installation complete! ===" diff --git a/README.md b/README.md index 8517b944..36eac07e 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev/Cor ### Local development -Set `REPO_BASE_URL` to your local repo and run any script directly: +Set `PS_REPO_URL` to your local repo and run any script directly: ```bash -export REPO_BASE_URL="file://$PWD" +export PS_REPO_URL="file://$PWD" bash Core/Orchestration/install_all_macOS.sh bash Core/VsCode/config/settings_macOS.sh bash Core/VsCode/config/extensions_macOS.sh @@ -25,11 +25,11 @@ bash Core/VsCode/config/extensions_macOS.sh ### How it works -All scripts use `curl -fsSL "$REPO_BASE_URL/..."` to reference other files in the repo. +All scripts use `curl -fsSL "$PS_REPO_URL/..."` to reference other files in the repo. -- **Orchestration scripts** default `REPO_BASE_URL` to the GitHub raw URL and export it so child scripts inherit it. -- **Child scripts** expect `REPO_BASE_URL` to be set in the environment. -- For local testing, `export REPO_BASE_URL="file://$PWD"` makes `curl` read from the local filesystem instead. +- **Orchestration scripts** default `PS_REPO_URL` to the GitHub raw URL and export it so child scripts inherit it. +- **Child scripts** expect `PS_REPO_URL` to be set in the environment. +- For local testing, `export PS_REPO_URL="file://$PWD"` makes `curl` read from the local filesystem instead. ### Error handling @@ -42,6 +42,3 @@ All scripts use `set -euo pipefail`. ### TODO - Directory structure too complex, i.e. do we need separate install uninstall dirs? -- Should we prefix all env vars with `PS_`? - * Use lowercase for local script variables? -- Should we merge/warn/overwrite `settings.json` in config installer? diff --git a/Utils/Conda/uninstall_macOS.sh b/Utils/Conda/uninstall_macOS.sh index 6ce90aa0..563f4aa3 100755 --- a/Utils/Conda/uninstall_macOS.sh +++ b/Utils/Conda/uninstall_macOS.sh @@ -16,30 +16,30 @@ echo "" removed_something=false # Find conda base prefix -CONDA_BASE="" +conda_base="" # Try conda command first if command -v conda &>/dev/null; then - CONDA_BASE="$(conda info --base 2>/dev/null || true)" + conda_base="$(conda info --base 2>/dev/null || true)" fi # Fallback: check common install locations -if [ -z "$CONDA_BASE" ] || [ ! -d "$CONDA_BASE" ]; then +if [ -z "$conda_base" ] || [ ! -d "$conda_base" ]; then for candidate in "$HOME/miniforge3" "$HOME/miniconda3" "$HOME/anaconda3"; do if [ -d "$candidate" ]; then - CONDA_BASE="$candidate" + conda_base="$candidate" break fi done fi -if [ -z "$CONDA_BASE" ]; then +if [ -z "$conda_base" ]; then echo " conda not found" else - echo " Found conda at: $CONDA_BASE" + echo " Found conda at: $conda_base" # Safety check: refuse to delete home or root - resolved_path="$(cd "$CONDA_BASE" && pwd)" + resolved_path="$(cd "$conda_base" && pwd)" if [ "$resolved_path" = "/" ] || [ "$resolved_path" = "$HOME" ]; then echo " ERROR: Refusing to delete unsafe path: $resolved_path" exit 1 @@ -53,11 +53,11 @@ else fi # Remove conda installation - if [ -d "$CONDA_BASE" ]; then - if [ -w "$CONDA_BASE" ]; then - rm -rf "$CONDA_BASE" + if [ -d "$conda_base" ]; then + if [ -w "$conda_base" ]; then + rm -rf "$conda_base" else - sudo rm -rf "$CONDA_BASE" + sudo rm -rf "$conda_base" fi echo " [OK] Conda installation removed" removed_something=true diff --git a/Utils/Python/uninstall_macOS.sh b/Utils/Python/uninstall_macOS.sh index cbeb2b32..e66cd472 100755 --- a/Utils/Python/uninstall_macOS.sh +++ b/Utils/Python/uninstall_macOS.sh @@ -11,8 +11,8 @@ set -euo pipefail -FRAMEWORK_DIR="/Library/Frameworks/Python.framework" -APPLICATIONS_DIR="/Applications" +framework_dir="/Library/Frameworks/Python.framework" +applications_dir="/Applications" echo "=== Uninstalling python.org Python installations ===" echo "" @@ -20,8 +20,8 @@ echo "" removed_something=false # Find installed Python versions in the framework directory -if [ -d "$FRAMEWORK_DIR/Versions" ]; then - for version_dir in "$FRAMEWORK_DIR/Versions"/3.*; do +if [ -d "$framework_dir/Versions" ]; then + for version_dir in "$framework_dir/Versions"/3.*; do [ -d "$version_dir" ] || continue version="$(basename "$version_dir")" echo " Found Python $version" @@ -35,17 +35,17 @@ fi # Remove Python framework echo "" -echo " Removing $FRAMEWORK_DIR ..." -if [ -w "$FRAMEWORK_DIR" ]; then - rm -rf "$FRAMEWORK_DIR" +echo " Removing $framework_dir ..." +if [ -w "$framework_dir" ]; then + rm -rf "$framework_dir" else - sudo rm -rf "$FRAMEWORK_DIR" + sudo rm -rf "$framework_dir" fi echo " [OK] Python framework removed" removed_something=true # Remove Python application folders (e.g. /Applications/Python 3.12) -for app_dir in "$APPLICATIONS_DIR"/Python\ 3.*; do +for app_dir in "$applications_dir"/Python\ 3.*; do [ -d "$app_dir" ] || continue echo " Removing $app_dir ..." if [ -w "$app_dir" ]; then diff --git a/Utils/VsCode/uninstall_macOS.sh b/Utils/VsCode/uninstall_macOS.sh index 215850bb..4b08d595 100755 --- a/Utils/VsCode/uninstall_macOS.sh +++ b/Utils/VsCode/uninstall_macOS.sh @@ -10,9 +10,9 @@ set -euo pipefail -APP_PATH="/Applications/Visual Studio Code.app" -CONFIG_DIR="$HOME/Library/Application Support/Code" -VSCODE_DIR="$HOME/.vscode" +app_path="/Applications/Visual Studio Code.app" +config_dir="$HOME/Library/Application Support/Code" +vscode_dir="$HOME/.vscode" echo "=== Uninstalling VS Code ===" echo "" @@ -20,12 +20,12 @@ echo "" removed_something=false # Remove application -if [ -d "$APP_PATH" ]; then - echo " Found VS Code at $APP_PATH" - if [ -w "$APP_PATH" ]; then - rm -rf "$APP_PATH" +if [ -d "$app_path" ]; then + echo " Found VS Code at $app_path" + if [ -w "$app_path" ]; then + rm -rf "$app_path" else - sudo rm -rf "$APP_PATH" + sudo rm -rf "$app_path" fi echo " [OK] Application removed" removed_something=true @@ -34,15 +34,15 @@ else fi # Remove settings and extensions -if [ -d "$CONFIG_DIR" ]; then - rm -rf "$CONFIG_DIR" +if [ -d "$config_dir" ]; then + rm -rf "$config_dir" echo " [OK] Settings and extensions removed" removed_something=true fi # Remove user data -if [ -d "$VSCODE_DIR" ]; then - rm -rf "$VSCODE_DIR" +if [ -d "$vscode_dir" ]; then + rm -rf "$vscode_dir" echo " [OK] User data (~/.vscode) removed" removed_something=true fi From 4160fbcfc69c3d8bd1474b4ad1740f294a359146 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Mon, 11 May 2026 13:39:07 +0200 Subject: [PATCH 08/15] Overwriteable var for miniforge url --- Core/Conda/README.md | 3 +++ Core/Conda/install/install_macOS.sh | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Core/Conda/README.md b/Core/Conda/README.md index 05f84a98..9fd41350 100644 --- a/Core/Conda/README.md +++ b/Core/Conda/README.md @@ -6,6 +6,9 @@ curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Core/Conda/install/install_macOS.sh | bash ``` +Optionally set the `PS_FORGE_URL` to specify the source of the Miniforge +installer (Philip's repo by default). + ## Uninstall (macOS) ```bash diff --git a/Core/Conda/install/install_macOS.sh b/Core/Conda/install/install_macOS.sh index 49a401be..6f8adced 100755 --- a/Core/Conda/install/install_macOS.sh +++ b/Core/Conda/install/install_macOS.sh @@ -10,7 +10,7 @@ set -euo pipefail -base_url="https://github.com/philipnickel/miniforge-PIS/releases/latest/download" +PS_FORGE_URL="${PS_FORGE_URL:-https://github.com/philipnickel/miniforge-PIS/releases/latest/download}" arch="$(uname -m)" installer_name="Miniforge3-MacOSX-${arch}.sh" install_dir="$HOME/miniforge3-dtu" @@ -28,7 +28,7 @@ else trap "rm -rf '$tmpdir_path'" EXIT KILL INT echo " Downloading ${installer_name}..." - curl -fSL "${base_url}/${installer_name}" -o "$tmpdir_path/${installer_name}" + curl -fSL "${PS_FORGE_URL}/${installer_name}" -o "$tmpdir_path/${installer_name}" echo " [OK] Download complete" # Run installer in batch mode (no prompts, no PATH modification) From 1c22cc311e017b756bac234e5caa57a42fd3e6ee Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Mon, 11 May 2026 14:19:38 +0200 Subject: [PATCH 09/15] Fix sig trap --- Core/VsCode/install/install_macOS.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/VsCode/install/install_macOS.sh b/Core/VsCode/install/install_macOS.sh index f730c7bb..400002c4 100755 --- a/Core/VsCode/install/install_macOS.sh +++ b/Core/VsCode/install/install_macOS.sh @@ -23,7 +23,7 @@ if command -v code &>/dev/null || [ -d "$app_path" ]; then else # Download tmpdir_path="$(mktemp -d)" - trap 'rm -rf "$tmpdir_path"' EXIT + trap "rm -rf '$tmpdir_path'" EXIT KILL INT echo " Downloading VS Code..." curl -fSL "$download_url" -o "$tmpdir_path/VSCode.zip" From ae007d0e69f1a512f278134f64d4e9b83485bc58 Mon Sep 17 00:00:00 2001 From: Philip Korsager Nickel Date: Thu, 4 Jun 2026 09:55:51 +0200 Subject: [PATCH 10/15] Updated installer link and added progress animation (#154) * Improve macOS installer progress UI (#62) Co-authored-by: manufer20 <143493629+manufer20@users.noreply.github.com> * added the progress bar --------- Co-authored-by: manufer20 <143493629+manufer20@users.noreply.github.com> --- Core/Orchestration/install_all_macOS.sh | 14 +- Utils/progress.sh | 293 ++++++++++++++++++++++-- 2 files changed, 288 insertions(+), 19 deletions(-) diff --git a/Core/Orchestration/install_all_macOS.sh b/Core/Orchestration/install_all_macOS.sh index 4d0c43e6..2887b0e0 100755 --- a/Core/Orchestration/install_all_macOS.sh +++ b/Core/Orchestration/install_all_macOS.sh @@ -13,20 +13,22 @@ set -euo pipefail PS_REPO_URL="${PS_REPO_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev}" export PS_REPO_URL +# Load the progress UI (defines the `progress` helper; the source guard in +# progress.sh skips its self-test when sourced rather than executed directly). +source <(curl -fsSL "$PS_REPO_URL/Utils/progress.sh") + echo "=========================================" echo " DTU Python Support - Full Installation" echo "=========================================" echo "" # Step 1: Install Miniforge/Conda -echo "--- Step 1/2: Miniforge ---" -bash <(curl -fsSL "$PS_REPO_URL/Core/Conda/install/install_macOS.sh") -echo "" +progress "Step 1/2: Miniforge" 10 \ + bash <(curl -fsSL "$PS_REPO_URL/Core/Conda/install/install_macOS.sh") # Step 2: Install VS Code (includes extensions and settings) -echo "--- Step 2/2: VS Code ---" -bash <(curl -fsSL "$PS_REPO_URL/Core/VsCode/install/install_macOS.sh") -echo "" +progress "Step 2/2: VS Code" 5 \ + bash <(curl -fsSL "$PS_REPO_URL/Core/VsCode/install/install_macOS.sh") echo "=========================================" echo " Installation complete!" diff --git a/Utils/progress.sh b/Utils/progress.sh index 12d1e1c9..8fd71f07 100644 --- a/Utils/progress.sh +++ b/Utils/progress.sh @@ -1,11 +1,38 @@ #!/usr/bin/bash +PROGRESS_LOG_FILE="${PROGRESS_LOG_FILE:-/tmp/dtu_log.txt}" +PROGRESS_LOG_LINES="${PROGRESS_LOG_LINES:-13}" +PROGRESS_BOX_WIDTH="${PROGRESS_BOX_WIDTH:-84}" + +progress_intro_title() { + echo "Welcome to the DTU Python Installation Support setup." +} + +progress_intro_lines() { + cat <<'EOF' +This setup is for first-year DTU students. +It installs Conda, Python, the required course packages, +and Visual Studio Code. + +It is intended for courses such as Mathematics 1a, +Mathematics 1b, Statistics, Physics, +and Computer Programming. + +You do not need much coding experience to complete this setup. +The installation will begin in a moment. +EOF +} + # Params: # - message: str # - estimated time: int # - command: str list progress() { - local msg=$1 mins=$2 log_file="/tmp/dtu_log.txt" start_line=1 + local msg=$1 mins=$2 log_file="$PROGRESS_LOG_FILE" start_line=1 + local requested_box_width="$PROGRESS_BOX_WIDTH" + local requested_box_lines="$PROGRESS_LOG_LINES" + local box_inner_width box_line_count footer_width term_cols term_rows max_inner_width max_box_lines + local box_top box_bottom shift 2 if [[ -f "$log_file" ]]; then @@ -15,35 +42,270 @@ progress() { fi echo "[DTULOG]: $msg ($(date))" >> "$log_file" - exec 4>/dev/tty 2>/dev/null || exec 4>&1 + if [[ -t 1 ]] && test -w /dev/tty 2>/dev/null; then + exec 4>/dev/tty + else + exec 4>&1 + fi cleanup() { + tput smam >&4 2>/dev/null || printf '\033[?7h' >&4 tput rmcup >&4 2>/dev/null || true tput cnorm >&4 2>/dev/null || true } + + type_line() { + local line=$1 i + for ((i = 0; i < ${#line}; i++)); do + printf '%s' "${line:i:1}" >&4 + sleep 0.035 + done + printf '\n' >&4 + } + + repeat_char() { + local char=$1 count=$2 repeated='' + while (( ${#repeated} < count )); do + repeated+="$char" + done + printf '%s' "${repeated:0:count}" + } + + setup_box_dimensions() { + term_cols=$(tput cols 2>/dev/null || printf '100') + if [[ ! "$term_cols" =~ ^[0-9]+$ ]]; then + term_cols=100 + fi + term_rows=$(tput lines 2>/dev/null || printf '30') + if [[ ! "$term_rows" =~ ^[0-9]+$ ]]; then + term_rows=30 + fi + + max_inner_width=$((term_cols - 6)) + if (( max_inner_width < 40 )); then + max_inner_width=40 + fi + max_box_lines=$((term_rows - 10)) + if (( max_box_lines < 6 )); then + max_box_lines=6 + fi + + box_inner_width=$requested_box_width + if (( box_inner_width > max_inner_width )); then + box_inner_width=$max_inner_width + fi + box_line_count=$requested_box_lines + if (( box_line_count > max_box_lines )); then + box_line_count=$max_box_lines + fi + + footer_width=$((box_inner_width + 4)) + box_top="$(repeat_char '━' "$footer_width")" + box_bottom="$(repeat_char '━' "$footer_width")" + } + + print_box_line() { + local line=${1//$'\r'/} + line=${line//$'\t'/ } + printf ' %-*.*s\n' "$box_inner_width" "$box_inner_width" "$line" >&4 + } + + print_footer_line() { + local line=$1 + printf ' %-*.*s\n' "$footer_width" "$footer_width" "$line" >&4 + } + + process_stream() { + DTU_DISPLAY_FILE="$display_file" DTU_LOG_FILE="$log_file" perl -e ' + use strict; + use warnings; + + my $display_file = $ENV{DTU_DISPLAY_FILE}; + my $log_file = $ENV{DTU_LOG_FILE}; + + open my $log_fh, ">>", $log_file or die "Cannot open log file: $!"; + binmode STDIN; + binmode $log_fh; + select((select($log_fh), $| = 1)[0]); + + my @lines = (); + my $current_line = ""; + + sub clean_output { + my ($text) = @_; + $text =~ s/\e\][^\a]*(?:\a|\e\\)//g; + $text =~ s/\e\[[0-9;?]*[ -\/]*[@-~]//g; + $text =~ s/[\x00-\x07\x0B\x0C\x0E-\x1F\x7F]//g; + return $text; + } + + sub clean_display_line { + my ($line) = @_; + $line =~ s#/var/folders/[^[:space:]]*/T/dtu-install-test\.[^/[:space:]]+##g; + $line =~ s/^\s+//; + $line =~ s/\s+$//; + return "" if $line =~ /^%\s+Total\s+%/; + return "" if $line =~ /^Dload\s+Upload\s+Total/; + return "" if $line =~ /^\d{1,3}\s+(?:\d+|[0-9.]+[KMGT]?)\s+/; + return $line; + } + + sub write_display { + open my $display_fh, ">", $display_file or die "Cannot open display file: $!"; + binmode $display_fh; + + my @display_lines = (); + my %package_line_index = (); + + for my $line (@lines, $current_line) { + my $display_line = clean_display_line($line); + next if $display_line eq ""; + next if $display_line eq "done"; + + if ($display_line =~ /^([A-Za-z0-9_.+-]+)\s+\|.*\|\s*\d+%$/) { + my $package_name = $1; + if (exists $package_line_index{$package_name}) { + $display_lines[$package_line_index{$package_name}] = $display_line; + } else { + $package_line_index{$package_name} = scalar @display_lines; + push @display_lines, $display_line; + } + next; + } + + push @display_lines, $display_line; + } + + for my $display_line (@display_lines) { + print {$display_fh} "$display_line\n"; + } + + close $display_fh; + } + + while (sysread(STDIN, my $chunk, 4096)) { + my $clean_chunk = clean_output($chunk); + my $log_chunk = $clean_chunk; + $log_chunk =~ s/\r/\n/g; + print {$log_fh} $log_chunk; + + for my $char (split //, $clean_chunk) { + if ($char eq "\r") { + $current_line = ""; + } elsif ($char eq "\n") { + push @lines, $current_line; + $current_line = ""; + } elsif ($char eq "\b") { + chop $current_line; + } elsif ($char eq "\t") { + $current_line .= " "; + } else { + $current_line .= $char; + } + } + + shift @lines while @lines > 200; + write_display(); + } + + write_display(); + close $log_fh; + ' + } + + render_recent_output() { + local -a recent_lines=() + local line_count=0 recent_line + mapfile -t recent_lines < <(tail -n "$box_line_count" "$display_file" 2>/dev/null) + + for recent_line in "${recent_lines[@]}"; do + print_box_line "$recent_line" + done + + line_count=${#recent_lines[@]} + + while (( line_count < box_line_count )); do + print_box_line '' + line_count=$((line_count + 1)) + done + } + + format_duration() { + if (( mins == 1 )); then + printf '%d minute' "$mins" + else + printf '%d minutes' "$mins" + fi + } + + show_intro() { + local intro_line + printf ' \033[1m%s\033[0m\n\n' "$(progress_intro_title)" >&4 + + while IFS= read -r intro_line || [[ -n "$intro_line" ]]; do + if [[ -z "$intro_line" ]]; then + printf '\n' >&4 + else + type_line " $intro_line" + fi + done < <(progress_intro_lines) + + sleep 5.5 + } + trap cleanup EXIT TERM HUP tput smcup >&4 2>/dev/null || true tput civis >&4 2>/dev/null || true + tput rmam >&4 2>/dev/null || printf '\033[?7l' >&4 - "$@" >>"$log_file" 2>&1 & + if (( start_line == 1 )); then + show_intro + fi + + setup_box_dimensions + + local display_file + display_file=$(mktemp) + local status_file + status_file=$(mktemp) + + ( + set +e + NO_COLOR=1 CLICOLOR=0 CLICOLOR_FORCE=0 "$@" 2>&1 + printf '%s' "$?" > "$status_file" + ) | process_stream & local cmd_pid=$! local spin='-\|/' i=0 + local spinner_char progress_line info_line while kill -0 "$cmd_pid" 2>/dev/null; do + spinner_char="${spin:i++%${#spin}:1}" + progress_line="[$spinner_char] $msg" + info_line="Please do not interrupt this step - it may take up to $(format_duration)." + printf '\e[H\e[2J' >&4 printf ' \033[1m%s\033[0m\n' "DTU installation in progress." >&4 - printf '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' >&4 - tail -n +"$((start_line + 1))" "$log_file" 2>/dev/null | tail -n 5 >&4 - printf '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' >&4 - printf ' \033[1m[%c] %s\033[0m\n' "${spin:i++%${#spin}:1}" "$msg" >&4 - printf ' %s\n' \ - "Please do not interrupt this script - it may take up to $mins minutes." >&4 + printf '%s\n' "$box_top" >&4 + render_recent_output + printf '%s\n' "$box_bottom" >&4 + printf ' \033[1m%-*.*s\033[0m\n' "$footer_width" "$footer_width" "$progress_line" >&4 + print_footer_line "$info_line" sleep 0.1 done - wait "$cmd_pid" - local status=$? + local status=0 + wait "$cmd_pid" || true + if [[ -s "$status_file" ]]; then + status=$(<"$status_file") + rm -f "$status_file" + if [[ ! "$status" =~ ^[0-9]+$ ]]; then + status=1 + fi + else + status=1 + fi + rm -f "$status_file" "$display_file" cleanup trap - EXIT TERM HUP @@ -52,7 +314,10 @@ progress() { if (( status != 0 )); then echo "[DTULOG]: failure ($status)" >> "$log_file" printf ' ┗━ \033[1;31m%s\033[00m (error code %d).\n' Failure $status - echo " Please contact us and provide the file '$log_file'." + echo " Contact us by email or Discord:" + echo " pythonsupport@dtu.dk | https://discord.gg/h8EVaV9ShP" + echo " https://pythonsupport.dtu.dk/#reach-us" + printf ' \033[1m%s\033[0m\n' "Please include the file '$log_file'." else echo "[DTULOG]: success" >> "$log_file" printf ' ┗━ \033[1;32m%s\033[0m\n' 'Success!' @@ -83,4 +348,6 @@ output1() { return 1 } -progress "Step 1/1: Installing XYZ" 15 output0 +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + progress "Step 1/1: Installing XYZ" 15 output1 +fi From 56b23adbafb04d6847b3e3fdc2f3ef9f988d6221 Mon Sep 17 00:00:00 2001 From: Philip Korsager Nickel Date: Thu, 4 Jun 2026 13:58:07 +0200 Subject: [PATCH 11/15] Trap fix (#156) * Improve macOS installer progress UI (#62) Co-authored-by: manufer20 <143493629+manufer20@users.noreply.github.com> * added the progress bar * link updates * fix traps --------- Co-authored-by: manufer20 <143493629+manufer20@users.noreply.github.com> --- Core/Conda/install/install_macOS.sh | 2 +- Core/VsCode/install/install_macOS.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Conda/install/install_macOS.sh b/Core/Conda/install/install_macOS.sh index 6f8adced..2e6e7669 100755 --- a/Core/Conda/install/install_macOS.sh +++ b/Core/Conda/install/install_macOS.sh @@ -25,7 +25,7 @@ if [ -d "$install_dir" ] && [ -x "$install_dir/bin/conda" ]; then else # Download tmpdir_path="$(mktemp -d)" - trap "rm -rf '$tmpdir_path'" EXIT KILL INT + trap "rm -rf '$tmpdir_path'" EXIT echo " Downloading ${installer_name}..." curl -fSL "${PS_FORGE_URL}/${installer_name}" -o "$tmpdir_path/${installer_name}" diff --git a/Core/VsCode/install/install_macOS.sh b/Core/VsCode/install/install_macOS.sh index 400002c4..dff82a3f 100755 --- a/Core/VsCode/install/install_macOS.sh +++ b/Core/VsCode/install/install_macOS.sh @@ -23,7 +23,7 @@ if command -v code &>/dev/null || [ -d "$app_path" ]; then else # Download tmpdir_path="$(mktemp -d)" - trap "rm -rf '$tmpdir_path'" EXIT KILL INT + trap "rm -rf '$tmpdir_path'" EXIT echo " Downloading VS Code..." curl -fSL "$download_url" -o "$tmpdir_path/VSCode.zip" From 9c311e8922782463e276e485fb55c3ddd2dcc938 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Mon, 22 Jun 2026 02:29:41 +0200 Subject: [PATCH 12/15] Base url var name --- Core/VsCode/config/settings_windows.ps1 | 2 +- Core/VsCode/install/install_windows.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/VsCode/config/settings_windows.ps1 b/Core/VsCode/config/settings_windows.ps1 index 657d33fa..34c07852 100644 --- a/Core/VsCode/config/settings_windows.ps1 +++ b/Core/VsCode/config/settings_windows.ps1 @@ -17,7 +17,7 @@ Write-Host "=== Applying VS Code Settings ===`n" New-Item -ItemType Directory -Path $settingsDir -Force | Out-Null -Invoke-WebRequest -Uri "$env:REPO_BASE_URL/Core/VsCode/config/default_settings_Windows.json" ` +Invoke-WebRequest -Uri "$env:PS_REPO_URL/Core/VsCode/config/default_settings_Windows.json" ` -OutFile $settingsFile ` -UseBasicParsing diff --git a/Core/VsCode/install/install_windows.ps1 b/Core/VsCode/install/install_windows.ps1 index 723136ed..626b3364 100644 --- a/Core/VsCode/install/install_windows.ps1 +++ b/Core/VsCode/install/install_windows.ps1 @@ -45,9 +45,9 @@ if (Get-Command code -ErrorAction SilentlyContinue -or (Test-Path $AppPath)) { } # Apply settings -Invoke-Expression (Invoke-WebRequest -Uri "$PS_REPO_URL/Core/VsCode/config/settings_windows.ps1" -UseBasicParsing).Content +Invoke-Expression (Invoke-WebRequest -Uri "$env:PS_REPO_URL/Core/VsCode/config/settings_windows.ps1" -UseBasicParsing).Content # Install extensions -Invoke-Expression (Invoke-WebRequest -Uri "$PS_REPO_URL/Core/VsCode/config/extensions_windows.ps1" -UseBasicParsing).Content +Invoke-Expression (Invoke-WebRequest -Uri "$env:PS_REPO_URL/Core/VsCode/config/extensions_windows.ps1" -UseBasicParsing).Content Write-Host "`n=== VS Code installation complete! ===" From d41ea970b0629029b6ef5ba89569d8066ee807d0 Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Mon, 22 Jun 2026 02:42:38 +0200 Subject: [PATCH 13/15] Remove duplicate REPO_BASE_UR --- Core/Orchestration/install_all_macOS.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Core/Orchestration/install_all_macOS.sh b/Core/Orchestration/install_all_macOS.sh index de95f227..2887b0e0 100755 --- a/Core/Orchestration/install_all_macOS.sh +++ b/Core/Orchestration/install_all_macOS.sh @@ -17,10 +17,6 @@ export PS_REPO_URL # progress.sh skips its self-test when sourced rather than executed directly). source <(curl -fsSL "$PS_REPO_URL/Utils/progress.sh") -# Load the progress UI (defines the `progress` helper; the source guard in -# progress.sh skips its self-test when sourced rather than executed directly). -source <(curl -fsSL "$REPO_BASE_URL/Utils/progress.sh") - echo "=========================================" echo " DTU Python Support - Full Installation" echo "=========================================" From 17dd92b5453f8371279fe5bbc35d54b9e859678d Mon Sep 17 00:00:00 2001 From: "Emil A. Overbeck" Date: Mon, 22 Jun 2026 02:49:14 +0200 Subject: [PATCH 14/15] Fix miniforge links --- Core/Conda/install/install_macOS.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Conda/install/install_macOS.sh b/Core/Conda/install/install_macOS.sh index 2e6e7669..ab528fba 100755 --- a/Core/Conda/install/install_macOS.sh +++ b/Core/Conda/install/install_macOS.sh @@ -10,7 +10,7 @@ set -euo pipefail -PS_FORGE_URL="${PS_FORGE_URL:-https://github.com/philipnickel/miniforge-PIS/releases/latest/download}" +PS_FORGE_URL="${PS_FORGE_URL:-https://github.com/dtudk/pythonsupport-forge/releases/latest/download}" #TODO: change to internal site arch="$(uname -m)" installer_name="Miniforge3-MacOSX-${arch}.sh" install_dir="$HOME/miniforge3-dtu" From 4032a8dd2e35ba7766e7a717cf67cf2959e7e515 Mon Sep 17 00:00:00 2001 From: Philip Korsager Nickel Date: Thu, 13 Aug 2026 07:30:48 +0200 Subject: [PATCH 15/15] Squash merge august into dev --- .gitignore | 6 +- Core/Conda/README.md | 1 - Core/Conda/install/install_windows.ps1 | 73 ++++++ Core/Orchestration/README.md | 15 -- Core/Orchestration/install_all_macOS.sh | 13 +- Core/Orchestration/install_all_windows.ps1 | 30 +++ Core/Orchestration/uninstall_all_macOS.sh | 2 +- Core/Orchestration/uninstall_all_windows.ps1 | 31 +++ Core/VsCode/README.md | 12 + Core/VsCode/config/extensions_windows.ps1 | 45 +++- Core/VsCode/config/settings_windows.ps1 | 19 +- Core/VsCode/install/install_windows.ps1 | 39 ++-- README.md | 99 ++++++-- Testing/Test-Cases.md | 3 - Utils/Conda/README.md | 12 + Utils/Conda/uninstall_Windows.ps1 | 227 +++++++++++++++++++ Utils/Health/README.md | 0 Utils/Health/check_Windows.ps1 | 0 Utils/Health/check_macOS.sh | 0 Utils/Python/README.md | 0 Utils/Python/uninstall_windows.ps1 | 0 Utils/VsCode/README.md | 10 + Utils/VsCode/uninstall_Windows.ps1 | 60 +++++ Utils/progress.ps1 | 115 ++++++++++ macOS_local.sh | 13 ++ release_assets/README.md | 8 + 26 files changed, 755 insertions(+), 78 deletions(-) create mode 100644 Core/Conda/install/install_windows.ps1 delete mode 100644 Core/Orchestration/README.md create mode 100644 Core/Orchestration/uninstall_all_windows.ps1 delete mode 100644 Testing/Test-Cases.md delete mode 100644 Utils/Health/README.md delete mode 100644 Utils/Health/check_Windows.ps1 delete mode 100644 Utils/Health/check_macOS.sh delete mode 100644 Utils/Python/README.md delete mode 100644 Utils/Python/uninstall_windows.ps1 create mode 100644 Utils/progress.ps1 create mode 100644 macOS_local.sh create mode 100644 release_assets/README.md diff --git a/.gitignore b/.gitignore index c30ef55f..ede9c761 100644 --- a/.gitignore +++ b/.gitignore @@ -106,4 +106,8 @@ docs/_build/ # Application specific *.dmg -*.app \ No newline at end of file +*.app + +# Locally cached release assets (installers) +release_assets/dtu-miniconda +release_assets/vsCode diff --git a/Core/Conda/README.md b/Core/Conda/README.md index 9fd41350..f1bbe91f 100644 --- a/Core/Conda/README.md +++ b/Core/Conda/README.md @@ -7,7 +7,6 @@ curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Co ``` Optionally set the `PS_FORGE_URL` to specify the source of the Miniforge -installer (Philip's repo by default). ## Uninstall (macOS) diff --git a/Core/Conda/install/install_windows.ps1 b/Core/Conda/install/install_windows.ps1 new file mode 100644 index 00000000..ac0137bc --- /dev/null +++ b/Core/Conda/install/install_windows.ps1 @@ -0,0 +1,73 @@ +# @doc +# @name: Miniforge Install (Windows) +# @description: Download and install Miniforge (conda) on Windows +# @category: Core +# @usage: powershell -File Core/Conda/install/install_windows.ps1 +# @requirements: Windows, PowerShell 5.1+ +# @notes: Downloads the latest Miniforge installer and runs it silently. +# The installer bundles Python and all required course packages. +# @/doc + +$ErrorActionPreference = "Stop" + +$PS_FORGE_URL = $env:PS_FORGE_URL +if (-not $PS_FORGE_URL) { $PS_FORGE_URL = "https://github.com/dtudk/pythonsupport-forge/releases/latest/download" } #TODO: change to internal site +$installerName = "Miniforge3-Windows-x86_64.exe" +$installDir = Join-Path $env:USERPROFILE "miniforge3-dtu" +$condaExe = Join-Path $installDir "Scripts\conda.exe" + + +Write-Host "=== Installing Miniforge ===`n" + +# Check if already installed +if (Test-Path $condaExe) { + Write-Host " Miniforge is already installed at $installDir" + Write-Host " [OK] Skipping download" +} else { + $tmpDir = $null + try { + $tmpDir = New-Item -ItemType Directory -Path ([System.IO.Path]::GetTempPath()) -Name ("miniforge_" + [System.Guid]::NewGuid()) + $installerPath = Join-Path $tmpDir.FullName $installerName + + Write-Host " Downloading $installerName..." + Invoke-WebRequest -Uri "$PS_FORGE_URL/$installerName" -OutFile $installerPath -UseBasicParsing + Write-Host " [OK] Download complete" + + # Run installer + # Flag rules per the constructor docs + # (https://conda.github.io/constructor/cli-options/#windows-installers): + Write-Host " Running installer..." + $argString = "/S /InstallationType=JustMe /RegisterPython=0 /AddToPath=0 /D=$installDir" + $proc = Start-Process -FilePath $installerPath -ArgumentList $argString ` + -NoNewWindow -Wait -PassThru + if ($proc.ExitCode -ne 0) { + throw "Miniforge installer exited with code $($proc.ExitCode)" + } + Write-Host " [OK] Miniforge installed to $installDir" + } + finally { + if ($tmpDir -and (Test-Path $tmpDir.FullName)) { + Remove-Item -Recurse -Force $tmpDir.FullName -ErrorAction SilentlyContinue + } + } +} + +#NOTE: Not needed if we just use miniconda prompt +# Load conda shell integration and activate the base environment +# (mirror of 'source conda.sh && conda activate' in the macOS script) +#Write-Host " Initializing conda..." +#$condaHook = Join-Path $installDir "shell\condabin\conda-hook.ps1" +#if (Test-Path $condaHook) { +# & $condaHook +# conda activate $installDir +#} + +# Initialize conda for all supported shells on this machine +# (on Windows: PowerShell profile + cmd.exe autorun) +#& $condaExe init --all +#if ($LASTEXITCODE -ne 0) { +# throw "conda init --all failed with exit code $LASTEXITCODE" +#} +#Write-Host " [OK] conda init complete (restart your terminal to activate)" + +Write-Host "`n=== Miniforge installation complete! ===" diff --git a/Core/Orchestration/README.md b/Core/Orchestration/README.md deleted file mode 100644 index 6cfed050..00000000 --- a/Core/Orchestration/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Orchestration - -One-command install or uninstall of the full DTU Python Support stack. - -## Install everything (macOS) - -```bash -curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Core/Orchestration/install_all_macOS.sh | bash -``` - -## Uninstall everything (macOS) - -```bash -curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Core/Orchestration/uninstall_all_macOS.sh | bash -``` diff --git a/Core/Orchestration/install_all_macOS.sh b/Core/Orchestration/install_all_macOS.sh index 2887b0e0..a5f159ed 100755 --- a/Core/Orchestration/install_all_macOS.sh +++ b/Core/Orchestration/install_all_macOS.sh @@ -10,12 +10,11 @@ set -euo pipefail -PS_REPO_URL="${PS_REPO_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev}" +PS_REPO_URL="${PS_REPO_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main}" export PS_REPO_URL # Load the progress UI (defines the `progress` helper; the source guard in -# progress.sh skips its self-test when sourced rather than executed directly). -source <(curl -fsSL "$PS_REPO_URL/Utils/progress.sh") +#source <(curl -fsSL "$PS_REPO_URL/Utils/progress.sh") echo "=========================================" echo " DTU Python Support - Full Installation" @@ -23,12 +22,12 @@ echo "=========================================" echo "" # Step 1: Install Miniforge/Conda -progress "Step 1/2: Miniforge" 10 \ - bash <(curl -fsSL "$PS_REPO_URL/Core/Conda/install/install_macOS.sh") +#progress "Step 1/2: Miniforge" 10 \ +bash <(curl -fsSL "$PS_REPO_URL/Core/Conda/install/install_macOS.sh") # Step 2: Install VS Code (includes extensions and settings) -progress "Step 2/2: VS Code" 5 \ - bash <(curl -fsSL "$PS_REPO_URL/Core/VsCode/install/install_macOS.sh") +#progress "Step 2/2: VS Code" 5 \ +bash <(curl -fsSL "$PS_REPO_URL/Core/VsCode/install/install_macOS.sh") echo "=========================================" echo " Installation complete!" diff --git a/Core/Orchestration/install_all_windows.ps1 b/Core/Orchestration/install_all_windows.ps1 index e69de29b..84e63ded 100644 --- a/Core/Orchestration/install_all_windows.ps1 +++ b/Core/Orchestration/install_all_windows.ps1 @@ -0,0 +1,30 @@ +# @doc +# @name: Full Installation (Windows) +# @description: Orchestrate the full installation of Miniforge and VS Code on Windows +# @category: Core +# @usage: irm https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Core/Orchestration/install_all_windows.ps1 | iex +# @requirements: Windows, PowerShell 5.1+ +# @notes: Runs all installation steps in order: Miniforge, VS Code (with extensions and settings) +# @/doc + +$ErrorActionPreference = "Stop" + +if (-not $env:PS_REPO_URL) { + $env:PS_REPO_URL = "https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main" +} + +Write-Host "=========================================" +Write-Host " DTU Python Support - Full Installation" +Write-Host "=========================================" +Write-Host "" + +# Step 1: Install Miniforge/Conda +#Invoke-Expression (Invoke-WebRequest -Uri "$env:PS_REPO_URL/Core/Conda/install/install_windows.ps1" -UseBasicParsing).Content + +# Step 2: Install VS Code (includes extensions and settings) +Invoke-Expression (Invoke-WebRequest -Uri "$env:PS_REPO_URL/Core/VsCode/install/install_windows.ps1" -UseBasicParsing).Content + +Write-Host "=========================================" +Write-Host " Installation complete!" +Write-Host " Open Miniforge Prompt from the Start menu to interact with conda." +Write-Host "=========================================" diff --git a/Core/Orchestration/uninstall_all_macOS.sh b/Core/Orchestration/uninstall_all_macOS.sh index e5c3b441..4cf1cf3b 100755 --- a/Core/Orchestration/uninstall_all_macOS.sh +++ b/Core/Orchestration/uninstall_all_macOS.sh @@ -10,7 +10,7 @@ set -euo pipefail -PS_REPO_URL="${PS_REPO_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev}" +PS_REPO_URL="${PS_REPO_URL:-https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main}" export PS_REPO_URL echo "=========================================" diff --git a/Core/Orchestration/uninstall_all_windows.ps1 b/Core/Orchestration/uninstall_all_windows.ps1 new file mode 100644 index 00000000..d95cbeec --- /dev/null +++ b/Core/Orchestration/uninstall_all_windows.ps1 @@ -0,0 +1,31 @@ +# @doc +# @name: Full Uninstall (Windows) +# @description: Uninstall VS Code and all detected Conda distributions on Windows +# @category: Core +# @usage: irm https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Core/Orchestration/uninstall_all_windows.ps1 | iex +# @requirements: Windows, PowerShell 5.1+ +# @notes: Removes VS Code first, followed by per-user and machine-wide Conda distributions and current-user data. +# @/doc + +$ErrorActionPreference = "Stop" + +if (-not $env:PS_REPO_URL) { + $env:PS_REPO_URL = "https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main" +} + +Write-Host "=========================================" +Write-Host " DTU Python Support - Full Uninstall" +Write-Host "=========================================" +Write-Host "" + +Write-Host "--- Step 1/2: VS Code ---" +Invoke-Expression (Invoke-WebRequest -Uri "$env:PS_REPO_URL/Utils/VsCode/uninstall_Windows.ps1" -UseBasicParsing).Content +Write-Host "" + +Write-Host "--- Step 2/2: Conda distributions ---" +Invoke-Expression (Invoke-WebRequest -Uri "$env:PS_REPO_URL/Utils/Conda/uninstall_Windows.ps1" -UseBasicParsing).Content +Write-Host "" + +Write-Host "=========================================" +Write-Host " Uninstall complete!" +Write-Host "=========================================" diff --git a/Core/VsCode/README.md b/Core/VsCode/README.md index 23162951..533841b8 100644 --- a/Core/VsCode/README.md +++ b/Core/VsCode/README.md @@ -8,6 +8,18 @@ Installs VS Code, applies default settings, and installs extensions. curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main/Core/VsCode/install/install_macOS.sh | bash ``` +## Install (Windows) + +The full Windows one-liner installs VS Code after Miniforge: + +```powershell +irm https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev/Core/Orchestration/install_all_windows.ps1 | iex +``` + +For local integration tests, `PS_VSCODE_URL` can override the VS Code installer +download URL. Production installs leave it unset and select the x64 or ARM64 +stable user installer automatically. + ## Uninstall (macOS) Removes VS Code, settings, extensions, and user data. diff --git a/Core/VsCode/config/extensions_windows.ps1 b/Core/VsCode/config/extensions_windows.ps1 index 0752290d..1748a360 100644 --- a/Core/VsCode/config/extensions_windows.ps1 +++ b/Core/VsCode/config/extensions_windows.ps1 @@ -9,12 +9,22 @@ $ErrorActionPreference = "Stop" -$codeCli = "$env:LOCALAPPDATA\Programs\Microsoft VS Code\bin\code.cmd" +# Resolve the VS Code CLI: prefer the default install location, fall back to PATH +$codeCli = Join-Path $env:LOCALAPPDATA "Programs\Microsoft VS Code\bin\code.cmd" +if (-not (Test-Path $codeCli)) { + $codeCmd = Get-Command code -ErrorAction SilentlyContinue + if ($codeCmd) { + $codeCli = $codeCmd.Source + } else { + throw "VS Code CLI ('code') not found. Is VS Code installed?" + } +} Write-Host "=== Installing VS Code Extensions ===`n" $extensionsUrl = "$env:PS_REPO_URL/Core/VsCode/config/extensions.txt" $lines = (Invoke-WebRequest -Uri $extensionsUrl -UseBasicParsing).Content -split "`n" +$failedExtensions = @() foreach ($line in $lines) { $line = $line.Trim() @@ -23,12 +33,39 @@ foreach ($line in $lines) { continue } + $previousErrorActionPreference = $ErrorActionPreference + $invocationError = $null + $exitCode = $null + try { - & $codeCli --install-extension $line --force 2>$null - Write-Host " [OK] $line" + # Windows PowerShell 5.1 promotes native stderr to its Error stream. + # Keep it non-terminating so output remains visible and use the native + # exit code to decide whether installation succeeded. + $ErrorActionPreference = "Continue" + & $codeCli --install-extension $line --force + $exitCode = $LASTEXITCODE } catch { - Write-Host " [FAIL] $line" + $invocationError = $_.Exception.Message + } finally { + $ErrorActionPreference = $previousErrorActionPreference } + + if ($invocationError) { + Write-Host " [FAIL] $line ($invocationError)" -ForegroundColor Red + $failedExtensions += $line + } elseif ($null -eq $exitCode) { + Write-Host " [FAIL] $line (VS Code CLI did not report an exit code)" -ForegroundColor Red + $failedExtensions += $line + } elseif ($exitCode -eq 0) { + Write-Host " [OK] $line" + } else { + Write-Host " [FAIL] $line (exit code $exitCode)" -ForegroundColor Red + $failedExtensions += $line + } +} + +if ($failedExtensions.Count -gt 0) { + throw "Failed to install VS Code extension(s): $($failedExtensions -join ', ')" } Write-Host "`n=== Extensions complete! ===" diff --git a/Core/VsCode/config/settings_windows.ps1 b/Core/VsCode/config/settings_windows.ps1 index 34c07852..141d80c9 100644 --- a/Core/VsCode/config/settings_windows.ps1 +++ b/Core/VsCode/config/settings_windows.ps1 @@ -10,16 +10,21 @@ # Like 'set -e' in bash $ErrorActionPreference = "Stop" -$settingsDir = "$env:APPDATA\Code\User" -$settingsFile = "$settingsDir\settings.json" +$settingsDir = Join-Path $env:APPDATA "Code\User" +$settingsFile = Join-Path $settingsDir "settings.json" Write-Host "=== Applying VS Code Settings ===`n" -New-Item -ItemType Directory -Path $settingsDir -Force | Out-Null +if (Test-Path $settingsFile) { + Write-Host " [WARNING] $settingsFile already exists. Keeping the existing settings." -ForegroundColor Yellow +} else { + New-Item -ItemType Directory -Path $settingsDir -Force | Out-Null -Invoke-WebRequest -Uri "$env:PS_REPO_URL/Core/VsCode/config/default_settings_Windows.json" ` - -OutFile $settingsFile ` - -UseBasicParsing + Invoke-WebRequest -Uri "$env:PS_REPO_URL/Core/VsCode/config/default_settings_Windows.json" ` + -OutFile $settingsFile ` + -UseBasicParsing + + Write-Host " [OK] Settings applied to $settingsFile" +} -Write-Host " [OK] Settings applied to $settingsFile" Write-Host "`n=== VS Code settings complete! ===" diff --git a/Core/VsCode/install/install_windows.ps1 b/Core/VsCode/install/install_windows.ps1 index 626b3364..524c0831 100644 --- a/Core/VsCode/install/install_windows.ps1 +++ b/Core/VsCode/install/install_windows.ps1 @@ -4,43 +4,50 @@ # @category: Core # @usage: powershell -File Core/VsCode/install/install_windows.ps1 # @requirements: Windows -# @notes: Downloads the installer and installs VS Code +# @notes: Downloads the architecture-appropriate user installer and installs VS Code. +# PS_VSCODE_URL may override the installer URL for local testing. # @/doc $ErrorActionPreference = "Stop" -$AppPath = "$Env:LOCALAPPDATA\Programs\Microsoft VS Code" -$DownloadUrl = "https://update.code.visualstudio.com/latest/win32-x64-user/stable" +$appPath = Join-Path $env:LOCALAPPDATA "Programs\Microsoft VS Code" +$downloadUrl = $env:PS_VSCODE_URL +if (-not $downloadUrl) { + $downloadUrl = "https://update.code.visualstudio.com/latest/win32-x64-user/stable" + if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { + $downloadUrl = "https://update.code.visualstudio.com/latest/win32-arm64-user/stable" + } +} Write-Host "=== Installing VS Code ===`n" # Check if already installed -if (Get-Command code -ErrorAction SilentlyContinue -or (Test-Path $AppPath)) { +if ((Get-Command code -ErrorAction SilentlyContinue) -or (Test-Path $appPath)) { Write-Host " VS Code is already installed." Write-Host " [OK] Skipping download" } else { - # Create temp directory - $TmpDir = New-Item -ItemType Directory -Path ([System.IO.Path]::GetTempPath()) -Name ("vscode_" + [System.Guid]::NewGuid()) + $tmpDir = $null try { - $ZipPath = Join-Path $TmpDir.FullName "VSCode.exe" + $tmpDir = New-Item -ItemType Directory -Path ([System.IO.Path]::GetTempPath()) -Name ("vscode_" + [System.Guid]::NewGuid()) + $installerPath = Join-Path $tmpDir.FullName "VSCode.exe" Write-Host " Downloading VS Code..." - Invoke-WebRequest -Uri $DownloadUrl -OutFile $ZipPath + Invoke-WebRequest -Uri $downloadUrl -OutFile $installerPath -UseBasicParsing Write-Host " [OK] Download complete" - # Remove existing installation if present - if (Test-Path $AppPath) { - Write-Host " Removing existing installation..." - Remove-Item -Recurse -Force $AppPath - } - # Install silently Write-Host " Installing..." - Start-Process -FilePath $ZipPath -ArgumentList "/silent /mergetasks=!runcode" -Wait + $proc = Start-Process -FilePath $installerPath -ArgumentList "/silent /mergetasks=!runcode" ` + -NoNewWindow -Wait -PassThru + if ($proc.ExitCode -ne 0) { + throw "VS Code installer exited with code $($proc.ExitCode)" + } Write-Host " [OK] VS Code installed" } finally { - Remove-Item -Recurse -Force $TmpDir + if ($tmpDir -and (Test-Path $tmpDir.FullName)) { + Remove-Item -Recurse -Force $tmpDir.FullName -ErrorAction SilentlyContinue + } } } diff --git a/README.md b/README.md index 36eac07e..d8399125 100644 --- a/README.md +++ b/README.md @@ -2,43 +2,96 @@ ## Usage -### Remote (end-user install) +### MacOS ```bash -# Install -curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev/Core/Orchestration/install_all_macOS.sh | bash +# Install (everything) +export PS_REPO_URL="https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main"; curl -fsSL "$PS_REPO_URL/Core/Orchestration/install_all_macOS.sh" | bash -# Uninstall -curl -fsSL https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev/Core/Orchestration/uninstall_all_macOS.sh | bash ``` -### Local development +```bash +# uninstall (everything) + +export PS_REPO_URL="https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main"; curl -fsSL "$PS_REPO_URL/Core/Orchestration/uninstall_all_macOS.sh" | bash +``` + +### Windows + +#### Install (everything) +```powershell + +$env:PS_REPO_URL = "https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main"; irm "$env:PS_REPO_URL/Core/Orchestration/install_all_windows.ps1" | iex +``` + +#### uninstall (everything) -Set `PS_REPO_URL` to your local repo and run any script directly: +```powershell + +$env:PS_REPO_URL = "https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/main"; irm "$env:PS_REPO_URL/Core/Orchestration/uninstall_all_windows.ps1" | iex +``` + +# Dev + +### MacOS ```bash -export PS_REPO_URL="file://$PWD" -bash Core/Orchestration/install_all_macOS.sh -bash Core/VsCode/config/settings_macOS.sh -bash Core/VsCode/config/extensions_macOS.sh +# Install (everything) +export PS_REPO_URL="https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev"; curl -fsSL "$PS_REPO_URL/Core/Orchestration/install_all_macOS.sh" | bash + ``` -### How it works +```bash +# uninstall (everything) + +export PS_REPO_URL="https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev"; curl -fsSL "$PS_REPO_URL/Core/Orchestration/uninstall_all_macOS.sh" | bash +``` + +### Windows + +#### Install (everything) +```powershell + +$env:PS_REPO_URL = "https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev"; irm "$env:PS_REPO_URL/Core/Orchestration/install_all_windows.ps1" | iex +``` + +#### uninstall (everything) + +```powershell + +$env:PS_REPO_URL = "https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev"; irm "$env:PS_REPO_URL/Core/Orchestration/uninstall_all_windows.ps1" | iex +``` + + + + + +## Local development + +### macOS + +To test scripts against your local checkout instead of the remote GitHub repo, point `PS_REPO_URL` at the local repo root using a `file://` URL. All internal `curl` calls will then read from disk, so uncommitted changes are tested too. -All scripts use `curl -fsSL "$PS_REPO_URL/..."` to reference other files in the repo. +1. Open a terminal at the repo root (the directory containing this README). +2. Export the repo root as a `file://` URL: -- **Orchestration scripts** default `PS_REPO_URL` to the GitHub raw URL and export it so child scripts inherit it. -- **Child scripts** expect `PS_REPO_URL` to be set in the environment. -- For local testing, `export PS_REPO_URL="file://$PWD"` makes `curl` read from the local filesystem instead. + ```bash + export PS_REPO_URL="file://$PWD" + ``` -### Error handling + If you are in a subdirectory, use an absolute path instead: -All scripts use `set -euo pipefail`. + ```bash + export PS_REPO_URL="file:///path/to/pythonsupport-scripts" + ``` -- `-e`: Exit immediately if a command fails. -- `-u`: Using an unset variable yields an error. -- `-o pipefail`: Pipes return the error code of the first failure. +3. Run any script directly: -### TODO + ```bash + bash Core/Orchestration/install_all_macOS.sh + bash Core/VsCode/config/settings_macOS.sh + bash Core/VsCode/config/extensions_macOS.sh + bash Core/Orchestration/uninstall_all_macOS.sh + ``` -- Directory structure too complex, i.e. do we need separate install uninstall dirs? +The variable only lives in the current shell session. Open a new terminal, or run `unset PS_REPO_URL`, to go back to testing against the remote repo. diff --git a/Testing/Test-Cases.md b/Testing/Test-Cases.md deleted file mode 100644 index 7eab474a..00000000 --- a/Testing/Test-Cases.md +++ /dev/null @@ -1,3 +0,0 @@ -# Windows -- Path issues with vsCode -- Spaces in user names diff --git a/Utils/Conda/README.md b/Utils/Conda/README.md index e69de29b..8ba7dfdb 100644 --- a/Utils/Conda/README.md +++ b/Utils/Conda/README.md @@ -0,0 +1,12 @@ +# Conda utilities + +## Uninstall Conda distributions (Windows) + +`uninstall_Windows.ps1` removes detected Conda plus common Miniforge, Miniconda, +Anaconda, and Mambaforge installations for the current user and the machine. It +also removes the current user's `.condarc` and `.conda` data. Machine-wide +installations may require running PowerShell as Administrator. + +```powershell +irm https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev/Utils/Conda/uninstall_Windows.ps1 | iex +``` diff --git a/Utils/Conda/uninstall_Windows.ps1 b/Utils/Conda/uninstall_Windows.ps1 index e69de29b..b927764c 100644 --- a/Utils/Conda/uninstall_Windows.ps1 +++ b/Utils/Conda/uninstall_Windows.ps1 @@ -0,0 +1,227 @@ +# @doc +# @name: Conda Distribution Uninstall (Windows) +# @description: Uninstall all detected Conda distributions and remove Conda user data +# @category: Utilities +# @usage: powershell -File Utils/Conda/uninstall_Windows.ps1 +# @requirements: Windows, PowerShell 5.1+ +# @notes: Removes positively identified per-user and machine-wide Miniforge, +# Miniconda, Anaconda, and Mambaforge installations. Administrator rights may +# be required for machine-wide installations. +# @/doc + +$ErrorActionPreference = "Stop" + +if ([string]::IsNullOrWhiteSpace($env:USERPROFILE)) { + throw "USERPROFILE is not set; refusing to resolve Conda uninstall paths." +} + +$userProfileFullPath = [System.IO.Path]::GetFullPath($env:USERPROFILE) +$userProfileRoot = [System.IO.Path]::GetPathRoot($userProfileFullPath) +if ($userProfileFullPath.Equals($userProfileRoot, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Refusing to remove Conda from an unsafe user profile: $userProfileFullPath" +} + +$userProfilePath = $userProfileFullPath.TrimEnd( + [System.IO.Path]::DirectorySeparatorChar, + [System.IO.Path]::AltDirectorySeparatorChar +) +$condarcPath = Join-Path $userProfilePath ".condarc" +$condaDataPath = Join-Path $userProfilePath ".conda" +$removedSomething = $false +$installDirs = New-Object System.Collections.ArrayList +$protectedPaths = New-Object System.Collections.ArrayList + +$protectedPathValues = @( + [System.IO.Path]::GetPathRoot($userProfilePath), + $userProfilePath, + $env:SystemRoot, + $env:windir, + $env:ProgramData, + $env:ProgramFiles, + [System.Environment]::GetEnvironmentVariable("ProgramFiles(x86)") +) +foreach ($path in $protectedPathValues) { + if (-not [string]::IsNullOrWhiteSpace($path)) { + [void]$protectedPaths.Add( + [System.IO.Path]::GetFullPath($path).TrimEnd( + [System.IO.Path]::DirectorySeparatorChar, + [System.IO.Path]::AltDirectorySeparatorChar + ) + ) + } +} + +function Add-CondaInstallCandidate { + param( + [Parameter(Mandatory = $true)][string]$Path, + [Parameter(Mandatory = $true)][AllowEmptyCollection()][System.Collections.ArrayList]$Candidates + ) + + $fullPath = [System.IO.Path]::GetFullPath($Path).TrimEnd( + [System.IO.Path]::DirectorySeparatorChar, + [System.IO.Path]::AltDirectorySeparatorChar + ) + if ($protectedPaths | Where-Object { $_.Equals($fullPath, [System.StringComparison]::OrdinalIgnoreCase) }) { + Write-Host " [WARNING] Skipping protected Conda path: $fullPath" -ForegroundColor Yellow + return + } + + if (Test-Path $fullPath) { + $item = Get-Item -Path $fullPath -Force + if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { + Write-Host " [WARNING] Skipping Conda path that is a symbolic link or junction: $fullPath" -ForegroundColor Yellow + return + } + + if (-not (Test-CondaInstallRoot -Path $fullPath) -and + -not ([System.IO.Path]::GetFileName($fullPath) -ieq "miniforge3-dtu")) { + Write-Host " [WARNING] Skipping directory without Conda installation markers: $fullPath" -ForegroundColor Yellow + return + } + } + + if (-not ($Candidates | Where-Object { $_.Equals($fullPath, [System.StringComparison]::OrdinalIgnoreCase) })) { + [void]$Candidates.Add($fullPath) + } +} + +function Test-CondaInstallRoot { + param([Parameter(Mandatory = $true)][string]$Path) + + if (-not (Test-Path $Path -PathType Container)) { + return $false + } + + $hasMetadata = Test-Path (Join-Path $Path "conda-meta") -PathType Container + $hasExecutable = (Test-Path (Join-Path $Path "Scripts\conda.exe") -PathType Leaf) -or + (Test-Path (Join-Path $Path "condabin\conda.bat") -PathType Leaf) + $hasUninstaller = $null -ne (Get-ChildItem -Path $Path -Filter "Uninstall-*.exe" -File ` + -ErrorAction SilentlyContinue | Select-Object -First 1) + return ($hasUninstaller -or ($hasMetadata -and $hasExecutable)) +} + +$commonInstallNames = @( + "miniforge3-dtu", + "miniforge3", + "miniforge", + "miniconda3", + "miniconda", + "anaconda3", + "anaconda", + "mambaforge" +) +$searchRoots = @( + $userProfilePath, + $env:ProgramData, + $env:ProgramFiles, + [System.Environment]::GetEnvironmentVariable("ProgramFiles(x86)") +) +foreach ($searchRoot in $searchRoots) { + if ([string]::IsNullOrWhiteSpace($searchRoot)) { + continue + } + foreach ($name in $commonInstallNames) { + Add-CondaInstallCandidate -Path (Join-Path $searchRoot $name) -Candidates $installDirs + } +} + +# Discover custom-named Conda roots directly below standard install roots. +foreach ($searchRoot in $searchRoots) { + if ([string]::IsNullOrWhiteSpace($searchRoot) -or -not (Test-Path $searchRoot -PathType Container)) { + continue + } + Get-ChildItem -Path $searchRoot -Directory -ErrorAction SilentlyContinue | ForEach-Object { + if (Test-CondaInstallRoot -Path $_.FullName) { + Add-CondaInstallCandidate -Path $_.FullName -Candidates $installDirs + } + } +} + +# Include registered installations with a usable InstallLocation. +$registryRoots = @( + "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall", + "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", + "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" +) +foreach ($registryRoot in $registryRoots) { + if (-not (Test-Path $registryRoot)) { + continue + } + Get-ItemProperty -Path "$registryRoot\*" -ErrorAction SilentlyContinue | Where-Object { + $_.DisplayName -match "(?i)(Anaconda|Miniconda|Miniforge|Mambaforge)" + } | ForEach-Object { + if (-not [string]::IsNullOrWhiteSpace($_.InstallLocation)) { + Add-CondaInstallCandidate -Path $_.InstallLocation -Candidates $installDirs + } + } +} + +$condaCommand = Get-Command conda -ErrorAction SilentlyContinue +if ($condaCommand) { + try { + $condaInvoker = if ([string]::IsNullOrWhiteSpace($condaCommand.Source)) { + $condaCommand.Name + } else { + $condaCommand.Source + } + $detectedBase = (& $condaInvoker info --base 2>$null | Select-Object -First 1).Trim() + if (-not [string]::IsNullOrWhiteSpace($detectedBase)) { + if (Test-CondaInstallRoot -Path $detectedBase) { + Add-CondaInstallCandidate -Path $detectedBase -Candidates $installDirs + } else { + Write-Host " [WARNING] Ignoring detected Conda base without installation markers: $detectedBase" -ForegroundColor Yellow + } + } + } catch { + Write-Host " [WARNING] Could not query the active Conda installation: $($_.Exception.Message)" -ForegroundColor Yellow + } +} + +Write-Host "=== Uninstalling Conda distributions ===`n" + +foreach ($installDir in $installDirs) { + if (-not (Test-Path $installDir)) { + continue + } + + Write-Host " Found Conda installation at $installDir" + $uninstaller = Get-ChildItem -Path $installDir -Filter "Uninstall-*.exe" -File -ErrorAction SilentlyContinue | + Select-Object -First 1 + + if ($uninstaller) { + Write-Host " Running $($uninstaller.Name)..." + $proc = Start-Process -FilePath $uninstaller.FullName -ArgumentList "/S" -Wait -PassThru + if ($proc.ExitCode -ne 0) { + throw "$($uninstaller.Name) exited with code $($proc.ExitCode)" + } + Write-Host " [OK] Conda uninstaller completed" + $removedSomething = $true + } else { + Write-Host " [WARNING] Conda uninstaller not found; removing the installation directory." -ForegroundColor Yellow + } + + if (Test-Path $installDir) { + Remove-Item -Path $installDir -Recurse -Force + Write-Host " [OK] Removed $installDir" + $removedSomething = $true + } +} + +if (Test-Path $condarcPath) { + Remove-Item -Path $condarcPath -Force + Write-Host " [OK] Conda configuration removed" + $removedSomething = $true +} + +if (Test-Path $condaDataPath) { + Remove-Item -Path $condaDataPath -Recurse -Force + Write-Host " [OK] Conda user data removed" + $removedSomething = $true +} + +Write-Host "" +if ($removedSomething) { + Write-Host "=== Conda uninstall complete! ===" +} else { + Write-Host "No changes made - no Conda installations found." +} diff --git a/Utils/Health/README.md b/Utils/Health/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Utils/Health/check_Windows.ps1 b/Utils/Health/check_Windows.ps1 deleted file mode 100644 index e69de29b..00000000 diff --git a/Utils/Health/check_macOS.sh b/Utils/Health/check_macOS.sh deleted file mode 100644 index e69de29b..00000000 diff --git a/Utils/Python/README.md b/Utils/Python/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/Utils/Python/uninstall_windows.ps1 b/Utils/Python/uninstall_windows.ps1 deleted file mode 100644 index e69de29b..00000000 diff --git a/Utils/VsCode/README.md b/Utils/VsCode/README.md index e69de29b..b6442660 100644 --- a/Utils/VsCode/README.md +++ b/Utils/VsCode/README.md @@ -0,0 +1,10 @@ +# VS Code utilities + +## Uninstall completely (Windows) + +`uninstall_Windows.ps1` runs the per-user VS Code uninstaller and removes the +current user's `%APPDATA%\Code` and `%USERPROFILE%\.vscode` data. + +```powershell +irm https://raw.githubusercontent.com/dtudk/pythonsupport-scripts/dev/Utils/VsCode/uninstall_Windows.ps1 | iex +``` diff --git a/Utils/VsCode/uninstall_Windows.ps1 b/Utils/VsCode/uninstall_Windows.ps1 index e69de29b..4c2aaf31 100644 --- a/Utils/VsCode/uninstall_Windows.ps1 +++ b/Utils/VsCode/uninstall_Windows.ps1 @@ -0,0 +1,60 @@ +# @doc +# @name: VS Code Uninstall (Windows) +# @description: Uninstall the DTU user installation of VS Code and remove its user data +# @category: Utilities +# @usage: powershell -File Utils/VsCode/uninstall_Windows.ps1 +# @requirements: Windows, PowerShell 5.1+ +# @notes: Runs the VS Code user uninstaller when present, then removes settings, +# extensions, and remaining files from the current user's profile. +# @/doc + +$ErrorActionPreference = "Stop" + +$appPath = Join-Path $env:LOCALAPPDATA "Programs\Microsoft VS Code" +$uninstallerPath = Join-Path $appPath "unins000.exe" +$configPath = Join-Path $env:APPDATA "Code" +$userDataPath = Join-Path $env:USERPROFILE ".vscode" +$removedSomething = $false + +Write-Host "=== Uninstalling VS Code ===`n" + +if (Test-Path $uninstallerPath -PathType Leaf) { + Write-Host " Running the VS Code uninstaller..." + $proc = Start-Process -FilePath $uninstallerPath ` + -ArgumentList "/VERYSILENT /NORESTART /SUPPRESSMSGBOXES" ` + -Wait -PassThru + if ($proc.ExitCode -ne 0) { + throw "VS Code uninstaller exited with code $($proc.ExitCode)" + } + Write-Host " [OK] VS Code uninstaller completed" + $removedSomething = $true +} elseif (Test-Path $appPath) { + Write-Host " [WARNING] VS Code uninstaller not found; removing the remaining application files." -ForegroundColor Yellow +} else { + Write-Host " VS Code application not found." +} + +if (Test-Path $appPath) { + Remove-Item -Path $appPath -Recurse -Force + Write-Host " [OK] Application files removed" + $removedSomething = $true +} + +if (Test-Path $configPath) { + Remove-Item -Path $configPath -Recurse -Force + Write-Host " [OK] Settings and application data removed" + $removedSomething = $true +} + +if (Test-Path $userDataPath) { + Remove-Item -Path $userDataPath -Recurse -Force + Write-Host " [OK] Extensions and user data removed" + $removedSomething = $true +} + +Write-Host "" +if ($removedSomething) { + Write-Host "=== VS Code uninstall complete! ===" +} else { + Write-Host "No changes made - VS Code not found." +} diff --git a/Utils/progress.ps1 b/Utils/progress.ps1 new file mode 100644 index 00000000..c4543960 --- /dev/null +++ b/Utils/progress.ps1 @@ -0,0 +1,115 @@ +#!/usr/bin/env pwsh +# @doc +# @name: Progress UI (Windows) +# @description: Progress wrapper used by the Windows orchestration scripts +# @category: Utils +# @usage: . ./Utils/progress.ps1 (defines the `progress` function) +# @requirements: PowerShell 5.1+ +# @notes: Minimal mirror of Utils/progress.sh. The full-screen TUI (typewriter +# intro, spinner, scrolling log box) is intentionally left out for now and +# can be developed in parallel - the `progress` call signature is final. +# @/doc + +$ErrorActionPreference = "Stop" + +# Mirror of PROGRESS_LOG_FILE in progress.sh (default: $env:TEMP\dtu_log.txt) +$script:PROGRESS_LOG_FILE = $env:PROGRESS_LOG_FILE +if (-not $script:PROGRESS_LOG_FILE) { + $tmpBase = if ($env:TEMP) { $env:TEMP } else { [System.IO.Path]::GetTempPath() } + $script:PROGRESS_LOG_FILE = Join-Path $tmpBase "dtu_log.txt" +} + +function script:Show-ProgressIntro { + # Same wording as progress_intro_lines() in progress.sh + # TODO(parallel TUI workstream): typewriter effect like the macOS version + Write-Host " Welcome to the DTU Python Installation Support setup." -ForegroundColor Cyan + Write-Host "" + Write-Host " This setup is for first-year DTU students." + Write-Host " It installs Conda, Python, the required course packages," + Write-Host " and Visual Studio Code." + Write-Host "" + Write-Host " It is intended for courses such as Mathematics 1a," + Write-Host " Mathematics 1b, Statistics, Physics," + Write-Host " and Computer Programming." + Write-Host "" + Write-Host " You do not need much coding experience to complete this setup." + Write-Host " The installation will begin in a moment." + Write-Host "" +} + +# Params (same call shape as the bash version: progress "msg" ): +# - Message: str +# - EstimatedMinutes: int +# - Command: scriptblock +function global:progress { + param( + [Parameter(Mandatory = $true, Position = 0)][string]$Message, + [Parameter(Mandatory = $true, Position = 1)][int]$EstimatedMinutes, + [Parameter(Mandatory = $true, Position = 2)][scriptblock]$Command + ) + + $logFile = $script:PROGRESS_LOG_FILE + $firstStep = -not (Test-Path $logFile) + + Add-Content -Path $logFile -Value "[DTULOG]: $Message ($(Get-Date))" + + if ($firstStep) { + Show-ProgressIntro + } + + $duration = if ($EstimatedMinutes -eq 1) { "1 minute" } else { "$EstimatedMinutes minutes" } + Write-Host "[$Message]" + Write-Host " Please do not interrupt this step - it may take up to $duration." + + # Transcript captures *everything* printed to the console (including + # Write-Host) into the log file. If the host does not support + # transcription, fall back to teeing the pipeline output only. + $transcriptStarted = $false + try { + Start-Transcript -Path $logFile -Append | Out-Null + $transcriptStarted = $true + } catch { } + + $failed = $false + $errorText = "" + try { + if ($transcriptStarted) { + & $Command + } else { + & $Command 2>&1 | Tee-Object -FilePath $logFile -Append + } + } catch { + $failed = $true + $errorText = "$_" + } finally { + if ($transcriptStarted) { + try { Stop-Transcript | Out-Null } catch { } + } + } + + Write-Host $Message + + if ($failed) { + Add-Content -Path $logFile -Value "[DTULOG]: failure ($errorText)" + Write-Host " ┗━ Failure." -ForegroundColor Red + Write-Host " Contact us by email or Discord:" + Write-Host " pythonsupport@dtu.dk | https://discord.gg/h8EVaV9ShP" + Write-Host " https://pythonsupport.dtu.dk/#reach-us" + Write-Host " Please include the file '$logFile'." -ForegroundColor Yellow + throw "Step failed: $Message" + } + + Add-Content -Path $logFile -Value "[DTULOG]: success" + Write-Host " ┗━ Success!" -ForegroundColor Green +} + +# Self-test: only runs when the file is executed directly, not when it is +# dot-sourced or loaded via Invoke-Expression (mirror of the BASH_SOURCE guard +# in progress.sh). +if (($MyInvocation.MyCommand -is [System.Management.Automation.ExternalScriptInfo]) -and + ($MyInvocation.InvocationName -ne '.')) { + progress "Step 1/1: Installing XYZ" 15 { + 1..6 | ForEach-Object { Write-Host $_; Start-Sleep -Milliseconds 500 } + throw "demo failure" + } +} diff --git a/macOS_local.sh b/macOS_local.sh new file mode 100644 index 00000000..9d79cad9 --- /dev/null +++ b/macOS_local.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +export PS_REPO_URL="file://$SCRIPT_DIR" + +export PS_FORGE_URL="file://$SCRIPT_DIR/release_assets/dtu-miniconda" + + + +echo $PS_REPO_URL + +bash "$SCRIPT_DIR/Core/Orchestration/install_all_macOS.sh" diff --git a/release_assets/README.md b/release_assets/README.md new file mode 100644 index 00000000..e36d86ae --- /dev/null +++ b/release_assets/README.md @@ -0,0 +1,8 @@ + + +```bash +gh release download --repo dtudk/pythonsupport-forge --dir + release_assets/dtu-miniconda/ --clobber --pattern '*' + + + ```