diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..4e3d8ef --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,21 @@ +# Avalonia desktop (net10 LTS): SDK 10 plus native X11/font/GL deps so the +# Modmanager builds and can at least smoke-run headless via xvfb. +FROM mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim + +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=1000 + +RUN groupadd --gid "$USER_GID" "$USERNAME" \ + && useradd --uid "$USER_UID" --gid "$USER_GID" -m -s /bin/bash "$USERNAME" \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + git sudo xvfb \ + libice6 libsm6 libx11-6 libxkbcommon0 libfontconfig1 libfreetype6 \ + libgl1 libegl1 zlib1g dbus-x11 \ + && echo "$USERNAME ALL=(root) NOPASSWD:ALL" > "/etc/sudoers.d/$USERNAME" \ + && chmod 0440 "/etc/sudoers.d/$USERNAME" \ + && rm -rf /var/lib/apt/lists/* + +USER "$USERNAME" +RUN dotnet --list-sdks diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..2260935 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "greg modmanager (avalonia, net10)", + "build": { + "dockerfile": "Dockerfile" + }, + "remoteUser": "vscode", + "containerEnv": { + "DOTNET_CLI_TELEMETRY_OPTOUT": "1", + "DOTNET_NOLOGO": "1" + }, + "mounts": [ + "source=greg-modmanager-nuget,target=/home/vscode/.nuget/packages,type=volume" + ], + "customizations": { + "vscode": { + "extensions": ["ms-dotnettools.csharp"] + } + }, + "postCreateCommand": "dotnet restore GregModmanager.sln" +} diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 114ef4c..3879230 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -246,6 +246,12 @@ jobs: --self-contained true /p:PublishTrimmed=true /p:TrimMode=full \ /p:PublishSingleFile=true /p:DebugType=none /p:DebugSymbols=false \ -o ./publish/linux-x64 + rm -f ./publish/linux-x64/steam_api64.dll + for steam_library in libsteam_api64.so libsteam_api.so; do + if [ -f "src/GregModmanager.Core/${steam_library}" ]; then + cp "src/GregModmanager.Core/${steam_library}" "./publish/linux-x64/${steam_library}" + fi + done - name: Create tarball id: package diff --git a/CHANGELOG.md b/CHANGELOG.md index 674d97d..0e67730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Headless publish accepts `--changelog ` (alias `--change-note`) so + Steam Workshop updates carry a change note for version control. + +- Shared Terminal Core components for secondary, compact, danger, and active-tab + buttons as well as reusable card and content-surface containers. - Docker Buildx targets for deterministic test runs, Windows cross-publish, and macOS x64/arm64 cross-publish; a Windows-container Dockerfile is available for compatible Windows Docker hosts. @@ -22,6 +27,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **Linux Steam publish**: replaced the broken Facepunch `Editor` publish path + with direct native `ISteamUGC` calls via reflection (`NativePublishAsync`). + `SetItemContent`/`SetItemPreview` through the `Editor` wrapper returned + `FileNotFound` on Linux even though every file existed and was readable. + The preview is staged to a temp copy before the upload (locked originals — + file manager, image viewer, or our own UI — no longer block Steam), live + progress and elapsed time are reported during the upload, and the wait loop + exits early on `CommittingChanges` instead of hanging. `TrimmerRoots.xml` + is now referenced from the Avalonia project so trimmed release builds keep + the `ISteamUGC` update methods. +- **Linux Steam connection**: replaced the bundled Linux `libsteam_api.so` / + `libsteam_api64.so` (Steamworks SDK 1.62 interface set, `SteamApps_v009`) with + the SDK 1.61 build (`SteamApps_v008`/`SteamFriends_v017`) matching + `Cherry.Facepunch.Steamworks 2.5.0`. `SteamClient.Init` failed with + `EntryPointNotFoundException` before, so the app permanently showed + "Steam Disconnected" on Linux; Workshop browse, subscribe, and publish work + again against a running Steam client. +- **Project editing**: each project card now has an explicit Edit/Bearbeiten + button (reliable `Click` handling alongside tap-to-open). Opening a project + can no longer fail silently: navigation and load errors are logged and shown + as a dialog instead of leaving the Projects page without feedback. + Navigation resolves the main window via the DI singleton with a visual-tree + fallback. +- **Merged Projects/My Uploads page**: the separate My Uploads page is gone. + The Projects page now has two tabs — local projects (search, refresh, edit) + and Workshop uploads (refresh, import selected, edit/add-update/download/view + on Steam, paging). Importing an item refreshes the local list and opens the + editor directly. The sidebar entry and profile-menu link point here. +- **Steam native-load diagnostics**: `SteamApiNativeLoader` now records every + searched candidate path, so the "not found" hint actually lists where the + loader looked instead of an empty path list. +- **Modstore authentication routing**: desktop login, token exchange, logout, + and Better Auth now use `datacentermods.com` as the primary service and + fail over to the trusted `datacentermods.home` endpoint only when the public + endpoint is unreachable or unavailable. +- **Steam fallback**: invalid Steam API initializations no longer cause project + opening or the My Uploads list to crash; local projects remain editable when + Workshop metadata cannot be refreshed. +- **Workshop editing**: the editor and Mod Store now use consistent tab and + action states, while item actions remain usable on narrow desktop windows. +- **Linux Steam detection**: native Steam API discovery now also checks the + Flatpak Steam installation and accepts whitespace-formatted Steam library + VDF files. +- **Linux release artifacts**: portable CI builds exclude the Windows Steam DLL + and include both supported Linux Steam API library names. +- **Linux protocol registration**: desktop entries correctly quote application + paths and refresh the actual applications directory. - **CI Build Error**: Replaced MAUI `Preferences.Default` and Avalonia `SettingsPage` references in Core services with platform-agnostic `S.Preferences` and `AppSettings` APIs, restoring Windows/Linux build compatibility. - **Build consistency**: aligned GitHub Actions with the SDK pinned in `global.json`, updated non-MelonLoader package/action dependencies, and diff --git a/EXTERNAL_DEPENDENCIES.md b/EXTERNAL_DEPENDENCIES.md index 2900728..f340f79 100644 --- a/EXTERNAL_DEPENDENCIES.md +++ b/EXTERNAL_DEPENDENCIES.md @@ -18,7 +18,8 @@ a release. | Asset | Platforms | Distribution note | | --- | --- | --- | -| Steam native library | Windows DLL or Linux shared object, depending on publish RID | governed by Valve Steamworks terms; do not present it as open source | +| Steam native library (Windows `steam_api64.dll`) | Windows x64 publish output | governed by Valve Steamworks terms; do not present it as open source | +| Steam native library (Linux `libsteam_api.so` / `libsteam_api64.so`, Steamworks SDK 1.61 interface set: `SteamApps_v008`/`SteamFriends_v017`) | Linux x64 publish output, sourced from the `Facepunch.Steamworks.Dll 1.61.0` NuGet redistributable (`runtimes/linux-x64/native/libsteam_api.so`) to match `Cherry.Facepunch.Steamworks 2.5.0` | governed by Valve Steamworks terms; do not present it as open source. Newer SDK builds (e.g. 1.62.0 with `SteamApps_v009`) are **not** compatible with the managed wrapper and break `SteamClient.Init` on Linux | | `steam_appid.txt` | platform publish output where configured | project configuration, not a library | | bundled fonts | desktop resources | see the licence files beside the vendored fonts | | Visual C++ Redistributable | Windows when required by the installed runtime/components | governed by Microsoft terms | diff --git a/README.md b/README.md index fd8088c..6ff30db 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ is built separately from the main solution and copied to the Avalonia assets by ## Technology -- .NET 9 (`net9.0`) and Avalonia 12.1.1 +- .NET 10 (`net10.0`) and Avalonia 12.1.1 - Cherry.Facepunch.Steamworks 2.5.0 - Microsoft.Extensions.DependencyInjection 10.0.10 - xUnit for automated tests diff --git a/api-compatibility.md b/api-compatibility.md index 1af9656..a9b4887 100644 --- a/api-compatibility.md +++ b/api-compatibility.md @@ -15,8 +15,8 @@ Stand: `v1.6.1-pre.13` | Zweck | Desktop-Aufruf | Webapp-Status | |---|---|---| -| Browser-Login starten | `GET /auth/login` auf `https://datacentermods.home` | Implementiert in `src/app/auth/login/route.ts` | -| Code gegen Session tauschen | `POST /auth/token` auf `https://api.datacentermods.home` | Implementiert in `src/app/auth/token/route.ts` | +| Browser-Login starten | `GET /auth/login` auf `https://datacentermods.com`, Fallback `https://datacentermods.home` | Implementiert in `src/app/auth/login/route.ts` | +| Code gegen Session tauschen | `POST /auth/token` auf `https://datacentermods.com`, Fallback `https://datacentermods.home` | Implementiert in `src/app/auth/token/route.ts` | | Refresh prüfen | `POST /auth/token` mit `state=refresh` | Implementiert; prüft die Better-Auth-Session gegen Ablauf/Widerruf | | Logout | `POST /auth/logout` mit Bearer-Sessiontoken | Implementiert in `src/app/auth/logout/route.ts` | | Webapp-Session | Better Auth `/api/auth/*` | Bestehende Webapp-Authentifizierung bleibt unverändert | @@ -26,9 +26,14 @@ Stand: `v1.6.1-pre.13` Der Starter `/usr/local/bin/gregmodmanager-local-test` setzt: - `MODSTORE_WEB_URL=https://datacentermods.home` -- `MODSTORE_API_URL=https://api.datacentermods.home` +- `MODSTORE_API_URL=https://datacentermods.home` - `IS_LOCAL_TEST_BUILD=TRUE` +Normal builds use `https://datacentermods.com` first. Optional +`MODSTORE_WEB_FALLBACK_URL` and `MODSTORE_API_FALLBACK_URL` overrides can +replace the trusted `https://datacentermods.home` fallback. Authentication +failures such as HTTP 401 or 403 never trigger failover. + Die lokale Webapp muss danach neu gebaut/gestartet werden. Außerdem müssen `datacentermods.home` und `api.datacentermods.home` auf die lokale Reverse-Proxy-Adresse zeigen; aktuell ist `api.datacentermods.home` auf diesem System noch nicht auflösbar. ## Nicht-Webapp-APIs diff --git a/build/installer/gregModmanager.iss b/build/installer/gregModmanager.iss index e6a5146..4f9e276 100644 --- a/build/installer/gregModmanager.iss +++ b/build/installer/gregModmanager.iss @@ -67,7 +67,7 @@ german.WelcomeLabel2=Hiermit wird {#MyAppName} {#MyAppVersion} auf Ihrem Compute Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce [Files] -Source: "..\..\src\GregModmanager.Avalonia\bin\Release\net9.0\win-x64\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "..\..\src\GregModmanager.Avalonia\bin\Release\net10.0\win-x64\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [InstallDelete] Type: filesandordirs; Name: "{app}\*" diff --git a/build/scripts/build.ps1 b/build/scripts/build.ps1 index bd500fa..5863ff0 100644 --- a/build/scripts/build.ps1 +++ b/build/scripts/build.ps1 @@ -323,7 +323,7 @@ if ($wantSign -and $SigningMode -in @('auto', 'self-signed')) { } $projPath = Join-Path $repoRoot 'src\GregModmanager.Avalonia\GregModmanager.Avalonia.csproj' $iss = Join-Path $repoRoot 'build\installer\gregModmanager.iss' -$winPublishDir = Join-Path $repoRoot 'src\GregModmanager.Avalonia\bin\Release\net9.0\win-x64\publish' +$winPublishDir = Join-Path $repoRoot 'src\GregModmanager.Avalonia\bin\Release\net10.0\win-x64\publish' $linuxPublishDir = Join-Path $repoRoot 'artifacts\publish\linux-x64' $installerOutDir = Join-Path $repoRoot 'build\installer\Output' $artifactsDir = Join-Path $repoRoot 'artifacts' diff --git a/build/scripts/install-local.ps1 b/build/scripts/install-local.ps1 index dcc269e..cf16562 100644 --- a/build/scripts/install-local.ps1 +++ b/build/scripts/install-local.ps1 @@ -24,7 +24,7 @@ if ([string]::IsNullOrWhiteSpace($InstallDir)) { $InstallDir = Join-Path $env:LOCALAPPDATA "Programs\$AppFolderName" } -$publishRel = Join-Path $repoRoot "src\GregModmanager.Avalonia\bin\Release\net9.0\win-x64\publish" +$publishRel = Join-Path $repoRoot "src\GregModmanager.Avalonia\bin\Release\net10.0\win-x64\publish" $exePath = Join-Path $InstallDir $ExeName $desktop = [Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop) $startMenuPrograms = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs' diff --git a/build/scripts/start.ps1 b/build/scripts/start.ps1 index 1c10963..48f0773 100644 --- a/build/scripts/start.ps1 +++ b/build/scripts/start.ps1 @@ -20,7 +20,7 @@ Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path -$buildPath = Join-Path $repoRoot "bin\$Configuration\net9.0-windows10.0.19041.0\$Platform" +$buildPath = Join-Path $repoRoot "bin\$Configuration\net10.0-windows10.0.19041.0\$Platform" $exePath = Join-Path $buildPath 'GregModmanager.exe' if (-not (Test-Path $exePath)) { diff --git a/current_features.md b/current_features.md index d361625..33f947b 100644 --- a/current_features.md +++ b/current_features.md @@ -286,9 +286,9 @@ Der eigentliche lokale Steam-Workshop-Upload, Update, Download und Fortschritt b - Webapp erzeugt `greg://install?modId=`-Installationslinks. - Desktop registriert und verarbeitet das `greg://`-Protokoll. -- Desktop-Testprofil unterstützt: - - Browser/Login: `https://datacentermods.home`; - - API: `https://api.datacentermods.home`. +- Desktop-Authentifizierung verwendet `https://datacentermods.com` als + primären Browser-/API-Endpunkt und `https://datacentermods.home` als + vertrauenswürdigen Verfügbarkeits-Fallback. - Webapp-Desktop-Auth-Bridge im Quellcode: - `GET /auth/login`; - `POST /auth/token`; diff --git a/docker/Dockerfile b/docker/Dockerfile index d6632dd..c29ac84 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ FROM mcr.microsoft.com/dotnet/runtime:9.0-noble AS runtime9 FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS restore WORKDIR /src -# The solution is built by SDK 10 (global.json) but testhost targets net9.0. +# The solution is built by SDK 10 (global.json) but testhost targets net10.0. COPY --from=runtime9 /usr/share/dotnet/shared/Microsoft.NETCore.App/ /usr/share/dotnet/shared/Microsoft.NETCore.App/ COPY global.json GregModmanager.sln ./ diff --git a/docker/Dockerfile.windows b/docker/Dockerfile.windows index 7de44a7..895f324 100644 --- a/docker/Dockerfile.windows +++ b/docker/Dockerfile.windows @@ -5,8 +5,8 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0-windowsservercore-ltsc2022 AS build SHELL ["powershell", "-NoProfile", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] WORKDIR C:\src -# The projects target net9.0 while global.json selects SDK 10. Copy the matching -# runtime into the SDK image so the test stage can execute net9.0 test binaries. +# The projects target net10.0 while global.json selects SDK 10. Copy the matching +# runtime into the SDK image so the test stage can execute net10.0 test binaries. COPY --from=runtime9 ["C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\", "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\"] COPY . . diff --git a/docker/README.md b/docker/README.md index c35e23c..655df2e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -28,7 +28,7 @@ Artifacts are written to `artifacts/docker/`, which is ignored by Git. It requires a Windows host with Docker configured for **Windows containers** and a host version compatible with `windowsservercore-ltsc2022`. -The project targets `net9.0` while `global.json` selects SDK 10. The image +The project targets `net10.0` while `global.json` selects SDK 10. The image copies the .NET 9 runtime into the SDK image so its `test` target can execute the test binaries without a manual image patch. diff --git a/docs/codebase/INTEGRATIONS.md b/docs/codebase/INTEGRATIONS.md index da02ce6..820dd95 100644 --- a/docs/codebase/INTEGRATIONS.md +++ b/docs/codebase/INTEGRATIONS.md @@ -8,7 +8,7 @@ | System | Type | Purpose | Auth | Criticality | Evidence | |---|---|---|---|---|---| | Steamworks | native SDK | Steam init and Workshop browse/download/upload | Steam client/session | high | `SteamWorkshopService.cs` | -| Modstore services | HTTPS API | desktop account/session bridge and configured service calls | bearer/session | high | `Services/Auth/` | +| Modstore services | HTTPS API | desktop account/session bridge and configured service calls; `datacentermods.com` primary with trusted `.home` availability fallback | bearer/session | high | `Services/Auth/` | | GitHub releases | HTTPS API | MelonLoader/SteamModfix downloads | public API/User-Agent | medium | installer services | | Local filesystem | local state/game files | workspace, projects, logs, deployment | OS permissions | high | `WorkspaceService`, `AppSettings` | @@ -33,7 +33,12 @@ logs or committed configuration. ## Reliability -Installer clients use finite HTTP timeouts. Cancellation and result records exist in download/install services, but no common retry policy or circuit breaker exists. Steam native loading tries multiple platform paths. +Authentication clients use finite HTTP timeouts and retry the explicitly trusted +`.home` endpoint only for transport failures, missing routes, timeouts, or server +availability failures. Authentication failures such as 401 and 403 are not +retried against another host. Cancellation and result records exist in +download/install services, but no app-wide retry policy or circuit breaker +exists. Steam native loading tries multiple platform paths. ## Observability diff --git a/docs/codebase/STACK.md b/docs/codebase/STACK.md index 416c05d..c8b5f21 100644 --- a/docs/codebase/STACK.md +++ b/docs/codebase/STACK.md @@ -8,7 +8,7 @@ | Area | Value | Evidence | |---|---|---| | Language | C# | `src/GregModmanager.Core/` | -| Runtime | .NET 9 (`net9.0`) | `src/GregModmanager.Core/GregModmanager.Core.csproj` | +| Runtime | .NET 10 (`net10.0`) | `src/GregModmanager.Core/GregModmanager.Core.csproj` | | UI | Avalonia 12.1.1 | `src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj` | | Build | .NET SDK/MSBuild | `GregModmanager.sln`, `global.json` | @@ -47,7 +47,8 @@ Trimming/single-file publishing is enabled in the Avalonia project. | --- | --- | --- | | `DATA_CENTER_GAME_DIR` | override automatic game-root discovery | no | | `IS_LOCAL_BUILD`, `IS_LOCAL_TEST_BUILD` | select local development endpoints | no | -| `MODSTORE_WEB_URL`, `MODSTORE_API_URL` | override web/API base URLs | no, but use trusted HTTPS endpoints | +| `MODSTORE_WEB_URL`, `MODSTORE_API_URL` | override primary web/API base URLs | no, but use trusted HTTPS endpoints | +| `MODSTORE_WEB_FALLBACK_URL`, `MODSTORE_API_FALLBACK_URL` | override the trusted fallback URLs (default `https://datacentermods.home`) | no, but security-sensitive | | `MELONLOADER_RELEASES_URL`, `MELONLOADER_LATEST_API_URL` | override MelonLoader release lookup | no | | `AUTH_LOGIN_URL_FORMAT`, `AUTH_CALLBACK_REDIRECT_URI` | override desktop authentication flow | no, but security-sensitive | | `TELEMETRY_URL`, `GIT_SERVER_URL` | override telemetry/Git endpoints | telemetry credentials are secret; URLs are not | diff --git a/src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj b/src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj index afbaeaa..08aca65 100644 --- a/src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj +++ b/src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj @@ -1,7 +1,7 @@ WinExe - net9.0 + net10.0 enable enable app.manifest @@ -23,6 +23,12 @@ en;de;fr;es;it;ja;pl;ru;zh + + + + + win-x64 @@ -55,6 +61,12 @@ + + steam_api64.dll + PreserveNewest + PreserveNewest + true + libsteam_api64.so PreserveNewest diff --git a/src/GregModmanager.Avalonia/MainWindow.axaml b/src/GregModmanager.Avalonia/MainWindow.axaml index 7b7f87b..63b7171 100644 --- a/src/GregModmanager.Avalonia/MainWindow.axaml +++ b/src/GregModmanager.Avalonia/MainWindow.axaml @@ -15,7 +15,7 @@ Grid.ColumnSpan="2" Classes="titlebar" PointerPressed="OnTitleBarPointerPressed"> - + +