Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ updates:
groups:
api-base-images:
patterns:
- "mcr.microsoft.com/dotnet/*"
# Dependabot tracks the registry separately from the image name.
- "dotnet/*"
# Best effort: registry digest updates without publication dates cannot
# enforce a cooldown. Review the pinned digest change before merging.
cooldown:
default-days: 7

Expand Down
68 changes: 18 additions & 50 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"

on:
Expand All @@ -28,12 +17,7 @@ concurrency:
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
# required for all workflows
Expand All @@ -53,54 +37,38 @@ jobs:
- language: actions
build-mode: none
- language: csharp
build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
# Trace the real project build, including Razor source generators.
# No-build extraction uses a synthetic Razor compilation that can log
# compiler failures even while reporting a successful analysis.
build-mode: manual
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
- name: Set up .NET
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
cache: false

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.

# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Do not persist dependencies restored while executing pull-request code.
dependency-caching: false

# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- name: Run manual build steps
- name: Build C# for CodeQL
if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
dotnet restore opengamebuilder.slnx
dotnet build opengamebuilder.slnx --configuration Release --no-restore --no-incremental

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
Expand Down
12 changes: 12 additions & 0 deletions docs/foundation-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,18 @@ independent authenticated channel, configures both environments, CI passes, and
a merged staging deployment passes. Follow the
[host-key setup guide](setup/deployment-host-key.md) for commands and trust limits.

