From 46552609863f2c3b76f9b8f9761f8b3163264a53 Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Fri, 10 Jul 2026 11:25:25 +0000 Subject: [PATCH] Add .NET SDK installation phase --- docs/BUILD-LOG.md | 49 +++++++ docs/REBUILD-GUIDE.md | 24 +++- install/README.md | 15 ++ install/bootstrap.sh | 1 - install/lib/packages.sh | 1 - install/lib/services.sh | 1 - install/lib/validation.sh | 1 - install/manifests/baseline-commands.txt | 1 - install/phases/10-dotnet.sh | 178 ++++++++++++++++++++++++ install/phases/README.md | 10 +- scripts/development/README.md | 1 - scripts/system/validate-host.sh | 1 - 12 files changed, 272 insertions(+), 11 deletions(-) create mode 100755 install/phases/10-dotnet.sh diff --git a/docs/BUILD-LOG.md b/docs/BUILD-LOG.md index ad54751..f699896 100644 --- a/docs/BUILD-LOG.md +++ b/docs/BUILD-LOG.md @@ -10,6 +10,55 @@ This file records chronological Atlas build and infrastructure milestones. Do no ### Phase +Phase 2 .NET development platform. + +### Objective + +Install the .NET SDKs required for Atlas and PlotDirector development. + +### Changes + +- Added `install/phases/10-dotnet.sh`. +- Installed .NET 8 SDK `8.0.128`. +- Installed .NET 10 SDK `10.0.109`. +- Used the official Ubuntu .NET backports PPA for .NET 8. +- Used the built-in Ubuntu `resolute-updates` and `resolute-security` package sources for .NET 10. +- Did not install .NET 9, SQL Server, nginx, or Docker. + +### Validation + +- `bash -n install/phases/10-dotnet.sh` passed. +- `install/phases/10-dotnet.sh --help` passed. +- `install/phases/10-dotnet.sh --dry-run` passed. +- `install/phases/10-dotnet.sh` installed the missing SDKs. +- A second `install/phases/10-dotnet.sh` run detected both SDKs already installed. +- `dotnet --info`, `dotnet --list-sdks`, and `dotnet --list-runtimes` passed. +- Temporary .NET 8 and .NET 10 console projects built and ran outside the repository. +- `scripts/system/validate-host.sh` passed after installation. + +### Problems + +- Initial package-candidate detection in `10-dotnet.sh` was corrected after it failed under `pipefail` before installing SDK packages. +- No unresolved .NET installation problems recorded. + +### Decisions + +- Use Ubuntu package feeds for .NET SDK installation on Ubuntu 26.04. + +### Git Commit + +Not yet committed. + +### Operator + +Codex. + +### Date + +2026-07-10 + +### Phase + Phase 2A. ### Objective diff --git a/docs/REBUILD-GUIDE.md b/docs/REBUILD-GUIDE.md index 16a289c..749e3a7 100644 --- a/docs/REBUILD-GUIDE.md +++ b/docs/REBUILD-GUIDE.md @@ -8,7 +8,8 @@ This guide describes the planned rebuild path for Atlas. At Phase 1, the reposit - Shared installation framework: present. - System discovery script: present. - Host validation script: present. -- Component installation scripts: not yet created. +- .NET installation script: present. +- Other component installation scripts: not yet created. - Configuration templates: not yet created. - Operational scripts: not yet created. - Smoke tests: not yet created. @@ -112,8 +113,9 @@ Planned bootstrap flow: 4. Run `scripts/system/discover-system.sh`. 5. Run `scripts/system/validate-host.sh`. 6. Run `install/bootstrap.sh --dry-run`. -7. Run ordered installation scripts from `install/phases/` when they exist. -8. Run smoke tests from `tests/` when they exist. +7. Run `install/phases/10-dotnet.sh` when .NET SDKs are required. +8. Run additional ordered installation scripts from `install/phases/` when they exist. +9. Run smoke tests from `tests/` when they exist. Bootstrap command sequence is `TODO: Confirm during the relevant implementation phase.` @@ -137,6 +139,22 @@ These scripts do not exist yet. When created, each script must: - Document rollback expectations. - Include or reference validation steps. +Current .NET installation command: + +```bash +install/phases/10-dotnet.sh +``` + +Current .NET verification commands: + +```bash +dotnet --info +dotnet --list-sdks +dotnet --list-runtimes +``` + +The current installed SDKs are .NET 8 SDK `8.0.128` and .NET 10 SDK `10.0.109`. + ## 8. Validation Minimum rebuild validation should confirm: diff --git a/install/README.md b/install/README.md index a013ed2..ab7d927 100644 --- a/install/README.md +++ b/install/README.md @@ -48,6 +48,21 @@ manifests/ phases/ ``` +Current Phase 2 installer: + +```bash +install/phases/10-dotnet.sh +``` + +The .NET installer installs only the required .NET SDK packages and verifies them with `dotnet --list-sdks`. + +Current installed SDKs: + +- .NET 8 SDK `8.0.128` +- .NET 10 SDK `10.0.109` + +.NET 8 is installed from the official Ubuntu .NET backports PPA. .NET 10 is installed from the built-in Ubuntu package feeds. + Runtime output belongs outside the repository: - `/var/log/atlas/install/` diff --git a/install/bootstrap.sh b/install/bootstrap.sh index 06268da..d6eda0f 100755 --- a/install/bootstrap.sh +++ b/install/bootstrap.sh @@ -98,4 +98,3 @@ else fi atlas_log_ok "Bootstrap scaffold completed" - diff --git a/install/lib/packages.sh b/install/lib/packages.sh index f184663..e3396f0 100644 --- a/install/lib/packages.sh +++ b/install/lib/packages.sh @@ -41,4 +41,3 @@ atlas_pending_update_count() { atlas_reboot_required() { [[ -f /var/run/reboot-required ]] } - diff --git a/install/lib/services.sh b/install/lib/services.sh index aa0208a..d371ede 100644 --- a/install/lib/services.sh +++ b/install/lib/services.sh @@ -55,4 +55,3 @@ atlas_service_restart() { atlas_log_info "Restarting service ${service_name}" atlas_sudo_run systemctl restart "$service_name" } - diff --git a/install/lib/validation.sh b/install/lib/validation.sh index 8aaf01d..a56eebc 100644 --- a/install/lib/validation.sh +++ b/install/lib/validation.sh @@ -65,4 +65,3 @@ atlas_gib_to_bytes() { local gib="$1" awk -v gib="$gib" 'BEGIN {printf "%.0f\n", gib * 1024 * 1024 * 1024}' } - diff --git a/install/manifests/baseline-commands.txt b/install/manifests/baseline-commands.txt index ac39798..dba4e58 100644 --- a/install/manifests/baseline-commands.txt +++ b/install/manifests/baseline-commands.txt @@ -29,4 +29,3 @@ awk|required|Text processing sed|required|Text processing grep|required|Text searching sudo|required|Non-interactive privileged command execution - diff --git a/install/phases/10-dotnet.sh b/install/phases/10-dotnet.sh new file mode 100755 index 0000000..3674861 --- /dev/null +++ b/install/phases/10-dotnet.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +REPO_ROOT="$(cd "${INSTALL_DIR}/.." && pwd)" + +# shellcheck source=install/lib/common.sh +source "${INSTALL_DIR}/lib/common.sh" +# shellcheck source=install/lib/logging.sh +source "${INSTALL_DIR}/lib/logging.sh" +# shellcheck source=install/lib/packages.sh +source "${INSTALL_DIR}/lib/packages.sh" + +atlas_set_script_name "10-dotnet.sh" +atlas_install_error_trap + +REQUIRED_OS_ID="ubuntu" +REQUIRED_OS_VERSION="26.04" +DOTNET_BACKPORTS_PPA="ppa:dotnet/backports" +DOTNET_REQUIRED_PACKAGES=("dotnet-sdk-8.0" "dotnet-sdk-10.0") + +usage() { + cat <<'USAGE' +Usage: install/phases/10-dotnet.sh [options] + +Install the Atlas .NET development SDKs from official Ubuntu package feeds. + +Required SDKs: + dotnet-sdk-8.0 + dotnet-sdk-10.0 + +Options: + --help Show this help text. + --version Show script/framework version. + --dry-run Show intended actions without making changes. + --verbose Print additional diagnostic output. + +USAGE +} + +while [[ "$#" -gt 0 ]]; do + case "$1" in + --help) + usage + exit "${ATLAS_EXIT_OK}" + ;; + --version) + atlas_print_version + exit "${ATLAS_EXIT_OK}" + ;; + --dry-run) + atlas_enable_dry_run + ;; + --verbose) + atlas_enable_verbose + ;; + *) + atlas_usage_error "Unknown option: $1" + exit "${ATLAS_EXIT_USAGE}" + ;; + esac + shift +done + +load_os_release() { + if [[ ! -r /etc/os-release ]]; then + atlas_log_error "/etc/os-release is not readable" + exit "${ATLAS_EXIT_PREREQ}" + fi + # shellcheck disable=SC1091 + . /etc/os-release + if [[ "${ID:-}" != "${REQUIRED_OS_ID}" || "${VERSION_ID:-}" != "${REQUIRED_OS_VERSION}" ]]; then + atlas_log_error "Unsupported OS: ${PRETTY_NAME:-unknown}; expected Ubuntu 26.04" + exit "${ATLAS_EXIT_PREREQ}" + fi +} + +package_has_candidate() { + local package_name="$1" + local candidate + candidate="$(apt-cache policy "$package_name" 2>/dev/null | awk -F: '/Candidate:/ {gsub(/^[ \t]+/, "", $2); print $2; exit}')" + [[ -n "$candidate" && "$candidate" != "(none)" ]] +} + +backports_configured() { + grep -RqsE 'ppa\\.launchpadcontent\\.net/dotnet/backports|LP-PPA-dotnet-backports|dotnet-ubuntu-backports' /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null +} + +ensure_backports_if_needed() { + if package_has_candidate dotnet-sdk-8.0; then + atlas_log_info "dotnet-sdk-8.0 is available from configured package sources" + return 0 + fi + + if backports_configured; then + atlas_log_info "Ubuntu .NET backports repository is configured; refreshing package metadata" + atlas_sudo_run apt-get update + return 0 + fi + + atlas_log_info "dotnet-sdk-8.0 is not available; adding official Ubuntu .NET backports repository" + if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then + atlas_log_info "Dry-run mode: would run add-apt-repository -y ${DOTNET_BACKPORTS_PPA}" + atlas_log_info "Dry-run mode: would run apt-get update" + return 0 + fi + + atlas_require_command add-apt-repository + atlas_sudo_run add-apt-repository -y "${DOTNET_BACKPORTS_PPA}" + atlas_sudo_run apt-get update +} + +install_missing_sdks() { + local missing=() + local package_name + + for package_name in "${DOTNET_REQUIRED_PACKAGES[@]}"; do + if atlas_package_installed "$package_name"; then + atlas_log_ok "${package_name} is already installed" + else + missing+=("$package_name") + fi + done + + if [[ "${#missing[@]}" -eq 0 ]]; then + atlas_log_ok "All required .NET SDK packages are installed" + return 0 + fi + + for package_name in "${missing[@]}"; do + if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then + atlas_log_info "Dry-run mode: would verify package candidate for ${package_name}" + continue + fi + if ! package_has_candidate "$package_name"; then + atlas_log_error "No official configured package candidate found for ${package_name}" + exit "${ATLAS_EXIT_PREREQ}" + fi + done + + atlas_install_packages "${missing[@]}" +} + +verify_dotnet() { + atlas_require_command dotnet + dotnet --list-sdks | grep -q '^8\.' || { + atlas_log_error ".NET 8 SDK was not found after installation" + exit "${ATLAS_EXIT_VALIDATION}" + } + dotnet --list-sdks | grep -q '^10\.' || { + atlas_log_error ".NET 10 SDK was not found after installation" + exit "${ATLAS_EXIT_VALIDATION}" + } + atlas_log_ok ".NET SDK verification passed" +} + +main() { + load_os_release + atlas_require_sudo_noninteractive || { + atlas_log_error "Passwordless non-interactive sudo is required" + exit "${ATLAS_EXIT_PREREQ}" + } + atlas_require_command apt-cache + atlas_require_command apt-get + atlas_require_command dpkg-query + + ensure_backports_if_needed + install_missing_sdks + + if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then + atlas_log_info "Dry-run mode: skipping post-install dotnet verification" + else + verify_dotnet + fi +} + +main diff --git a/install/phases/README.md b/install/phases/README.md index 59384b9..d74987c 100644 --- a/install/phases/README.md +++ b/install/phases/README.md @@ -2,7 +2,7 @@ This directory is reserved for future ordered Atlas installation phases. -Phase 2A intentionally does not create speculative .NET, SQL Server, nginx, or Docker installer scripts. Future scripts should be added only when their implementation phase is approved. +Phase 2A intentionally did not create speculative .NET, SQL Server, nginx, or Docker installer scripts. Phase 2 adds the .NET installer only. ## Naming Convention @@ -18,3 +18,11 @@ Use a two-digit phase number and lower-case hyphenated component name: Each phase must source the shared framework under `install/lib/`, support dry-run behaviour where practical, and follow [../../SERVER-STANDARDS.md](../../SERVER-STANDARDS.md). +## Current Phases + +- `10-dotnet.sh`: installs the required .NET 8 and .NET 10 SDKs from official Ubuntu package sources. + +Installed SDKs after the current Phase 2 run: + +- .NET 8 SDK `8.0.128` +- .NET 10 SDK `10.0.109` diff --git a/scripts/development/README.md b/scripts/development/README.md index f1f21a8..ae035db 100644 --- a/scripts/development/README.md +++ b/scripts/development/README.md @@ -5,4 +5,3 @@ This directory is reserved for development workflow helpers that do not belong t Do not store secrets, generated logs, package caches, or one-off experiments here. Future structure is `TODO: Confirm during the relevant implementation phase.` - diff --git a/scripts/system/validate-host.sh b/scripts/system/validate-host.sh index fc289e4..31a2be6 100755 --- a/scripts/system/validate-host.sh +++ b/scripts/system/validate-host.sh @@ -270,4 +270,3 @@ fi printf '\n' atlas_validation_summary atlas_validation_exit_code -