diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee4a92e..97a73f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,8 +115,84 @@ jobs: - run: dotnet build --configuration Release --no-restore - run: dotnet test --configuration Release --no-build - publish: - name: publish + # The headline artifact: one executable per platform that runs on a machine with no .NET + # installed. Nothing to unpack beyond the zip, nothing to install. + publish-standalone: + name: publish (${{ matrix.rid }}) + needs: [version, verify] + if: needs.version.outputs.proceed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 25 + strategy: + fail-fast: true + matrix: + rid: [win-x64, linux-x64, osx-arm64] + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: '10.0.x' + + - name: Publish ${{ matrix.rid }} + shell: bash + env: + VERSION: ${{ needs.version.outputs.version }} + RID: ${{ matrix.rid }} + run: | + set -euo pipefail + dotnet publish src/TargetApiSimulator/TargetApiSimulator.csproj \ + --configuration Release \ + --runtime "$RID" \ + --self-contained true \ + -p:PublishSingleFile=true \ + -p:EnableCompressionInSingleFile=true \ + -p:DebugType=embedded \ + -p:Version="$VERSION" \ + -p:InformationalVersion="$VERSION+$GITHUB_SHA" \ + --output "out/$RID" + + LAUNCHER=TargetApiSimulator + [[ "$RID" == win-* ]] && LAUNCHER=TargetApiSimulator.exe + + if [[ ! -f "out/$RID/$LAUNCHER" ]]; then + echo "No launcher was produced for $RID; the zip would not be runnable." >&2 + ls -la "out/$RID" >&2 + exit 1 + fi + + - name: Package + shell: bash + env: + VERSION: ${{ needs.version.outputs.version }} + RID: ${{ matrix.rid }} + run: | + set -euo pipefail + NAME="TargetApiSimulator-$VERSION-$RID" + + # IIS hosting leftovers from the Web SDK. Nothing here is hosted in IIS, and they only + # make the download look like it needs assembling. + rm -f "out/$RID/web.config" \ + "out/$RID/aspnetcorev2_inprocess.dll" \ + "out/$RID/TargetApiSimulator.staticwebassets.endpoints.json" + + [[ "$RID" == win-* ]] || chmod +x "out/$RID/TargetApiSimulator" + + mkdir -p dist + ( cd "out/$RID" && zip -r "$GITHUB_WORKSPACE/dist/$NAME.zip" . ) + ( cd dist && sha256sum "$NAME.zip" > "$NAME.zip.sha256" ) + + - uses: actions/upload-artifact@v7 + with: + name: package-${{ matrix.rid }} + path: dist/* + retention-days: 14 + if-no-files-found: error + + # A small framework-dependent build for anyone who already has the .NET runtime and would + # rather download 30 KB than 50 MB. + publish-portable: + name: publish (portable) needs: [version, verify] if: needs.version.outputs.proceed == 'true' runs-on: ubuntu-latest @@ -128,9 +204,6 @@ jobs: with: dotnet-version: '10.0.x' - # One portable package, and deliberately no apphost. Without the native launcher a - # per-RID publish produces the same file set anyway, and a freshly built unsigned .exe - # with no download reputation gets flagged as Trojan:Script/Wacatac.B!ml. - name: Publish shell: bash env: @@ -145,32 +218,28 @@ jobs: -p:InformationalVersion="$VERSION+$GITHUB_SHA" \ --output out/portable - if ls out/portable | grep -qiE '\.exe$'; then - echo "An .exe ended up in the package; UseAppHost=false is not taking effect." >&2 - exit 1 - fi - - name: Package shell: bash env: VERSION: ${{ needs.version.outputs.version }} run: | set -euo pipefail - NAME="TargetApiSimulator-$VERSION" + NAME="TargetApiSimulator-$VERSION-portable" + rm -f out/portable/web.config out/portable/TargetApiSimulator.staticwebassets.endpoints.json mkdir -p dist ( cd out/portable && zip -r "$GITHUB_WORKSPACE/dist/$NAME.zip" . ) ( cd dist && sha256sum "$NAME.zip" > "$NAME.zip.sha256" ) - uses: actions/upload-artifact@v7 with: - name: package + name: package-portable path: dist/* retention-days: 14 if-no-files-found: error release: name: release - needs: [version, publish] + needs: [version, publish-standalone, publish-portable] if: needs.version.outputs.should_release == 'true' runs-on: ubuntu-latest timeout-minutes: 10 @@ -179,7 +248,8 @@ jobs: steps: - uses: actions/download-artifact@v8 with: - name: package + pattern: package-* + merge-multiple: true path: dist # Creates the tag at this commit as part of publishing, so VERSION.md stays the only diff --git a/CHANGELOG.md b/CHANGELOG.md index cd5daa9..6d14fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,25 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [1.0.3] - 2026-07-31 + +### Changed + +- **Releases ship a runnable executable again.** 1.0.1 and 1.0.2 contained a DLL and a handful of + JSON files, which is not something anyone can start. Each platform now gets a self-contained, + single-file build — `TargetApiSimulator.exe` on Windows, `TargetApiSimulator` on Linux and + macOS — that runs on a machine with no .NET installed at all. Roughly 50 MB, because the + runtime is inside it. +- The small framework-dependent build is still published as + `TargetApiSimulator--portable.zip` for anyone who already has the .NET 10 runtime. +- Console timestamps and the `Microsoft.AspNetCore` log filter are now defaults in code rather + than only in `appsettings.json`, so the standalone executable logs identically when it runs + with no files beside it. Configuration still overrides both. +- Packages no longer carry `web.config`, `aspnetcorev2_inprocess.dll` or the static web assets + manifest — IIS hosting leftovers that made the download look like it needed assembling. +- The release pipeline fails if a platform build produces no launcher, replacing the earlier + check that failed if one was present. + ## [1.0.2] - 2026-07-31 ### Fixed @@ -119,7 +138,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Every pull request runs `dotnet list package --vulnerable --include-transitive` and fails on a hit. CLI output is forced to English so the check cannot silently pass on a localised runner. -[Unreleased]: https://github.com/Mysttic/TargetApiSimulator/compare/v1.0.2...HEAD +[Unreleased]: https://github.com/Mysttic/TargetApiSimulator/compare/v1.0.3...HEAD +[1.0.3]: https://github.com/Mysttic/TargetApiSimulator/compare/v1.0.2...v1.0.3 [1.0.2]: https://github.com/Mysttic/TargetApiSimulator/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/Mysttic/TargetApiSimulator/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/Mysttic/TargetApiSimulator/releases/tag/v1.0.0 diff --git a/README.md b/README.md index 5545a1b..f3452e1 100644 --- a/README.md +++ b/README.md @@ -221,40 +221,35 @@ The container runs as an unprivileged user (`uid 1654`) and listens on 8080 only ## Get a release -Every release on the [Releases page](https://github.com/Mysttic/TargetApiSimulator/releases) ships one portable -zip plus its `.sha256` checksum. The .NET 10 runtime has to be installed; the same zip then runs on Windows, -Linux and macOS. +Download the zip for your platform from the +[Releases page](https://github.com/Mysttic/TargetApiSimulator/releases), unpack it, and run the executable. +**Nothing else has to be installed — not even .NET.** -Fetch and start the latest one in a single step — PowerShell: - -```powershell -$tag = (Invoke-RestMethod https://api.github.com/repos/Mysttic/TargetApiSimulator/releases/latest).tag_name -Invoke-WebRequest "https://github.com/Mysttic/TargetApiSimulator/releases/download/$tag/TargetApiSimulator-$($tag.TrimStart('v')).zip" -OutFile tas.zip -Expand-Archive tas.zip -DestinationPath tas -Force -dotnet tas/TargetApiSimulator.dll --urls http://localhost:5000 -``` - -bash: +| Asset | Run | +|---|---| +| `TargetApiSimulator--win-x64.zip` | `TargetApiSimulator.exe` | +| `TargetApiSimulator--linux-x64.zip` | `./TargetApiSimulator` | +| `TargetApiSimulator--osx-arm64.zip` | `./TargetApiSimulator` | +| `TargetApiSimulator--portable.zip` | `dotnet TargetApiSimulator.dll` — 30 KB, needs the .NET 10 runtime | ```bash -TAG=$(curl -sL https://api.github.com/repos/Mysttic/TargetApiSimulator/releases/latest | grep -oP '"tag_name": "\K[^"]+') -curl -sL "https://github.com/Mysttic/TargetApiSimulator/releases/download/$TAG/TargetApiSimulator-${TAG#v}.zip" -o tas.zip -unzip -o tas.zip -d tas -dotnet tas/TargetApiSimulator.dll --urls http://localhost:5000 +./TargetApiSimulator --urls http://localhost:5000 ``` -Verify the download if you want to: +It listens on port 5000, prints everything it receives to the console, and stops with Ctrl+C. The three +platform builds are self-contained, which is why they are around 50 MB: the .NET runtime is inside the +executable. If you already have .NET 10, the `portable` zip does the same job in 30 KB. + +Each asset has a `.sha256` file next to it: ```bash -sha256sum -c TargetApiSimulator-1.0.0.zip.sha256 +sha256sum -c TargetApiSimulator-1.0.3-linux-x64.zip.sha256 ``` -> The zip deliberately contains **no `.exe` launcher**. A freshly built, unsigned executable with no download -> reputation is routinely flagged by antivirus machine-learning heuristics — Microsoft Defender reports it as -> `Trojan:Script/Wacatac.B!ml`. Shipping only the managed assembly removes the false positive entirely, at the -> cost of typing `dotnet` in front of the command. - -Everything the application receives is printed to the console it runs in. +> **If antivirus blocks the download:** the executables are unsigned, and Microsoft Defender flags freshly +> built unsigned binaries with no download reputation as `Trojan:Script/Wacatac.B!ml` — a machine-learning +> guess, not a signature match. Check the `.sha256`, or use the container image, which is never affected: +> `docker run --rm -p 5000:8080 ghcr.io/mysttic/targetapisimulator`. ## Versioning diff --git a/VERSION.md b/VERSION.md index a1d1ce0..d54973c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,6 +1,6 @@ # Version -1.0.2 +1.0.3