Template · 3 warianty · prawdziwe YAML-e

Deploy: wybierz typ aplikacji

Każdy przycisk pokazuje osobny playbook: inny workflow, inna konfiguracja aplikacji i inny przykład nginx. Nic z API/Swagger nie miesza się z frontendem.

Konwencja nazw (serwery i katalogi)

Auth (tylko API)Quiz (osobny frontend)PPL (wwwroot w repo)
Katalog aplikacji/home/devops/baseauth/backend/home/devops/quiz/backend/home/devops/ppl
Katalog runnera/home/devops/actions-runner/home/devops/actions-runner-quiz/home/devops/actions-runner-ppl
Usługa systemdbaseauthquizhubppl
Usługa runneraactions.runner.kandascan-baseauthbackend.bb-Default-stringactions.runner.kandascan-quiz.bb-Default-stringactions.runner.kandascan-ppl.bb-Default-string
Port Kestrel500750065080
Domenaauth.pepisboczek.ddns.netquiz.pepisboczek.ddns.netppl.pepisboczek.ddns.net
DLLBaseAuth.Api.dllQuizHub.Api.dllPPL.dll

Dwie dozwolone konwencje katalogu aplikacji:

  • /home/devops/<app>/backend — Auth, Quiz
  • /home/devops/<app> — PPL

Runner: jeden self-hosted runner na jedno repo GitHub, katalog /home/devops/actions-runner-<app> (wyjątek: Auth stoi w /home/devops/actions-runner bez przyrostka).

Pracuj jako bb. Usługi i runner działają jako devops. Nie używaj su devops.

Instalacja runnera GitHub Actions

Robisz to raz na nowe repo, zanim pierwszy workflow ma szansę ruszyć. Token bierz z GitHub: repo → Settings → Actions → Runners → New self-hosted runner.

  1. Katalog (jako bb):
sudo mkdir -p /home/devops/actions-runner-<app>
sudo chown -R devops:devops /home/devops/actions-runner-<app>
cd /home/devops/actions-runner-<app>

Archiwum runnera jest już na serwerze: /home/devops/actions-runner-linux-x64-2.336.0.tar.gz (albo pobierz nowsze z GitHuba).

sudo -u devops tar xzf /home/devops/actions-runner-linux-x64-2.336.0.tar.gz -C /home/devops/actions-runner-<app>

Konfiguracja (jako devops, w katalogu runnera). --url to adres repozytorium, nie strony WWW:

sudo -u devops bash -lc 'cd /home/devops/actions-runner-<app> && ./config.sh --url https://github.com/<owner>/<repo> --token <TOKEN>'

Instalacja i start usługi (jako root):

cd /home/devops/actions-runner-<app>
sudo ./svc.sh install devops
sudo ./svc.sh start
systemctl status actions.runner.kandascan-<repo>.bb-Default-string
Nie dodawaj w YAML kroku actions/setup-dotnet. Runner działa jako devops i nie może pisać do /usr/share/dotnet. Używaj systemowego /usr/bin/dotnet. setup-node przy Quizie jest OK — instaluje Node w katalogu joba, nie w systemie.

W YAML zawsze: runs-on: self-hosted (domyślna etykieta po config.sh).

Wybór wariantu

Po kliknięciu widać tylko ten playbook: workflow, konfigurację app, systemd i nginx.

Playbook: tylko backend (Auth)

Wzorzec: BaseAuth. Brak frontendu, brak Node. Testujesz Swagger.

Prawdziwy workflow (działa na serwerze)

name: Deploy BaseAuth

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: self-hosted

    steps:
      - name: Checkout
        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

Szablon dla nowego API

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

Tu nie ma Setup Node.js, npm ani kopiowania dist.

Konfiguracja aplikacji (tylko API)

Za nginx zaufaj X-Forwarded-Proto. W Production wyłącz UseHttpsRedirection — inaczej aplikacja zrzuca na port Kestrel i przeglądarka krzyczy „Not secure”.

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});
var app = builder.Build();
app.UseForwardedHeaders();
if (!app.Environment.IsProduction())
{
    app.UseHttpsRedirection();
}
Na dedykowanej subdomenie nie ustawiaj SwaggerBasePath: "/prod". Swagger ma być na /swagger. Testuj GET /swagger/index.html, nie curl -I (HEAD często 404).

Serwer i systemd (Auth)

sudo mkdir -p /home/devops/<app>/backend
sudo chown -R devops:devops /home/devops/<app>
sudo nano /etc/systemd/system/<app-service>.service

Działający unit BaseAuth:

[Unit]
Description=BaseAuth App
After=network.target

[Service]
Type=simple
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
Restart=always
RestartSec=5
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable <app-service>
sudo visudo -f /etc/sudoers.d/devops-<app-service>
# treść:
devops ALL=(ALL) NOPASSWD: /bin/systemctl restart <app-service>

Nginx — reverse proxy (Auth, bez WebSocket)

Tylko location / na port API. Bez /hubs/.

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;
}
server {
    listen 80;
    server_name auth.pepisboczek.ddns.net;
    return 301 https://$host$request_uri;
}

Przed certbotem wystarczy listen 80 + ten sam location /.

SSL

sudo ln -s /etc/nginx/sites-available/<app> /etc/nginx/sites-enabled/<app>
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d <sub>.pepisboczek.ddns.net
curl -s -o /dev/null -w "%{http_code}\n" https://auth.pepisboczek.ddns.net/swagger/index.html

Checklist — tylko backend

  • YAML: checkout → publish → cp do .../backend → restart — bez Node
  • ForwardedHeaders + brak HttpsRedirection w Production
  • Swagger na /swagger, test GET
  • systemd --urls http://0.0.0.0:<port>
  • nginx tylko location /
  • sudoers + runner w actions-runner-<app>

Gotchas (wszystkie warianty)

  • Runner = devops, praca = bb, nigdy su devops.
  • Bez setup-dotnet (Permission denied na /usr/share/dotnet).
  • Sudoers: wyłącznie systemctl restart <ta-usługa>.
  • Testuj GET, nie HEAD.
  • Kestrel na 0.0.0.0, nie sam localhost.