Merge pull request #49 from StewKI/feature-deploy

Deployment
This commit is contained in:
Andrija Stevanović
2026-03-08 16:53:06 +01:00
committed by GitHub
14 changed files with 365 additions and 2 deletions

29
.dockerignore Normal file
View File

@@ -0,0 +1,29 @@
.git
.gitignore
# IDE
.idea
.vs
.vscode
**/.idea
# Build artifacts
**/bin
**/obj
front/node_modules
front/dist
# Documentation
Docs/
# Dev files
docker/
*.sh
# Env files (secrets must not be in the image)
.env
deploy/.env
# Misc
**/*.user
**/*.DotSettings.user

17
deploy/.env.example Normal file
View File

@@ -0,0 +1,17 @@
# PostgreSQL (shared VPS instance — create DB/user manually)
POSTGRES_DB=aips_db
POSTGRES_USER=aips_user
POSTGRES_PASSWORD=CHANGE_ME_strong_password_here
# RabbitMQ
RABBITMQ_DEFAULT_USER=aips_rabbit
RABBITMQ_DEFAULT_PASS=CHANGE_ME_rabbit_password
RABBITMQ_DEFAULT_VHOST=/
RABBITMQ_EXCHANGE=aips
# JWT
JWT_ISSUER=AIPS
JWT_AUDIENCE=AIPSWebApi
JWT_KEY=CHANGE_ME_generate_a_64_char_random_string_here
JWT_EXPIRATION_MINUTES=60
JWT_REFRESH_TOKEN_EXPIRATION_DAYS=7

14
deploy/Dockerfile.front Normal file
View File

@@ -0,0 +1,14 @@
FROM oven/bun:1 AS build
WORKDIR /app
COPY front/package.json front/bun.lock ./
RUN bun install --frozen-lockfile
COPY front/ .
RUN bun run build
FROM nginx:alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY deploy/nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80

23
deploy/Dockerfile.rt Normal file
View File

@@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY dotnet/dotnet.sln dotnet/dotnet.sln
COPY dotnet/AipsCore/AipsCore.csproj dotnet/AipsCore/
COPY dotnet/AipsWebApi/AipsWebApi.csproj dotnet/AipsWebApi/
COPY dotnet/AipsRT/AipsRT.csproj dotnet/AipsRT/
COPY dotnet/AipsWorker/AipsWorker.csproj dotnet/AipsWorker/
WORKDIR /src/dotnet
RUN dotnet restore dotnet.sln
WORKDIR /src
COPY dotnet/ dotnet/
WORKDIR /src/dotnet
RUN dotnet publish AipsRT/AipsRT.csproj -c Release -o /app/publish --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "AipsRT.dll"]

23
deploy/Dockerfile.webapi Normal file
View File

@@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY dotnet/dotnet.sln dotnet/dotnet.sln
COPY dotnet/AipsCore/AipsCore.csproj dotnet/AipsCore/
COPY dotnet/AipsWebApi/AipsWebApi.csproj dotnet/AipsWebApi/
COPY dotnet/AipsRT/AipsRT.csproj dotnet/AipsRT/
COPY dotnet/AipsWorker/AipsWorker.csproj dotnet/AipsWorker/
WORKDIR /src/dotnet
RUN dotnet restore dotnet.sln
WORKDIR /src
COPY dotnet/ dotnet/
WORKDIR /src/dotnet
RUN dotnet publish AipsWebApi/AipsWebApi.csproj -c Release -o /app/publish --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "AipsWebApi.dll"]

22
deploy/Dockerfile.worker Normal file
View File

@@ -0,0 +1,22 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY dotnet/dotnet.sln dotnet/dotnet.sln
COPY dotnet/AipsCore/AipsCore.csproj dotnet/AipsCore/
COPY dotnet/AipsWebApi/AipsWebApi.csproj dotnet/AipsWebApi/
COPY dotnet/AipsRT/AipsRT.csproj dotnet/AipsRT/
COPY dotnet/AipsWorker/AipsWorker.csproj dotnet/AipsWorker/
WORKDIR /src/dotnet
RUN dotnet restore dotnet.sln
WORKDIR /src
COPY dotnet/ dotnet/
WORKDIR /src/dotnet
RUN dotnet publish AipsWorker/AipsWorker.csproj -c Release -o /app/publish --no-restore
FROM mcr.microsoft.com/dotnet/runtime:10.0
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "AipsWorker.dll"]

103
deploy/docker-compose.yml Normal file
View File

