Rozdziały · prawdziwe configi z serwera

Deploy krok po kroku

Trzy osobne workflow (Auth / Quiz / PPL), trzy nginxy, instalacja runnera. Swagger tylko przy API.

1. Trzy typy — nie mieszaj kroków

TypPrzykładCo jest w YAMLCzego nie ma
Tylko backendAuthdotnet publishNode, npm, dist, wwwroot copy
Osobny frontendQuizsetup-node, npm ci, npm run build, cp dist → wwwroot, publishSwagger, setup-dotnet
Frontend w repoPPLdotnet publish (wwwroot już w projekcie)Node, npm, Swagger

2. Nazwy katalogów

AuthQuizPPL
Aplikacja/home/devops/baseauth/backend/home/devops/quiz/backend/home/devops/ppl
Runner/home/devops/actions-runner/home/devops/actions-runner-quiz/home/devops/actions-runner-ppl
systemd appbaseauthquizhubppl
Port500750065080

Konwencje aplikacji: .../<app>/backend (Auth, Quiz) albo płaskie .../<app> (PPL). Runner: zwykle /home/devops/actions-runner-<app>, jeden runner = jedno repo. Praca jako bb, procesy jako devops.

3. Instalacja self-hosted runnera

Jeden runner = jedno repo GitHub. Katalog: /home/devops/actions-runner-<app> (wyjątek Auth: /home/devops/actions-runner bez przyrostka). Istniejące: actions-runner, actions-runner-quiz, actions-runner-ppl (oraz actions-runner-battleship).

Praca jako bb. Procesy runnera jako devops. Token: GitHub → to konkretne repo → Settings → Actions → Runners → New self-hosted runner. --url to adres repozytorium, nie domeny WWW.

sudo mkdir -p /home/devops/actions-runner-<app>
sudo chown -R devops:devops /home/devops/actions-runner-<app>
sudo -u devops tar xzf /home/devops/actions-runner-linux-x64-2.336.0.tar.gz -C /home/devops/actions-runner-<app>
sudo -u devops bash -lc 'cd /home/devops/actions-runner-<app> && ./config.sh --url https://github.com/<owner>/<repo> --token <TOKEN>'
cd /home/devops/actions-runner-<app>
sudo ./svc.sh install devops
sudo ./svc.sh start
systemctl status actions.runner.kandascan-<repo>.bb-Default-string

Nazwa usługi systemd po svc.sh install ma wzór actions.runner.kandascan-<repo>.bb-Default-string (tak jest na serwerze).

W YAML: runs-on: self-hosted. Nie dodawaj actions/setup-dotnet (Permission denied w /usr/share/dotnet). setup-node tylko przy Quizie — instaluje Node w katalogu joba.

4. Workflow Auth — tylko backend

Bez Node. Test: GET /swagger/index.html.

