Deploy hub to SEOSoft appserver; run CI on the self-hosted runner
Some checks are pending
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
CI / Build og unit tests (push) Successful in 1m10s
deploy hub / deploy (push) Successful in 19s
CI / MSI-installer (push) Successful in 50s
Some checks are pending
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
CI / Build og unit tests (push) Successful in 1m10s
deploy hub / deploy (push) Successful in 19s
CI / MSI-installer (push) Successful in 50s
deploy-hub.yml builds deploy/hub/Dockerfile on the appserver runner, writes .env from repo secrets and starts docker-compose.prod.yml (external PostgreSQL, /data bind-mounted), then polls /health. ci.yml used runs-on: docker, which no runner has, so jobs never started. Build/tests and the MSI now run inside the SDK image via deploy/ci/Dockerfile, because the host-mode runner ignores container: and has no apt-get. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
188a27381a
commit
4fdeff87fa
4 changed files with 205 additions and 30 deletions
|
|
@ -6,52 +6,36 @@ on:
|
||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
|
|
||||||
DOTNET_NOLOGO: "1"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build og unit tests
|
name: Build og unit tests
|
||||||
# "docker" er standard-label for en Forgejo runner. Ret til jeres runners label, hvis det er et andet.
|
# Self-hosted appserver-runner (host-mode). Selve bygget kører i SDK-imaget via deploy/ci/Dockerfile.
|
||||||
runs-on: docker
|
runs-on: [self-hosted, appserver]
|
||||||
container:
|
timeout-minutes: 30
|
||||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
run: |
|
uses: actions/checkout@v4
|
||||||
git init -q .
|
- name: Restore, build og unit tests
|
||||||
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
|
run: docker build --progress=plain -f deploy/ci/Dockerfile --target test -t "sqlinsight-ci:${{ github.sha }}" .
|
||||||
git -c http.extraHeader="Authorization: token ${{ secrets.GITHUB_TOKEN }}" fetch -q --depth 1 origin "${{ github.sha }}"
|
- name: Ryd op
|
||||||
git checkout -q FETCH_HEAD
|
if: always()
|
||||||
- name: Restore
|
run: docker image rm "sqlinsight-ci:${{ github.sha }}" 2>/dev/null || true
|
||||||
run: dotnet restore
|
|
||||||
- name: Build
|
|
||||||
run: dotnet build --no-restore --configuration Release
|
|
||||||
- name: Unit tests
|
|
||||||
run: dotnet test --no-build --configuration Release --filter "Category!=Integration"
|
|
||||||
|
|
||||||
msi:
|
msi:
|
||||||
name: MSI-installer
|
name: MSI-installer
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: docker
|
runs-on: [self-hosted, appserver]
|
||||||
container:
|
timeout-minutes: 30
|
||||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
run: |
|
uses: actions/checkout@v4
|
||||||
git init -q .
|
|
||||||
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
|
|
||||||
git -c http.extraHeader="Authorization: token ${{ secrets.GITHUB_TOKEN }}" fetch -q --depth 1 origin "${{ github.sha }}"
|
|
||||||
git checkout -q FETCH_HEAD
|
|
||||||
- name: Værktøjer (wixl, msitools; nodejs til upload-artifact)
|
|
||||||
run: apt-get update -q && apt-get install -y -q --no-install-recommends wixl msitools python3 nodejs
|
|
||||||
- name: Byg og kontrollér MSI
|
- name: Byg og kontrollér MSI
|
||||||
# Et tag som v1.2.0 giver version 1.2.0; ellers versionen fra Directory.Build.props.
|
# Et tag som v1.2.0 giver version 1.2.0; ellers versionen fra Directory.Build.props.
|
||||||
run: |
|
run: |
|
||||||
version="${GITHUB_REF_NAME#v}"
|
version="${GITHUB_REF_NAME#v}"
|
||||||
case "$GITHUB_REF" in refs/tags/v*) ;; *) version=$(sed -n 's|.*<Version>\(.*\)</Version>.*|\1|p' Directory.Build.props) ;; esac
|
case "$GITHUB_REF" in refs/tags/v*) ;; *) version=$(sed -n 's|.*<Version>\(.*\)</Version>.*|\1|p' Directory.Build.props) ;; esac
|
||||||
installer/build-msi.sh "$version"
|
docker buildx build --progress=plain -f deploy/ci/Dockerfile --target msi --build-arg VERSION="$version" --output type=local,dest=dist .
|
||||||
|
ls -l dist/*.msi
|
||||||
- name: Gem MSI
|
- name: Gem MSI
|
||||||
uses: https://code.forgejo.org/actions/upload-artifact@v3
|
uses: https://code.forgejo.org/actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
137
.forgejo/workflows/deploy-hub.yml
Normal file
137
.forgejo/workflows/deploy-hub.yml
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
# =============================================================================
|
||||||
|
# Deploy SQLInsight Hub til appserveren ved push til main.
|
||||||
|
#
|
||||||
|
# Kører på den self-hostede appserver-runner (host-mode, docker.sock).
|
||||||
|
# Runner-containeren har ikke INSTALL_DIR mountet, så .env, compose-filen og
|
||||||
|
# 'docker compose up' håndteres via kortlivede helper-containere med
|
||||||
|
# INSTALL_DIR bind-mountet fra værten.
|
||||||
|
#
|
||||||
|
# Alle miljøværdier (også IP'er) kommer fra Forgejo repo-secrets.
|
||||||
|
#
|
||||||
|
# Påkrævede secrets: HOST_BIND HOST_PORT DB_SERVER DB_PORT DB_NAME DB_USER DB_PASSWORD KNOWN_PROXY
|
||||||
|
# Valgfri: TRUSTED_KEY_ID + TRUSTED_KEY (offentlig licensnøgle fra keygen)
|
||||||
|
# =============================================================================
|
||||||
|
name: deploy hub
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "src/**"
|
||||||
|
- "deploy/hub/**"
|
||||||
|
- "Directory.*.props"
|
||||||
|
- "global.json"
|
||||||
|
- ".forgejo/workflows/deploy-hub.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-sqlinsight-hub
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
INSTALL_DIR: /opt/sqlinsight-hub
|
||||||
|
IMAGE: sqlinsight-hub
|
||||||
|
CONTAINER: sqlinsight-hub
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: [self-hosted, appserver]
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
docker build -f deploy/hub/Dockerfile -t "${IMAGE}:latest" -t "${IMAGE}:${{ github.sha }}" .
|
||||||
|
|
||||||
|
- name: Write .env + docker-compose.yml to INSTALL_DIR
|
||||||
|
env:
|
||||||
|
HOST_BIND: ${{ secrets.HOST_BIND }}
|
||||||
|
HOST_PORT: ${{ secrets.HOST_PORT }}
|
||||||
|
DB_SERVER: ${{ secrets.DB_SERVER }}
|
||||||
|
DB_PORT: ${{ secrets.DB_PORT }}
|
||||||
|
DB_NAME: ${{ secrets.DB_NAME }}
|
||||||
|
DB_USER: ${{ secrets.DB_USER }}
|
||||||
|
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||||
|
KNOWN_PROXY: ${{ secrets.KNOWN_PROXY }}
|
||||||
|
TRUSTED_KEY_ID: ${{ secrets.TRUSTED_KEY_ID }}
|
||||||
|
TRUSTED_KEY: ${{ secrets.TRUSTED_KEY }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
required="HOST_BIND HOST_PORT DB_SERVER DB_PORT DB_NAME DB_USER DB_PASSWORD KNOWN_PROXY"
|
||||||
|
missing=0
|
||||||
|
for v in $required; do
|
||||||
|
if [ -z "$(printenv "$v" || true)" ]; then
|
||||||
|
echo "ERROR: required secret $v is not set in repo settings"
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ "$missing" -eq 0 ] || exit 1
|
||||||
|
|
||||||
|
# Værdier skrives single-quoted, så compose ikke udvider '$' i passwords
|
||||||
|
for v in $required TRUSTED_KEY_ID TRUSTED_KEY; do
|
||||||
|
case "$(printenv "$v" || true)" in
|
||||||
|
*"'"*|*";"*) echo "ERROR: secret $v indeholder ' eller ; – ikke understøttet"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
{
|
||||||
|
printf "HOST_BIND='%s'\n" "$HOST_BIND"
|
||||||
|
printf "HOST_PORT='%s'\n" "$HOST_PORT"
|
||||||
|
printf "ConnectionStrings__Hub='Host=%s;Port=%s;Database=%s;Username=%s;Password=%s;GSS Encryption Mode=Disable'\n" \
|
||||||
|
"$DB_SERVER" "$DB_PORT" "$DB_NAME" "$DB_USER" "$DB_PASSWORD"
|
||||||
|
printf "SQLInsight__Hub__KnownProxies__0='%s'\n" "$KNOWN_PROXY"
|
||||||
|
if [ -n "${TRUSTED_KEY_ID:-}" ] && [ -n "${TRUSTED_KEY:-}" ]; then
|
||||||
|
printf "SQLInsight__Hub__TrustedKeys__%s='%s'\n" "$TRUSTED_KEY_ID" "$TRUSTED_KEY"
|
||||||
|
else
|
||||||
|
echo "WARNING: TRUSTED_KEY_ID/TRUSTED_KEY ikke sat – hubben kan ikke verificere licenser" >&2
|
||||||
|
fi
|
||||||
|
} > "$tmp/.env"
|
||||||
|
cp deploy/hub/docker-compose.prod.yml "$tmp/docker-compose.yml"
|
||||||
|
|
||||||
|
tar -C "$tmp" -cf - .env docker-compose.yml | docker run --rm -i \
|
||||||
|
-v "${INSTALL_DIR}:/out" alpine:3 \
|
||||||
|
sh -c 'tar -C /out -xf - && chown root:root /out/.env /out/docker-compose.yml && chmod 600 /out/.env && chmod 644 /out/docker-compose.yml'
|
||||||
|
rm -rf "$tmp"
|
||||||
|
|
||||||
|
- name: Deploy (compose up)
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
docker run --rm \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v "${INSTALL_DIR}:${INSTALL_DIR}" \
|
||||||
|
-w "${INSTALL_DIR}" \
|
||||||
|
docker:cli \
|
||||||
|
docker compose --project-name sqlinsight-hub up -d --force-recreate --remove-orphans
|
||||||
|
|
||||||
|
- name: Wait for /health
|
||||||
|
env:
|
||||||
|
HOST_BIND: ${{ secrets.HOST_BIND }}
|
||||||
|
HOST_PORT: ${{ secrets.HOST_PORT }}
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
# aspnet-imaget har hverken curl eller wget – poll udefra
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if docker run --rm alpine:3 wget -qO- -T 3 "http://${HOST_BIND}:${HOST_PORT}/health" 2>/dev/null | grep -q '"ok"'; then
|
||||||
|
echo "${CONTAINER} is healthy"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "attempt $i: not ready"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
echo "${CONTAINER} did not answer /health within 150s — last 100 log lines:"
|
||||||
|
docker logs --tail 100 "${CONTAINER}" || true
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Prune old images
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker image prune -f || true
|
||||||
|
# Behold latest + de 3 nyeste sha-tags
|
||||||
|
docker images "${IMAGE}" --format '{{.Tag}} {{.CreatedAt}}' \
|
||||||
|
| grep -v '^latest ' | sort -k2 -r | awk 'NR>3 {print $1}' \
|
||||||
|
| xargs -r -I{} docker image rm "${IMAGE}:{}" || true
|
||||||
24
deploy/ci/Dockerfile
Normal file
24
deploy/ci/Dockerfile
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# CI-byg til den self-hostede Forgejo runner (host-mode: jobs kører i runnerens
|
||||||
|
# egen Alpine-container, så 'container:' og apt-get virker ikke dér).
|
||||||
|
# Build og tests kører i stedet i SDK-imaget via docker build:
|
||||||
|
# docker build -f deploy/ci/Dockerfile --target test .
|
||||||
|
# docker buildx build -f deploy/ci/Dockerfile --target msi --build-arg VERSION=1.2.0 --output type=local,dest=dist .
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS test
|
||||||
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN dotnet restore \
|
||||||
|
&& dotnet build --no-restore --configuration Release \
|
||||||
|
&& dotnet test --no-build --configuration Release --filter "Category!=Integration"
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS msi-build
|
||||||
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
|
||||||
|
RUN apt-get update -q && apt-get install -y -q --no-install-recommends wixl msitools python3 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
ARG VERSION=1.0.0
|
||||||
|
RUN installer/build-msi.sh "$VERSION"
|
||||||
|
|
||||||
|
FROM scratch AS msi
|
||||||
|
COPY --from=msi-build /src/dist/ /
|
||||||
30
deploy/hub/docker-compose.prod.yml
Normal file
30
deploy/hub/docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# SQLInsight Hub i SEOSofts drift (appserver). Kopieres til INSTALL_DIR af
|
||||||
|
# .forgejo/workflows/deploy-hub.yml sammen med en .env, der genereres fra
|
||||||
|
# Forgejo repo-secrets. PostgreSQL kører på en separat DB-server, ikke i compose.
|
||||||
|
#
|
||||||
|
# Image `sqlinsight-hub:latest` bygges lokalt af runneren; intet registry-push.
|
||||||
|
#
|
||||||
|
# Manuel drift på appserveren:
|
||||||
|
# cd /opt/sqlinsight-hub && docker compose ps | logs -f | restart
|
||||||
|
# docker exec -it sqlinsight-hub dotnet SQLInsight.Hub.dll add-staff <brugernavn> "<navn>"
|
||||||
|
|
||||||
|
services:
|
||||||
|
hub:
|
||||||
|
image: sqlinsight-hub:latest
|
||||||
|
container_name: sqlinsight-hub
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
SQLInsight__Hub__UpdateFeedPath: /data/feed
|
||||||
|
volumes:
|
||||||
|
# keys/ (login-cookies) og feed/. Tag backup; mistes keys, logges alle ud.
|
||||||
|
- ./data:/data
|
||||||
|
ports:
|
||||||
|
# Bind til DMZ-IP (HOST_BIND i .env). Reverse proxyen terminerer TLS.
|
||||||
|
- "${HOST_BIND}:${HOST_PORT}:8080"
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
Loading…
Add table
Reference in a new issue