diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..04fd547 --- /dev/null +++ b/.env.example @@ -0,0 +1,33 @@ +# Copy to .env next to docker-compose.yml and edit. Compose reads it automatically; +# git ignores .env. Every value here is optional — the defaults in docker-compose.yml +# are what you get without it. + +# --- Ports on your machine ----------------------------------------------------------- +# Container-side ports are fixed (API 5000, nginx 80); only these move. +#LADOSE_WEB_PORT=8080 +#LADOSE_API_PORT=5000 + +# --- Database ------------------------------------------------------------------------ +# Overrides ConnectionStrings:DbContext from LaDOSE.Api/appsettings.json. The stack has +# no Postgres of its own, so this has to point at a reachable server. +# +# Postgres on the machine running the containers: +#LADOSE_DB_CONNECTION=Host=host.docker.internal;Username=tom;Password=tom;Database=ladoseapi +# +# Postgres elsewhere on the LAN — check the name resolves *inside* the container +# (`docker compose run --rm api getent hosts kafka.local`) and use the IP if it does not: +#LADOSE_DB_CONNECTION=Host=kafka.local;Username=tom;Password=tom;Database=ladoseapi +# +# Sql/dump_20240316.sql then Sql/2026-08-05_roles.sql populate an empty database. + +# --- Secrets ------------------------------------------------------------------------- +# appsettings.json carries placeholders for these. Any value works for JWT locally, but +# it must be at least 32 characters: Startup.cs feeds it to HMAC-SHA256 as raw ASCII. +#LADOSE_JWT_SECRET=dev-only-secret-not-for-any-deployed-environment +#LADOSE_SMASH_API_KEY= +#LADOSE_CHALLONGE_API_KEY= + +# --- Frontend ------------------------------------------------------------------------ +# Only needed if the API is not on http://localhost:${LADOSE_API_PORT}. Resolved by the +# browser, so container names like http://api:5000 will not work. +#LADOSE_API_BASE_URL=http://localhost:5000 diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 9439198..c62529a 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -4,7 +4,7 @@ on: [push] jobs: Build: - runs-on: ubuntu-latest-real + runs-on: ubuntu-latest steps: - name: Update run: | diff --git a/.gitignore b/.gitignore index 3e759b7..76cb842 100644 --- a/.gitignore +++ b/.gitignore @@ -326,5 +326,12 @@ ASALocalRun/ # NVidia Nsight GPU debugger configuration file *.nvuser -# MFractors (Xamarin productivity tool) working folder +# MFractors (Xamarin productivity tool) working folder .mfractor/ + +# Local docker-compose overrides: connection string, API keys, ports. +# .env.example is documentation and stays tracked. +.env +.env.* +!.env.example +docker-compose.override.yml diff --git a/LaDOSE.Src/.dockerignore b/LaDOSE.Src/.dockerignore index 92316b4..917f9d6 100644 --- a/LaDOSE.Src/.dockerignore +++ b/LaDOSE.Src/.dockerignore @@ -1,2 +1,22 @@ -*/*/bin* -*/*/obj* +# Context for LaDOSE.Src/Dockerfile. +# +# The previous patterns here were */*/bin* and */*/obj*, which matched nothing: this +# context is rooted at LaDOSE.Src, so build output sits one level down (LaDOSE.Api/bin), +# not two. +**/bin/ +**/obj/ + +# The frontend is a separate image with its own context (LaDOSE.WebApp/Dockerfile). +# Its node_modules alone was adding ~118 MB to every API build. +LaDOSE.WebApp/node_modules/ +LaDOSE.WebApp/build/ +LaDOSE.WebApp/.svelte-kit/ + +# Local state and editor noise. Note Libraries/ is NOT excluded: LaDOSE.Business +# references ChallongeCSharpDriver.dll from there by HintPath. +.git +.vs/ +.vscode/ +.idea/ +*.user +*.suo diff --git a/LaDOSE.Src/Dockerfile b/LaDOSE.Src/Dockerfile index 62cbb10..778bfed 100644 --- a/LaDOSE.Src/Dockerfile +++ b/LaDOSE.Src/Dockerfile @@ -1,15 +1,44 @@ -FROM microsoft/dotnet:sdk AS build-env +# Builds the LaDOSE.Api image. Context is LaDOSE.Src (see .dockerignore next to this file). +# +# Only LaDOSE.Api is published. LaDOSE.linux.sln also carries the Avalonia desktop app, +# the Discord bot and LinuxTest, and `dotnet publish -o out` flattens every project +# into that one directory — which is why the previous version of this file copied from +# /app/LaDOSE.Api/out/ and found nothing there. +ARG DOTNET_VERSION=9.0 + +FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} AS build +WORKDIR /src + +# Debug is deliberate for local work, and docker-compose.yml passes it: the OpenAPI +# document and the Scalar UI are gated behind `#if DEBUG` in Startup.cs *and* behind +# Condition="'$(Configuration)' == 'Debug'" on their PackageReferences in +# LaDOSE.Api.csproj. A Release image therefore serves no /openapi/v1.json, which is +# exactly what LaDOSE.WebApp's `npm run api:sync` reads. Default stays Release. +ARG BUILD_CONFIGURATION=Release + +# Project files first so this layer survives every .cs edit. Restore has to run under +# the same Configuration as the publish below, or the conditional PackageReferences +# above make the two disagree about which packages the assets file should contain. +COPY global.json ./ +COPY LaDOSE.Api/LaDOSE.Api.csproj LaDOSE.Api/ +COPY LaDOSE.DTO/LaDOSE.DTO.csproj LaDOSE.DTO/ +COPY LaDOSE.Entity/LaDOSE.Entity.csproj LaDOSE.Entity/ +COPY LaDOSE.Service/LaDOSE.Business.csproj LaDOSE.Service/ +RUN dotnet restore LaDOSE.Api/LaDOSE.Api.csproj -p:Configuration=${BUILD_CONFIGURATION} + +# Libraries/ChallongeCSharpDriver.dll is a HintPath reference from LaDOSE.Business, +# so the build needs the whole tree, not just the projects listed above. +COPY . . +RUN dotnet publish LaDOSE.Api/LaDOSE.Api.csproj -c ${BUILD_CONFIGURATION} --no-restore -o /app/out + +FROM mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION} WORKDIR /app +COPY --from=build /app/out/ ./ -# Copy everything else and build -COPY . ./ - -RUN dotnet publish LaDOSE.linux.sln -c Release -o out - -# Build runtime image -FROM microsoft/dotnet:aspnetcore-runtime -WORKDIR /app -COPY --from=build-env /app/LaDOSE.Api/out/ . +# Fixed in the image on purpose. Program.cs binds Kestrel from appsettings.json's +# AllowedHosts/Port through a ConfigurationBuilder that reads *only* that file, so a +# Port env var would not move the listener — remap on the host side instead. +# Everything Startup.cs reads does honour env vars (ConnectionStrings__DbContext, +# ApiKey__SmashApiKey, ApiKey__ChallongeApiKey, JWTTokenSecret). EXPOSE 5000 ENTRYPOINT ["dotnet", "LaDOSE.Api.dll"] - diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..abc593b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +# Local development stack: LaDOSE.Api + LaDOSE.WebApp, built from the two Dockerfiles +# already in the tree. +# +# docker compose up --build start both, rebuilding when a Dockerfile changed +# docker compose watch same, plus rebuild the service you are editing +# docker compose logs -f api follow the API +# docker compose down stop +# +# Then: webapp on http://localhost:8080, API on http://localhost:5000, +# Scalar API reference on http://localhost:5000/scalar. +# +# The database is NOT part of this stack — it stays wherever appsettings.json points. +# Copy .env.example to .env to change the ports, the connection string or the API keys. +name: ladose + +services: + api: + build: + context: ./LaDOSE.Src + dockerfile: Dockerfile + args: + # Debug, not Release: /openapi/v1.json and /scalar are compiled out of a + # Release build. See the comment in LaDOSE.Src/Dockerfile. + BUILD_CONFIGURATION: Debug + environment: + # Startup.cs gates the developer exception page, MapOpenApi() and + # MapScalarApiReference() on IsDevelopment(). + ASPNETCORE_ENVIRONMENT: Development + # Double underscore is the .NET section separator, so this overrides + # ConnectionStrings:DbContext from appsettings.json. Left unset in .env, the + # default below reproduces what is committed there. + ConnectionStrings__DbContext: ${LADOSE_DB_CONNECTION:-Host=kafka.local;Username=tom;Password=tom;Database=ladoseapi} + # appsettings.json ships placeholders for these three. Real values belong in + # .env, which git ignores. + JWTTokenSecret: ${LADOSE_JWT_SECRET:-dev-only-secret-not-for-any-deployed-environment} + ApiKey__SmashApiKey: ${LADOSE_SMASH_API_KEY:-} + ApiKey__ChallongeApiKey: ${LADOSE_CHALLONGE_API_KEY:-} + ports: + # Container side is pinned at 5000: Program.cs reads AllowedHosts/Port straight + # from appsettings.json, through a ConfigurationBuilder that ignores env vars. + - "${LADOSE_API_PORT:-5000}:5000" + extra_hosts: + # Lets LADOSE_DB_CONNECTION use Host=host.docker.internal to reach a Postgres + # running on the machine hosting the containers. + - "host.docker.internal:host-gateway" + develop: + watch: + - action: rebuild + path: ./LaDOSE.Src + ignore: + - LaDOSE.WebApp/ + - "**/bin/" + - "**/obj/" + + web: + build: + context: ./LaDOSE.Src/LaDOSE.WebApp + dockerfile: Dockerfile + # VITE_API_BASE_URL is deliberately not passed. Vite would inline it at build + # time; LADOSE_API_BASE_URL below is read at container start instead, so the + # port can change without rebuilding the image. + environment: + # docker-entrypoint.sh turns this into /config.js. It is resolved by the + # *browser*, so it must be a host-visible URL — not http://api:5000. + LADOSE_API_BASE_URL: ${LADOSE_API_BASE_URL:-http://localhost:${LADOSE_API_PORT:-5000}} + ports: + - "${LADOSE_WEB_PORT:-8080}:80" + depends_on: + # Ordering only. The SPA is served by nginx and talks to the API from the + # browser, so it comes up fine on its own; this just avoids a confusing + # first-load failure when starting both at once. + - api + develop: + watch: + - action: rebuild + path: ./LaDOSE.Src/LaDOSE.WebApp + ignore: + - node_modules/ + - build/ + - .svelte-kit/