@@ -0,0 +1,103 @@
services:
rabbitmq:
image: rabbitmq:3-management
container_name: aips-rabbitmq
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_DEFAULT_VHOST}
volumes:
- rabbitmqdata:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 10s
retries: 5
webapi:
build:
context: ..
dockerfile: deploy/Dockerfile.webapi
container_name: aips-webapi
restart: unless-stopped
environment:
ASPNETCORE_URLS: "http://+:8080"
ASPNETCORE_ENVIRONMENT: "Production"
DB_CONN_STRING: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
RABBITMQ_AMQP_URI: "amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/${RABBITMQ_DEFAULT_VHOST}"
RABBITMQ_EXCHANGE: "${RABBITMQ_EXCHANGE}"
JWT_ISSUER: "${JWT_ISSUER}"
JWT_AUDIENCE: "${JWT_AUDIENCE}"
JWT_KEY: "${JWT_KEY}"
JWT_EXPIRATION_MINUTES: "${JWT_EXPIRATION_MINUTES}"
JWT_REFRESH_TOKEN_EXPIRATION_DAYS: "${JWT_REFRESH_TOKEN_EXPIRATION_DAYS}"
networks:
- default
- back_network
depends_on:
rabbitmq:
condition: service_healthy
rt:
build:
context: ..
dockerfile: deploy/Dockerfile.rt
container_name: aips-rt
restart: unless-stopped
environment:
ASPNETCORE_URLS: "http://+:8080"
ASPNETCORE_ENVIRONMENT: "Production"
DB_CONN_STRING: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
RABBITMQ_AMQP_URI: "amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/${RABBITMQ_DEFAULT_VHOST}"
RABBITMQ_EXCHANGE: "${RABBITMQ_EXCHANGE}"
JWT_ISSUER: "${JWT_ISSUER}"
JWT_AUDIENCE: "${JWT_AUDIENCE}"
JWT_KEY: "${JWT_KEY}"
JWT_EXPIRATION_MINUTES: "${JWT_EXPIRATION_MINUTES}"
JWT_REFRESH_TOKEN_EXPIRATION_DAYS: "${JWT_REFRESH_TOKEN_EXPIRATION_DAYS}"
networks:
- default
- back_network
depends_on:
rabbitmq:
condition: service_healthy
worker:
build:
context: ..
dockerfile: deploy/Dockerfile.worker
container_name: aips-worker
restart: unless-stopped
environment:
DB_CONN_STRING: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
RABBITMQ_AMQP_URI: "amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/${RABBITMQ_DEFAULT_VHOST}"
RABBITMQ_EXCHANGE: "${RABBITMQ_EXCHANGE}"
JWT_ISSUER: "${JWT_ISSUER}"
JWT_AUDIENCE: "${JWT_AUDIENCE}"
JWT_KEY: "${JWT_KEY}"
networks:
- default
- back_network
depends_on:
rabbitmq:
condition: service_healthy
nginx:
build:
context: ..
dockerfile: deploy/Dockerfile.front
container_name: aips-nginx
restart: unless-stopped
ports:
- "8090:80"
depends_on:
- webapi
- rt
networks:
back_network:
external: true
volumes:
rabbitmqdata:

View File

@@ -0,0 +1,45 @@
server {
listen 80;
server_name aips.stewki.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name aips.stewki.com;
ssl_certificate /etc/letsencrypt/live/aips.stewki.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/aips.stewki.com/privkey.pem;
client_max_body_size 10M;
location / {
proxy_pass http://host.docker.internal:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /hubs/ {
proxy_pass http://host.docker.internal:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}

46
deploy/nginx/nginx.conf Normal file
View File

@@ -0,0 +1,46 @@
upstream webapi {
server webapi:8080;
}
upstream rt {
server rt:8080;
}
server {
listen 80;
server_name _;
client_max_body_size 10M;
# REST API
location /api/ {
proxy_pass http://webapi;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# SignalR hubs (WebSocket support)
location /hubs/ {
proxy_pass http://rt;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# Vue SPA
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}

27
dos-start-back.bat Normal file
View File

@@ -0,0 +1,27 @@
<# : batch portion
@echo off
set "SCRIPT_DIR=%~dp0"
powershell -ExecutionPolicy Bypass "iex((Get-Content '%~f0' -Raw))"
exit /b
#>
Set-Location (Join-Path $env:SCRIPT_DIR "dotnet")
$jobs = @()
$jobs += Start-Job -ScriptBlock { Set-Location $using:PWD; dotnet run --project AipsWebApi 2>&1 | ForEach-Object { "[WebApi] $_" } }
$jobs += Start-Job -ScriptBlock { Set-Location $using:PWD; dotnet run --project AipsRT 2>&1 | ForEach-Object { "[RT] $_" } }
$jobs += Start-Job -ScriptBlock { Set-Location $using:PWD; dotnet run --project AipsWorker 2>&1 | ForEach-Object { "[Worker] $_" } }
try {
while ($jobs | Where-Object { $_.State -eq 'Running' }) {
foreach ($job in $jobs) {
Receive-Job -Job $job
}
Start-Sleep -Milliseconds 200
}
foreach ($job in $jobs) {
Receive-Job -Job $job
}
} finally {
$jobs | Stop-Job -PassThru | Remove-Job
}

4
dos-start-front.bat Normal file
View File

@@ -0,0 +1,4 @@
@echo off
cd /d "%~dp0front"
bun dev

4
dos-start-infra.bat Normal file
View File

@@ -0,0 +1,4 @@
@echo off
cd /d "%~dp0docker"
docker compose -p aips --env-file ..\.env up

View File

@@ -8,7 +8,10 @@ using AipsRT.Services.Interfaces;
using DotNetEnv;
using Microsoft.AspNetCore.SignalR;
if (File.Exists("../../.env"))
{
Env.Load("../../.env");
}
var builder = WebApplication.CreateBuilder(args);

View File

@@ -3,7 +3,10 @@ using AipsCore.Infrastructure.Persistence.Db;
using AipsWebApi.Middleware;
using DotNetEnv;
if (File.Exists("../../.env"))
{
Env.Load("../../.env");
}
var builder = WebApplication.CreateBuilder(args);