diff --git a/.diploi/helm/app.yaml b/.diploi/helm/app.yaml index ca34610..c256eeb 100644 --- a/.diploi/helm/app.yaml +++ b/.diploi/helm/app.yaml @@ -42,7 +42,7 @@ spec: - /bin/sh - -c - > - dotnet build || exit 1; + (dotnet restore && dotnet build) || exit 1; (dotnet tool restore && dotnet tool run libman restore) || echo "Libman restore failed, continuing..."; workingDir: /app{{ .Values.folder }} volumeMounts: @@ -78,6 +78,10 @@ spec: - name: {{ .identifier }} value: {{ .value | quote }} {{- end }} + {{- if eq .Values.stage "development" }} + - name: NUGET_PACKAGES + value: /app{{ .Values.folder }}/.nuget + {{- end }} volumeMounts: {{- if hasKey .Values.storage "code" }} - name: app-mount diff --git a/.gitignore b/.gitignore index 427a76b..f8038dd 100644 --- a/.gitignore +++ b/.gitignore @@ -483,4 +483,10 @@ docker-compose.override.yaml ## tmp/ -temp/ \ No newline at end of file +temp/ + +# NuGet packages +packages/ +*.nuget.props +*.nuget.targets +.nuget/ \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index bc61c41..4d3c770 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,5 +1,10 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 +RUN apt-get update \ + && apt-get install -y --no-install-recommends tini nodejs npm iproute2 net-tools \ + && npm install -g nodemon \ + && rm -rf /var/lib/apt/lists/* + ARG FOLDER=/app COPY --chown=1000:1000 . /app @@ -10,10 +15,14 @@ USER 1000:1000 EXPOSE 5054 -ENV ASPNETCORE_URLS=http://+:5054 +ENV ASPNETCORE_URLS=http://0.0.0.0:5054 ENV ASPNETCORE_ENVIRONMENT=Development ENV ASPNETCORE_HTTPS_PORT="" -CMD ["dotnet", "watch", "--no-launch-profile", "--hot-reload", "--non-interactive"] \ No newline at end of file +ENV DOTNET_USE_POLLING_FILE_WATCHER=1 + +ENTRYPOINT ["/usr/bin/tini", "--"] + +CMD ["nodemon"] \ No newline at end of file diff --git a/Program.cs b/Program.cs index bd6b76f..94655cf 100644 --- a/Program.cs +++ b/Program.cs @@ -1,5 +1,6 @@ using component_blazor.Components; using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Server.Kestrel.Core; var builder = WebApplication.CreateBuilder(args); @@ -7,6 +8,20 @@ builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); +builder.Services.Configure(options => +{ + options.ShutdownTimeout = TimeSpan.FromSeconds(1); +}); + +// Configure Kestrel to allow port reuse and SO_REUSEADDR +builder.WebHost.ConfigureKestrel((context, options) => +{ + options.ListenAnyIP(5054, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); +}); + // Configure data protection for Docker builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo("/var/dpkeys")) diff --git a/README.md b/README.md index 8accb19..554a90a 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,19 @@ Link to the full guide - upcoming ### Development -During development, the container uses `dotnet watch` to enable automatic reloads when files change. The development server is started with: +During development, the container uses `nodemon` to enable automatic reloads when files and new dependencies change. The development server is started with: ```sh -dotnet watch --no-launch-profile --hot-reload --non-interactive +dotnet run \ + --no-launch-profile \ + --non-interactive ``` This will: - Use `dotnet watch` to monitor for changes to .cs, .razor, and .css files and restart the server when changes are detected. - Run the Blazor Server application with hot reload enabled. - Start the app on port 5054. -- Enable automatic browser refresh when Razor components or CSS files change. +- Avoid using any launch profile so environment variables are controlled by the container. ### Installing Packages @@ -64,7 +66,7 @@ dotnet add package Microsoft.EntityFrameworkCore.SqlServer The `libman.json` file manages client-side libraries, while the `.csproj` file tracks NuGet dependencies. Both are automatically restored during development and build. -> **Important:** After installing packages, open the Deployment page and restart the running Blazor container so it loads the new dependencies. +`dotnet add package` updates the `.csproj` and restores the package to the local NuGet cache. `nodemon` notices the new package reference in `component-blazor.csproj` and restarts the runtime so the change is picked up immediately. ### Production diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..e1cae3a --- /dev/null +++ b/nodemon.json @@ -0,0 +1,13 @@ +{ + "watch": [ + "." + ], + "ignore": [ + "**/bin/**", + "**/obj/**" + ], + "ext": "cs,csproj,razor,cshtml,json,html,js,css", + "exec": "sh run-dotnet-watch.sh", + "signal": "SIGTERM", + "delay": "1000" +} \ No newline at end of file diff --git a/run-dotnet-watch.sh b/run-dotnet-watch.sh new file mode 100644 index 0000000..09cdf88 --- /dev/null +++ b/run-dotnet-watch.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -eu + +PORT=5054 + +# Try graceful shutdown first +echo "=== Checking if port ${PORT} is in use ===" +if ss -ltnp 2>/dev/null | grep -q ":${PORT} "; then + echo "Port ${PORT} is in use, attempting graceful shutdown..." + pkill -TERM -f "dotnet watch" 2>/dev/null || true + pkill -TERM -f "dotnet run" 2>/dev/null || true + pkill -TERM -f "dotnet" 2>/dev/null || true + dotnet clean 2>/dev/null || true + sleep 2 +fi + +# Wait for port to be released (bounded wait) +echo "=== Waiting for port ${PORT} to be released ===" +for i in {1..50}; do + if ! ss -ltn 2>/dev/null | grep -q ":${PORT} "; then + echo "Port ${PORT} released." + break + fi + sleep 0.25 +done + +echo "=== Hard cleaning port ${PORT} if still busy ===" +# Hard kill only if still stuck +if ss -ltn 2>/dev/null | grep -q ":${PORT} "; then + echo "Port ${PORT} still busy, forcing release..." + fuser -k ${PORT}/tcp 2>/dev/null || true + sleep 2 +fi + +# Check for TIME_WAIT sockets (use ss -tan to see all TCP states, not just LISTEN) +echo "=== Checking for lingering TIME_WAIT sockets ===" +if ss -tan state time-wait 2>/dev/null | grep -q ":${PORT} "; then + echo "TIME_WAIT sockets found on port ${PORT}, waiting briefly..." + sleep 2 +fi + +echo "=== Starting running dotnet application" +exec dotnet run \ + --no-launch-profile \ + --non-interactive \ No newline at end of file