From 045515369c3e8c9652e8582701a8a12d090f5fce Mon Sep 17 00:00:00 2001 From: HueByte Date: Tue, 24 Feb 2026 19:44:46 +0100 Subject: [PATCH 1/2] feat: add Chocolatey package and Linux/macOS install script with automated publishing --- .github/workflows/release.yml | 19 ++ docs/articles/getting-started.md | 40 +++- docs/changelog/v0.2.8.md | 6 + packaging/choco/echohub.nuspec | 26 +++ packaging/choco/tools/LICENSE.txt | 21 ++ packaging/choco/tools/VERIFICATION.txt | 17 ++ packaging/choco/tools/chocolateyInstall.ps1 | 18 ++ packaging/choco/tools/chocolateyUninstall.ps1 | 3 + scripts/install.sh | 199 ++++++++++++++++++ 9 files changed, 339 insertions(+), 10 deletions(-) create mode 100644 packaging/choco/echohub.nuspec create mode 100644 packaging/choco/tools/LICENSE.txt create mode 100644 packaging/choco/tools/VERIFICATION.txt create mode 100644 packaging/choco/tools/chocolateyInstall.ps1 create mode 100644 packaging/choco/tools/chocolateyUninstall.ps1 create mode 100644 scripts/install.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 834855c..618917d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -157,3 +157,22 @@ jobs: EchoHub-Client-linux-arm64.zip env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish Chocolatey package + if: steps.changes.outputs.src_changed == 'true' && steps.check_release.outputs.exists == 'false' + run: | + VERSION="${{ steps.version.outputs.version }}" + CHECKSUM=$(sha256sum EchoHub-Client-win-x64.zip | awk '{print $1}') + + # Stamp version and checksum into package templates + sed -i "s/__VERSION__/$VERSION/g" packaging/choco/echohub.nuspec + sed -i "s/__VERSION__/$VERSION/g" packaging/choco/tools/chocolateyInstall.ps1 + sed -i "s/__CHECKSUM64__/$CHECKSUM/g" packaging/choco/tools/chocolateyInstall.ps1 + + # Build and push the package + mkdir -p /tmp/choco-out + cd packaging/choco + choco pack echohub.nuspec --output-directory /tmp/choco-out + choco push /tmp/choco-out/echohub.*.nupkg --source https://push.chocolatey.org/ --api-key "$CHOCO_API_KEY" + env: + CHOCO_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md index 7b7bba3..86ebd87 100644 --- a/docs/articles/getting-started.md +++ b/docs/articles/getting-started.md @@ -1,12 +1,33 @@ # Getting Started -## Prerequisites +## Install the Client -- [.NET 10 SDK](https://dotnet.microsoft.com/download) +### Windows (Chocolatey) -Or grab a self-contained binary from [Releases](https://github.com/HueByte/EchoHub/releases) -- no runtime needed. +```bash +choco install echohub +``` -## Docker +### Linux / macOS + +```bash +curl -sSfL https://raw.githubusercontent.com/HueByte/EchoHub/master/scripts/install.sh | sh +``` + +To install a specific version or to a custom directory: + +```bash +curl -sSfL .../install.sh | sh -s -- --version 0.2.8 +curl -sSfL .../install.sh | sh -s -- --install-dir /opt/echohub +``` + +### Manual Download + +Grab a self-contained binary from [Releases](https://github.com/HueByte/EchoHub/releases) -- no runtime needed. + +## Host a Server + +### Docker The quickest way to host a server: @@ -17,24 +38,23 @@ docker compose up -d See the [Docker guide](docker.md) for configuration, pre-built images, and more. -## Run the Server +### From Source ```bash dotnet run --project src/EchoHub.Server ``` +Requires [.NET 10 SDK](https://dotnet.microsoft.com/download). + On first run, the server automatically: 1. Creates `appsettings.json` from the example config 2. Generates a secure JWT secret 3. Creates the SQLite database with a `#general` channel -## Run the Client - -```bash -dotnet run --project src/EchoHub.Client -``` +## Usage +After installing the client, run `echohub` (or `dotnet run --project src/EchoHub.Client` from source). Connect to a server, register an account, and start chatting. ## Connect via IRC diff --git a/docs/changelog/v0.2.8.md b/docs/changelog/v0.2.8.md index 0f7d6ea..d4c7a8b 100644 --- a/docs/changelog/v0.2.8.md +++ b/docs/changelog/v0.2.8.md @@ -21,7 +21,13 @@ - Extract `IUserService`/`UserService` — consolidate user registration, authentication, and profile management into a dedicated service, eliminating duplicated logic between `AuthController` and `ChatService` - IRC gateway now checks ban status during authentication (previously skipped) +## Distribution + +- Add Chocolatey package — `choco install echohub` for Windows users, auto-published from CI on each release +- Add Linux/macOS install script — `curl -sSfL .../install.sh | sh` with automatic OS/arch detection + ## CI - Add Docker workflow — builds and pushes multi-arch (`amd64`/`arm64`) server image to GHCR on release - Add `linux-arm64` builds to release pipeline — server and client binaries for ARM Linux (Raspberry Pi, cloud ARM instances) +- Automate Chocolatey package publishing in release workflow (checksum calculation, pack, push) diff --git a/packaging/choco/echohub.nuspec b/packaging/choco/echohub.nuspec new file mode 100644 index 0000000..e4415c7 --- /dev/null +++ b/packaging/choco/echohub.nuspec @@ -0,0 +1,26 @@ + + + + echohub + __VERSION__ + EchoHub + Hue + Hue + false + https://github.com/HueByte/EchoHub/blob/master/LICENSE + https://github.com/HueByte/EchoHub + https://github.com/HueByte/EchoHub + https://huebyte.github.io/EchoHub + https://github.com/HueByte/EchoHub/issues + https://github.com/HueByte/EchoHub/tree/master/packaging/choco + https://raw.githubusercontent.com/HueByte/EchoHub/master/assets/hue_icon.png + EchoHub is a decentralized IRC-style chat application with a terminal user interface (TUI). Connect to any EchoHub server, join channels, and chat — all from your terminal. Features include SignalR real-time messaging, file sharing, custom themes, and IRC gateway compatibility. + Decentralized terminal chat client with IRC gateway support + chat irc decentralized tui terminal signalr echohub + https://huebyte.github.io/EchoHub/changelog/v__VERSION__.html + Copyright (c) 2026 Hue + + + + + diff --git a/packaging/choco/tools/LICENSE.txt b/packaging/choco/tools/LICENSE.txt new file mode 100644 index 0000000..b28e337 --- /dev/null +++ b/packaging/choco/tools/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Hue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packaging/choco/tools/VERIFICATION.txt b/packaging/choco/tools/VERIFICATION.txt new file mode 100644 index 0000000..62b31d3 --- /dev/null +++ b/packaging/choco/tools/VERIFICATION.txt @@ -0,0 +1,17 @@ +VERIFICATION + +To verify the package contents: + +1. Download the official release from: + https://github.com/HueByte/EchoHub/releases + +2. Download: EchoHub-Client-win-x64.zip + +3. Calculate the SHA256 checksum: + - PowerShell: Get-FileHash EchoHub-Client-win-x64.zip -Algorithm SHA256 + - Linux/macOS: sha256sum EchoHub-Client-win-x64.zip + +4. Compare with the checksum in chocolateyInstall.ps1 (checksum64 value). + +LICENSE: MIT License - see LICENSE.txt in this directory or +https://github.com/HueByte/EchoHub/blob/master/LICENSE diff --git a/packaging/choco/tools/chocolateyInstall.ps1 b/packaging/choco/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000..a483c19 --- /dev/null +++ b/packaging/choco/tools/chocolateyInstall.ps1 @@ -0,0 +1,18 @@ +$ErrorActionPreference = 'Stop' + +$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$version = $env:chocolateyPackageVersion + +$packageArgs = @{ + packageName = $env:chocolateyPackageName + unzipLocation = $toolsDir + url64bit = "https://github.com/HueByte/EchoHub/releases/download/v$version/EchoHub-Client-win-x64.zip" + checksum64 = '__CHECKSUM64__' + checksumType64 = 'sha256' +} + +Install-ChocolateyZipPackage @packageArgs + +# Create a shim so 'echohub' is available on PATH +$exePath = Join-Path $toolsDir 'client-win-x64' 'EchoHub.Client.exe' +Install-BinFile -Name 'echohub' -Path $exePath diff --git a/packaging/choco/tools/chocolateyUninstall.ps1 b/packaging/choco/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000..0e8b0d9 --- /dev/null +++ b/packaging/choco/tools/chocolateyUninstall.ps1 @@ -0,0 +1,3 @@ +$ErrorActionPreference = 'Stop' + +Uninstall-BinFile -Name 'echohub' diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 0000000..82989ad --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,199 @@ +#!/bin/sh +# EchoHub Client Installer +# Usage: curl -sSfL https://raw.githubusercontent.com/HueByte/EchoHub/master/scripts/install.sh | sh +# +# Options (pass as arguments or environment variables): +# --version X.Y.Z Install a specific version (default: latest) +# --install-dir DIR Install to a custom directory +# --help Show this help message + +set -eu + +REPO="HueByte/EchoHub" +BINARY_NAME="echohub" +INSTALL_DIR="" +VERSION="" + +# ── Argument parsing ────────────────────────────────────────────────────── + +while [ $# -gt 0 ]; do + case "$1" in + --version) + VERSION="$2" + shift 2 + ;; + --install-dir) + INSTALL_DIR="$2" + shift 2 + ;; + --help) + sed -n '2,8p' "$0" 2>/dev/null || true + echo "" + echo " curl -sSfL https://raw.githubusercontent.com/$REPO/master/scripts/install.sh | sh" + echo " curl ... | sh -s -- --version 0.2.8" + echo " curl ... | sh -s -- --install-dir /opt/echohub" + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +# ── Platform detection ──────────────────────────────────────────────────── + +detect_os() { + case "$(uname -s)" in + Linux*) echo "linux" ;; + Darwin*) echo "osx" ;; + *) + echo "Error: Unsupported operating system: $(uname -s)" >&2 + echo "EchoHub supports Linux and macOS. For Windows, use: choco install echohub" >&2 + exit 1 + ;; + esac +} + +detect_arch() { + case "$(uname -m)" in + x86_64|amd64) echo "x64" ;; + aarch64|arm64) echo "arm64" ;; + *) + echo "Error: Unsupported architecture: $(uname -m)" >&2 + echo "EchoHub supports x64 and arm64." >&2 + exit 1 + ;; + esac +} + +# ── Version resolution ──────────────────────────────────────────────────── + +resolve_version() { + if [ -n "$VERSION" ]; then + echo "$VERSION" + return + fi + + # Fetch latest release tag from GitHub API + if command -v curl >/dev/null 2>&1; then + tag=$(curl -sSf "https://api.github.com/repos/$REPO/releases/latest" \ + | grep '"tag_name"' | head -1 | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v\?\([^"]*\)".*/\1/') + elif command -v wget >/dev/null 2>&1; then + tag=$(wget -qO- "https://api.github.com/repos/$REPO/releases/latest" \ + | grep '"tag_name"' | head -1 | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v\?\([^"]*\)".*/\1/') + else + echo "Error: curl or wget is required to detect the latest version." >&2 + echo "Install curl/wget or specify a version with --version X.Y.Z" >&2 + exit 1 + fi + + if [ -z "$tag" ]; then + echo "Error: Could not determine latest version from GitHub." >&2 + exit 1 + fi + + echo "$tag" +} + +# ── Install directory resolution ────────────────────────────────────────── + +resolve_install_dir() { + if [ -n "$INSTALL_DIR" ]; then + echo "$INSTALL_DIR" + return + fi + + # Prefer /usr/local/bin if writable, otherwise ~/.local/bin + if [ -w "/usr/local/bin" ]; then + echo "/usr/local/bin" + else + local_bin="$HOME/.local/bin" + mkdir -p "$local_bin" + echo "$local_bin" + fi +} + +# ── Download helper ─────────────────────────────────────────────────────── + +download() { + url="$1" + output="$2" + + if command -v curl >/dev/null 2>&1; then + curl -sSfL "$url" -o "$output" + elif command -v wget >/dev/null 2>&1; then + wget -qO "$output" "$url" + else + echo "Error: curl or wget is required." >&2 + exit 1 + fi +} + +# ── Main ────────────────────────────────────────────────────────────────── + +main() { + os=$(detect_os) + arch=$(detect_arch) + version=$(resolve_version) + install_dir=$(resolve_install_dir) + + artifact="EchoHub-Client-${os}-${arch}.zip" + url="https://github.com/$REPO/releases/download/v${version}/${artifact}" + + echo "EchoHub Installer" + echo " Version: v${version}" + echo " Platform: ${os}-${arch}" + echo " Install to: ${install_dir}" + echo "" + + # Create temp directory with cleanup trap + tmpdir=$(mktemp -d) + trap 'rm -rf "$tmpdir"' EXIT + + echo "Downloading ${artifact}..." + download "$url" "$tmpdir/echohub.zip" + + echo "Extracting..." + if command -v unzip >/dev/null 2>&1; then + unzip -qo "$tmpdir/echohub.zip" -d "$tmpdir/extract" + else + echo "Error: unzip is required to extract the archive." >&2 + echo "Install it with: apt install unzip / brew install unzip" >&2 + exit 1 + fi + + # The ZIP contains a client-{os}-{arch}/ subdirectory + src_dir="$tmpdir/extract/client-${os}-${arch}" + if [ ! -d "$src_dir" ]; then + # Fallback: look for any directory containing the binary + src_dir=$(find "$tmpdir/extract" -name "EchoHub.Client" -type f -printf '%h' -quit 2>/dev/null || true) + if [ -z "$src_dir" ]; then + echo "Error: Could not find EchoHub.Client binary in the archive." >&2 + exit 1 + fi + fi + + # Install the binary + mkdir -p "$install_dir" + cp "$src_dir/EchoHub.Client" "$install_dir/$BINARY_NAME" + chmod +x "$install_dir/$BINARY_NAME" + + echo "" + + # Verify + if command -v "$BINARY_NAME" >/dev/null 2>&1; then + echo "Installed successfully! Run 'echohub' to start." + else + echo "Installed to: ${install_dir}/${BINARY_NAME}" + echo "" + echo "WARNING: ${install_dir} is not in your PATH." + echo "Add it to your shell profile:" + echo "" + echo " echo 'export PATH=\"${install_dir}:\$PATH\"' >> ~/.bashrc" + echo " # or for zsh:" + echo " echo 'export PATH=\"${install_dir}:\$PATH\"' >> ~/.zshrc" + fi +} + +main From 6295831045e80d8f4e6b40a791b3e7ea6cd396a6 Mon Sep 17 00:00:00 2001 From: HueByte Date: Tue, 24 Feb 2026 19:44:55 +0100 Subject: [PATCH 2/2] feat: update nuspec metadata and add PATH setup functionality for terminal access --- packaging/choco/echohub.nuspec | 22 ++--- scripts/install.sh | 46 +++++++--- src/EchoHub.Client/Program.cs | 3 + src/EchoHub.Client/Services/PathSetup.cs | 102 +++++++++++++++++++++++ 4 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 src/EchoHub.Client/Services/PathSetup.cs diff --git a/packaging/choco/echohub.nuspec b/packaging/choco/echohub.nuspec index e4415c7..e908659 100644 --- a/packaging/choco/echohub.nuspec +++ b/packaging/choco/echohub.nuspec @@ -4,21 +4,21 @@ echohub __VERSION__ EchoHub - Hue - Hue + HueByte + HueByte false - https://github.com/HueByte/EchoHub/blob/master/LICENSE - https://github.com/HueByte/EchoHub - https://github.com/HueByte/EchoHub - https://huebyte.github.io/EchoHub - https://github.com/HueByte/EchoHub/issues - https://github.com/HueByte/EchoHub/tree/master/packaging/choco - https://raw.githubusercontent.com/HueByte/EchoHub/master/assets/hue_icon.png + https://github.com/HueByteByte/EchoHub/blob/master/LICENSE + https://github.com/HueByteByte/EchoHub + https://github.com/HueByteByte/EchoHub + https://HueBytebyte.github.io/EchoHub + https://github.com/HueByteByte/EchoHub/issues + https://github.com/HueByteByte/EchoHub/tree/master/packaging/choco + https://raw.githubusercontent.com/HueByteByte/EchoHub/master/assets/HueByte_icon.png EchoHub is a decentralized IRC-style chat application with a terminal user interface (TUI). Connect to any EchoHub server, join channels, and chat — all from your terminal. Features include SignalR real-time messaging, file sharing, custom themes, and IRC gateway compatibility. Decentralized terminal chat client with IRC gateway support chat irc decentralized tui terminal signalr echohub - https://huebyte.github.io/EchoHub/changelog/v__VERSION__.html - Copyright (c) 2026 Hue + https://HueBytebyte.github.io/EchoHub/changelog/v__VERSION__.html + Copyright (c) 2026 HueByte diff --git a/scripts/install.sh b/scripts/install.sh index 82989ad..576c30e 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -130,6 +130,31 @@ download() { fi } +# ── PATH setup ──────────────────────────────────────────────────────────── + +add_to_path() { + dir="$1" + export_line="export PATH=\"${dir}:\$PATH\" # Added by EchoHub" + + added=0 + for profile in "$HOME/.profile" "$HOME/.bashrc" "$HOME/.zshrc"; do + if [ -f "$profile" ]; then + if grep -q "$dir" "$profile" 2>/dev/null; then + continue # Already present + fi + printf '\n%s\n' "$export_line" >> "$profile" + echo " Added to PATH in $(basename "$profile")" + added=1 + fi + done + + # If no profile existed, create .profile + if [ "$added" -eq 0 ]; then + printf '\n%s\n' "$export_line" >> "$HOME/.profile" + echo " Added to PATH in .profile" + fi +} + # ── Main ────────────────────────────────────────────────────────────────── main() { @@ -181,18 +206,17 @@ main() { echo "" - # Verify - if command -v "$BINARY_NAME" >/dev/null 2>&1; then - echo "Installed successfully! Run 'echohub' to start." - else - echo "Installed to: ${install_dir}/${BINARY_NAME}" + # Ensure install directory is on PATH + if ! command -v "$BINARY_NAME" >/dev/null 2>&1; then + add_to_path "$install_dir" + fi + + echo "Installed successfully! Run 'echohub' to start." + echo "" + if ! echo "$PATH" | tr ':' '\n' | grep -qx "$install_dir"; then + echo "NOTE: Restart your shell or run the following to use echohub now:" echo "" - echo "WARNING: ${install_dir} is not in your PATH." - echo "Add it to your shell profile:" - echo "" - echo " echo 'export PATH=\"${install_dir}:\$PATH\"' >> ~/.bashrc" - echo " # or for zsh:" - echo " echo 'export PATH=\"${install_dir}:\$PATH\"' >> ~/.zshrc" + echo " export PATH=\"${install_dir}:\$PATH\"" fi } diff --git a/src/EchoHub.Client/Program.cs b/src/EchoHub.Client/Program.cs index 1394ab2..f86f1c3 100644 --- a/src/EchoHub.Client/Program.cs +++ b/src/EchoHub.Client/Program.cs @@ -77,6 +77,9 @@ Log.Logger = new LoggerConfiguration() Log.Information("EchoHub client starting"); +// == Ensure echohub is on PATH for convenient terminal access ============ +PathSetup.EnsureOnPath(); + // == Post-update detection: stale backup cleanup or flag for rollback menu ================ if (UpdateBackupService.BackupExists()) { diff --git a/src/EchoHub.Client/Services/PathSetup.cs b/src/EchoHub.Client/Services/PathSetup.cs new file mode 100644 index 0000000..c7184d5 --- /dev/null +++ b/src/EchoHub.Client/Services/PathSetup.cs @@ -0,0 +1,102 @@ +using Serilog; + +namespace EchoHub.Client.Services; + +/// +/// Ensures the application's directory is on the system PATH so users +/// can run 'echohub' from any terminal session. +/// +public static class PathSetup +{ + private const string PathMarker = "# Added by EchoHub"; + + /// + /// Checks if the app directory is on PATH; if not, adds it persistently. + /// On Windows: modifies user-level PATH environment variable. + /// On Linux/macOS: appends an export line to shell profile files. + /// + public static void EnsureOnPath() + { + try + { + var appDir = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + if (IsOnPath(appDir)) + return; + + if (OperatingSystem.IsWindows()) + AddToWindowsPath(appDir); + else + AddToUnixPath(appDir); + } + catch (Exception ex) + { + Log.Debug(ex, "Could not add app directory to PATH"); + } + } + + private static bool IsOnPath(string directory) + { + var pathVar = Environment.GetEnvironmentVariable("PATH") ?? ""; + var separator = OperatingSystem.IsWindows() ? ';' : ':'; + + return pathVar + .Split(separator, StringSplitOptions.RemoveEmptyEntries) + .Any(p => string.Equals( + p.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), + directory, + OperatingSystem.IsWindows() + ? StringComparison.OrdinalIgnoreCase + : StringComparison.Ordinal)); + } + + private static void AddToWindowsPath(string directory) + { + var userPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? ""; + + // Double-check against user PATH specifically (process PATH includes system + user) + if (userPath.Split(';', StringSplitOptions.RemoveEmptyEntries) + .Any(p => string.Equals(p.TrimEnd('\\', '/'), directory, StringComparison.OrdinalIgnoreCase))) + return; + + var newPath = string.IsNullOrEmpty(userPath) ? directory : userPath + ";" + directory; + Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.User); + Log.Information("Added {Directory} to user PATH", directory); + } + + private static void AddToUnixPath(string directory) + { + var exportLine = $"export PATH=\"{directory}:$PATH\" {PathMarker}"; + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + // Target the most common shell profiles + string[] profiles = [ + Path.Combine(home, ".profile"), + Path.Combine(home, ".bashrc"), + Path.Combine(home, ".zshrc") + ]; + + var added = false; + foreach (var profile in profiles) + { + if (!File.Exists(profile)) + continue; + + var content = File.ReadAllText(profile); + if (content.Contains(directory)) + continue; // Already present (manual or previous run) + + File.AppendAllText(profile, $"\n{exportLine}\n"); + added = true; + Log.Information("Added PATH export to {Profile}", profile); + } + + // If no profile existed, create .profile + if (!added) + { + var fallback = Path.Combine(home, ".profile"); + File.AppendAllText(fallback, $"\n{exportLine}\n"); + Log.Information("Created PATH export in {Profile}", fallback); + } + } +}