**Live application evidence (2026-09-22 UTC):** the
[staging retry](https://github.com/OpenGameBuilder/opengamebuilder/actions/runs/35673447966/attempts/2)
and [production v0.10.0 release](https://github.com/OpenGameBuilder/opengamebuilder/actions/runs/35674772060)
passed the image permission/liveness probe, strict pinned-host SSH connections,
activation, browser revision checks, and finalization. The automatic version
bump and [staging rollout of 0.11.0](https://github.com/OpenGameBuilder/opengamebuilder/actions/runs/35675216560)
also passed. These prove that the configured pins work, not independently how
the administrator authenticated the original host key. Keep that trust-source
confirmation explicit. The running Caddy digest and native-service boot state
still need [host verification](setup/hosting.md#host-caddy-conflicts-and-read-only-verification);
application deployments do not apply edge-image changes.

## Phase 3: Prepare to welcome community contributors

### 16. Replace the placeholder README with a contributor front door
Expand Down
28 changes: 26 additions & 2 deletions docs/quality/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,28 @@ only its own production references and additional dependencies. Package versions
remain in [`Directory.Packages.props`](../../Directory.Packages.props), and the
same compiler warnings-as-errors policy applies to tests and production code.

## CodeQL build coverage

The repository-owned [CodeQL workflow](../../.github/workflows/codeql.yml) traces
a real Release build of the solution for C#, including Razor-generated sources.
It installs the SDK selected by `global.json`, restores without a persistent
dependency cache, and forces a non-incremental build after initializing CodeQL.
This avoids the synthetic Razor compilation used by no-build extraction, which
logged a compiler exit-code error even in successful analyses. Actions analysis
still uses no-build mode because it does not compile C#.

A passing local Release build does not prove CodeQL extraction or upload. Check
the first hosted PR run's C# build, analysis output, and source coverage after
changing this workflow. Do not treat a green check as proof that its logs contain
no extraction errors.

GitHub's separately managed **CodeQL - Code Quality** workflow is configured
outside this repository. Its documented settings do not expose the same manual
build-mode control. Keep that coverage enabled; if its synthetic Razor compiler
diagnostic persists, retain the run URL for GitHub Support rather than disabling
analysis or claiming this repository change fixed that managed workflow. See
[Code Quality configuration](https://docs.github.com/en/code-security/how-tos/maintain-quality-code/enable-code-quality).

## CI and deployment validation

[CI](../../.github/workflows/ci.yml) and
Expand Down Expand Up @@ -56,7 +78,8 @@ The shared-edge apply script also has an isolated Bash test:
```

It mocks Docker and verifies invalid-candidate rejection, Caddyfile-only reload,
failed-reload restoration, intentional Compose updates, and first-time setup.
failed-reload restoration, intentional Compose updates, first-time setup, and
recovery of a stopped edge even when the candidate files are unchanged.
CI runs it without SSH, Docker, deployment credentials, or service changes.
Live staging and production availability must still be checked by the deployment
smoke tests after the workflow change reaches `main`.
Expand All @@ -82,7 +105,8 @@ Supply-chain declarations have an additional deterministic check:
It rejects third-party Actions that are not full commit SHAs, mutable API base or
edge image references, a missing explicit API user, deployment-time
`ssh-keyscan`, inherited NuGet source mappings, or missing Dependabot ecosystems.
CI also loads the locally built API image and runs
It also checks that the API-image group patterns match Dependabot's normalized
dependency names (without their registry). CI loads the locally built API image and runs
`scripts/verify-api-image.sh`; that Docker-backed check verifies the runtime user,
application-directory permissions, startup, and `/api/alive`.

Expand Down
15 changes: 10 additions & 5 deletions docs/setup/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,10 @@ required check; a local pre-commit hook is only a convenience.
[`Directory.Build.targets`](../../Directory.Build.targets) applies this policy
unconditionally after project properties are loaded. There is no test exemption.
This is the C# `TreatWarningsAsErrors` policy, not MSBuild's command-line
`-warnaserror` switch; task-level warnings such as ASPIRE010 may still be warnings.
`-warnaserror` switch; task-level warnings are handled separately.
The AppHost explicitly retains NuGet-restored orchestration dependencies and
acknowledges only `ASPIRE010`, the advisory about optional CLI bundle delegation.
This does not disable compiler warnings or change the local launch workflow.

To opt into the existing Husky pre-commit hook, run these commands from the
repository root (Git for Windows supplies its `sh` interpreter):
Expand Down Expand Up @@ -247,7 +250,9 @@ ordinary restore, build, test, or publish commands.
- **Blazor breakpoint does not bind:** use Edge or Chrome, the
`OpenGameBuilder.Web` profile, and the recommended debugger extensions. See
[Microsoft's Blazor debugging guide](https://learn.microsoft.com/aspnet/core/blazor/debug?view=aspnetcore-10.0).
- **ASPIRE010 CLI-bundle warning:** the current AppHost SDK/hosting-package
combination emits this warning when built without the CLI bundle. A successful
build alone is not a startup check; use the endpoint checks above. Do not
suppress warnings or change package versions just to follow this guide.
- **Aspire dependency mode:** `AspireUseCliBundle=false` is intentional. The
AppHost restores orchestration dependencies from NuGet rather than requiring
CLI bundle delegation during IDE/CI builds, and suppresses only the associated
`ASPIRE010` advisory using the [documented opt-out](https://aspire.dev/diagnostics/aspire010/).
Revisit that choice if adopting CLI-bundle-only features. A successful build
alone is not a startup check; use the endpoint checks above.
57 changes: 56 additions & 1 deletion docs/setup/hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ The reviewed SDK, ASP.NET runtime, and Caddy tags are paired with immutable imag
digests. Dependabot continues to propose tag/digest updates, but a registry
retag cannot change an unreviewed build or edge deployment. Each built API image
is also deployed by its generated GHCR digest.
Dependabot's Docker cooldown is best effort: when the registry supplies no
publication date for a digest update, its PR reports that the cooldown could not
be applied. The digest still requires review and merging before it is used.

The API image declares the .NET image's non-root application user. CI and the
deployment job verify the effective UID is nonzero, the application assembly is
Expand Down Expand Up @@ -52,13 +55,61 @@ the service. A failed reload restores the previous files and attempts to reload
the previous configuration. The workflow checks both API liveness URLs after
the update. Inspect the job log and host state if activation or recovery fails;
do not assume a failed job automatically restored service availability.
An unchanged candidate leaves a running Caddy container alone, but starts it if
it is stopped. This cannot repair a port conflict with another host process.

The staging and production application workflows update only their own release
directory and API service. They require the shared edge network to exist and
never sync or restart Caddy. Staging deployments queue instead of cancelling an
in-flight deployment. Production `/api/alive` is checked before and after a
staging update; a failed preflight stops the update.

### Host Caddy conflicts and read-only verification

Only the Docker edge should own this server's ports 80 and 443. A separately
installed `caddy.service` can start at boot, occupy those ports, and prevent
`ogb-edge-caddy-1` from starting. An API container being up does not establish
that either public site is reachable.

In an existing **root SSH session on the Hetzner server**, from any directory,
these commands inspect state without changing services or printing environment
variables or private keys:

```bash
systemctl is-enabled caddy
systemctl is-active caddy
ss -ltnp '( sport = :80 or sport = :443 )'
docker inspect --format '{{.Name}} image={{.Config.Image}} status={{.State.Status}} restarts={{.RestartCount}}' ogb-edge-caddy-1 ogb-staging-api-1 ogb-production-api-1
docker logs --since 2h --tail 150 ogb-edge-caddy-1 2>&1
docker logs --since 2h --tail 150 ogb-staging-api-1 2>&1
docker logs --since 2h --tail 150 ogb-production-api-1 2>&1
```

The native Caddy service should be disabled/inactive (or not installed).
`systemctl` returns a nonzero status for some of those expected states; run
these inspection commands individually, not in a fail-fast script. Container
logs may contain client addresses or request data; redact sensitive content
before sharing them.

If inspection confirms the native service is the conflicting, superseded OGB
proxy, coordinate a short interruption for **both sites**, then run on the host:

```bash
systemctl disable --now caddy
docker start ogb-edge-caddy-1
curl --fail --show-error https://opengamebuilder.com/api/alive
curl --fail --show-error https://staging.opengamebuilder.com/api/alive
```

Do not stop a service hosting unrelated sites. No root-password reset or web
console is needed when the existing SSH session works. Restarting the container
restores its existing image; it does **not** apply a new image pin. After an edge
change is reviewed and merged, use GitHub **Actions > CD Shared Edge > Run
workflow**, select `main`, and approve the production environment. A Compose
image change can recreate the shared proxy and briefly interrupt both sites.
Verify the running image reference matches `deploy/edge/compose.yml` and that
both workflow liveness checks pass.

After source validation, the package job produces one Release web archive.
After environment approval, deployment verifies that archive and builds and
pushes the API image once to the triggering repository owner's package namespace.
Expand All @@ -85,7 +136,11 @@ maps the concrete `index.html` URL to the home page. Loaded pages continue to
request their own release's assets, and the prior web directory remains
available. The first deployment with this layout copies the
old in-place web files into a `legacy-*` release and retains its original root
assets.
assets when a prior `release-manifest.txt` exists. A first deployment without
that manifest records `previous release: none`; it has no managed rollback
target until a subsequent successful release retains this one. Do not infer
rollback readiness from a successful first release or create a release solely
to manufacture a predecessor.

`scripts/deploy-app.sh` checks the archive checksum, stages the new files,
records the previous release, pulls and starts the API by its digest reference,
Expand Down
10 changes: 6 additions & 4 deletions scripts/apply-edge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ fi
if [[ -f "$active_caddyfile" ]] && cmp -s "$candidate_caddyfile" "$active_caddyfile"; then
config_changed=false
fi
if [[ "$compose_changed" == false && "$config_changed" == false ]]; then
echo "Edge configuration is already current."
compose_active=(docker compose -f "$active_compose" --project-directory "$edge_dir")
# Matching files do not guarantee the edge survived a host restart or port
# conflict. A stopped service still needs the normal Compose startup below.
if [[ "$compose_changed" == false && "$config_changed" == false ]] &&
[[ -n "$("${compose_active[@]}" ps -q caddy)" ]]; then
echo "Edge configuration is already current and Caddy is running."
exit 0
fi

Expand Down Expand Up @@ -70,8 +74,6 @@ if [[ "$config_changed" == true ]] && ! cp "$candidate_caddyfile" "$active_caddy
exit 1
fi

compose_active=(docker compose -f "$active_compose" --project-directory "$edge_dir")

# A Compose change is an explicit edge-service update. Caddyfile-only changes
# leave the container running and use Caddy's graceful configuration reload.
if [[ "$compose_changed" == true ]] ||
Expand Down
4 changes: 4 additions & 0 deletions src/OpenGameBuilder.AppHost/OpenGameBuilder.AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsAspireHost>true</IsAspireHost>
<!-- Keep NuGet-restored orchestration dependencies for IDE and CI builds.
CLI bundle delegation is intentionally not required by this project. -->
<AspireUseCliBundle>false</AspireUseCliBundle>
<NoWarn>$(NoWarn);ASPIRE010</NoWarn>
<UserSecretsId>89227420-16d3-4bd8-adee-b8fb8ff68c4c</UserSecretsId>
</PropertyGroup>

Expand Down
Loading
Loading