name: Deploy BaseAuth
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
      - name: Publish backend
        run: |
          cd src/BaseAuth.Api
          dotnet publish -c Release -o publish
      - name: Deploy to server
        run: |
          DEPLOY_DIR="/home/devops/baseauth/backend"
          mkdir -p "$DEPLOY_DIR"
          cp -r src/BaseAuth.Api/publish/* "$DEPLOY_DIR/"
      - name: Restart BaseAuth service
        run: sudo systemctl restart baseauth

5. Workflow Quiz — osobny frontend

Kolejność obowiązkowa: Node → npm ci / npm run build → kopia frontend/dist/* do backend/QuizHub.Api/wwwroot/ → publish → deploy → restart.

name: Deploy Quiz
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: npm
          cache-dependency-path: frontend/package-lock.json
      - name: Build frontend
        run: |
          cd frontend
          npm ci
          npm run build
      - name: Copy frontend to backend static folder
        run: |
          mkdir -p backend/QuizHub.Api/wwwroot
          cp -r frontend/dist/* backend/QuizHub.Api/wwwroot/
      - name: Publish backend
        run: |
          cd backend/QuizHub.Api
          dotnet publish -c Release -o publish
      - name: Deploy to server
        run: |
          DEPLOY_DIR="/home/devops/quiz/backend"
          mkdir -p "$DEPLOY_DIR"
          cp -r backend/QuizHub.Api/publish/* "$DEPLOY_DIR/"
      - name: Restart Quiz service
        run: sudo systemctl restart quizhub

6. Workflow PPL — frontend w wwwroot

Bez npm. Katalog płaski /home/devops/ppl.

name: Deploy PPL
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
      - name: Publish
        run: |
          cd PPL
          dotnet publish -c Release -o publish
      - name: Deploy to server
        run: |
          DEPLOY_DIR="/home/devops/ppl"
          mkdir -p "$DEPLOY_DIR"
          cp -r PPL/publish/* "$DEPLOY_DIR/"
      - name: Restart PPL service
        run: sudo systemctl restart ppl

7. Konfiguracja aplikacji — rozdzielone

Tylko Auth / API

ForwardedHeaders; w Production bez UseHttpsRedirection; Swagger na /swagger bez prefixu /prod. Test GET, nie HEAD.

Quiz

UI z wwwroot po npm build. Bind --urls http://0.0.0.0:5006. Brak SwaggerBasePath. SignalR wymaga nginx /hubs/.

PPL

UI już w wwwroot. Bind przez ASPNETCORE_URLS=http://0.0.0.0:5080. Brak Node i Swaggera.

8. systemd — trzy działające unity

baseauth.service

[Service]
User=devops
WorkingDirectory=/home/devops/baseauth/backend
ExecStart=/usr/bin/dotnet /home/devops/baseauth/backend/BaseAuth.Api.dll --urls http://0.0.0.0:5007
Environment=ASPNETCORE_ENVIRONMENT=Production

quizhub.service

[Service]
User=devops
WorkingDirectory=/home/devops/quiz/backend
ExecStart=/usr/bin/dotnet /home/devops/quiz/backend/QuizHub.Api.dll --urls http://0.0.0.0:5006
Environment=ASPNETCORE_ENVIRONMENT=Production

ppl.service

[Service]
User=devops
WorkingDirectory=/home/devops/ppl
ExecStart=/usr/bin/dotnet /home/devops/ppl/PPL.dll
Environment=ASPNETCORE_URLS=http://0.0.0.0:5080
Environment=ASPNETCORE_ENVIRONMENT=Production
sudo systemctl daemon-reload
sudo systemctl enable <usługa>
sudo visudo -f /etc/sudoers.d/devops-<usługa>
devops ALL=(ALL) NOPASSWD: /bin/systemctl restart <usługa>

9. Nginx — reverse proxy (pełne configi)

Wspólne: proxy_pass na localhost:port, nagłówki Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto. Różnice poniżej.

Auth — tylko location / (port 5007)

server {
    server_name auth.pepisboczek.ddns.net;
    location / {
        proxy_pass http://127.0.0.1:5007/;
        proxy_http_version 1.1;
        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;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/auth.pepisboczek.ddns.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.pepisboczek.ddns.net/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

Quiz — location / plus /hubs/ (port 5006, WebSocket)

server {
    server_name quiz.pepisboczek.ddns.net;
    location / {
        proxy_pass http://127.0.0.1:5006/;
        proxy_http_version 1.1;
        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://127.0.0.1:5006;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/quiz.pepisboczek.ddns.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/quiz.pepisboczek.ddns.net/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

PPL — tylko location / (port 5080)

server {
    server_name ppl.pepisboczek.ddns.net;
    location / {
        proxy_pass http://127.0.0.1:5080/;
        proxy_http_version 1.1;
        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;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/ppl.pepisboczek.ddns.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ppl.pepisboczek.ddns.net/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
sudo nano /etc/nginx/sites-available/<app>
sudo ln -s /etc/nginx/sites-available/<app> /etc/nginx/sites-enabled/<app>
sudo nginx -t
sudo systemctl reload nginx

10. SSL

sudo certbot --nginx -d <sub>.pepisboczek.ddns.net

HTTP→HTTPS dopisuje certbot. Testuj GET w przeglądarce.

11. Gotchas

  • Bez setup-dotnet na self-hosted.
  • bb vs devops — nie su devops.
  • Quiz: npm przed publish, inaczej pusty frontend.
  • Swagger i SwaggerBasePath tylko przy API.
  • /hubs/ w nginx tylko gdy jest SignalR (Quiz).
  • GET, nie HEAD.