diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..ec5e39c --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,56 @@ +name: Deploy SingleChat + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + SSH_HOST: ${{ vars.PROD_HOST }} + SSH_PORT: ${{ vars.PROD_PORT }} + SSH_USER: ${{ vars.PROD_USER }} + DEPLOY_SCRIPT: ${{ vars.PROD_DEPLOY_SCRIPT }} + + steps: + - name: Show resolved non-secret config + run: | + echo "SSH_HOST=$SSH_HOST" + echo "SSH_PORT=$SSH_PORT" + echo "SSH_USER=$SSH_USER" + echo "DEPLOY_SCRIPT=${DEPLOY_SCRIPT:-/usr/local/bin/actualize-singlechat.sh}" + + - name: Prepare SSH + run: | + set -e + mkdir -p ~/.ssh + printf '%s' "${{ secrets.PROD_SSH_KEY_B64 }}" | base64 -d > ~/.ssh/id_deploy + chmod 600 ~/.ssh/id_deploy + ssh-keygen -l -f ~/.ssh/id_deploy + ssh-keyscan -p "$SSH_PORT" "$SSH_HOST" >> ~/.ssh/known_hosts + + - name: Test SSH connection + run: | + set -e + ssh -i ~/.ssh/id_deploy \ + -o StrictHostKeyChecking=no \ + -o BatchMode=yes \ + -o ConnectTimeout=10 \ + -p "$SSH_PORT" \ + "$SSH_USER@$SSH_HOST" \ + "echo SSH OK" + + - name: Run deployment script + run: | + set -e + script="${DEPLOY_SCRIPT:-/usr/local/bin/actualize-singlechat.sh}" + ssh -i ~/.ssh/id_deploy \ + -o StrictHostKeyChecking=no \ + -o BatchMode=yes \ + -o ConnectTimeout=10 \ + -p "$SSH_PORT" \ + "$SSH_USER@$SSH_HOST" \ + "$script" diff --git a/README-DEPLOYMENT.md b/README-DEPLOYMENT.md index 9afa7f9..f3e2902 100644 --- a/README-DEPLOYMENT.md +++ b/README-DEPLOYMENT.md @@ -53,6 +53,24 @@ sudo journalctl -u ypchat -f ## Troubleshooting +## Auto-Rollout mit Gitea + +Der Workflow `.gitea/workflows/deploy.yml` startet bei Push auf `main` per SSH das Server-Skript `/usr/local/bin/actualize-singlechat.sh`. + +Gitea-Konfiguration: + +- Variables: `PROD_HOST`, `PROD_PORT`, `PROD_USER` +- Optional: `PROD_DEPLOY_SCRIPT`, falls der Skriptpfad abweicht +- Secret: `PROD_SSH_KEY_B64` mit dem base64-kodierten privaten Deploy-Key + +Server-Skript installieren: + +```bash +sudo install -m 755 scripts/actualize-singlechat.sh /usr/local/bin/actualize-singlechat.sh +``` + +Das Skript aktualisiert `/opt/ypchat`, baut den Client neu und startet `ypchat` per systemd neu. + ### Service startet nicht ```bash @@ -86,4 +104,3 @@ cd /opt/ypchat sudo -u www-data npm run build sudo -u www-data cp -r client/dist docroot/ ``` - diff --git a/README-PRODUCTION.md b/README-PRODUCTION.md index 4791dc5..d1c3eee 100644 --- a/README-PRODUCTION.md +++ b/README-PRODUCTION.md @@ -99,6 +99,28 @@ sudo systemctl stop singlechat ## Updates +### Automatisch per Gitea Actions + +Der Workflow `.gitea/workflows/deploy.yml` deployt bei jedem Push auf `main` per SSH auf den Produktionsserver und startet dort das Rollout-Skript. + +In Gitea müssen dafür gesetzt sein: + +- Repository Variables: + - `PROD_HOST`: Produktionsserver, z.B. `tsschulz.de` + - `PROD_PORT`: SSH-Port, z.B. `2222` + - `PROD_USER`: SSH-User für den Deploy + - `PROD_DEPLOY_SCRIPT`: optional, Standard ist `/usr/local/bin/actualize-singlechat.sh` +- Repository Secret: + - `PROD_SSH_KEY_B64`: privater SSH-Key base64-kodiert + +Auf dem Produktionsserver das Rollout-Skript installieren: + +```bash +sudo install -m 755 scripts/actualize-singlechat.sh /usr/local/bin/actualize-singlechat.sh +``` + +Das Skript aktualisiert `/opt/ypchat` aus `ssh://git@tsschulz.de:2222/torsten/singlechat`, installiert Dependencies mit `npm ci`, baut den Client, aktualisiert `docroot/dist` und startet `ypchat` neu. Bei Bedarf können `APP_DIR`, `REPO_URL`, `BRANCH` und `SERVICE_NAME` als Environment-Variablen überschrieben werden. + Nach Code-Änderungen: ```bash @@ -159,4 +181,3 @@ Die folgenden Umgebungsvariablen können in `.env` gesetzt werden: - **HTTPS**: Stelle sicher, dass SSL/TLS korrekt konfiguriert ist - **Firewall**: Port 4000 sollte nur von localhost erreichbar sein - **Updates**: Halte Node.js und alle Dependencies aktuell - diff --git a/scripts/actualize-singlechat.sh b/scripts/actualize-singlechat.sh new file mode 100755 index 0000000..2c1516c --- /dev/null +++ b/scripts/actualize-singlechat.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash + +set -euo pipefail + +APP_DIR="${APP_DIR:-/opt/ypchat}" +REPO_URL="${REPO_URL:-ssh://git@tsschulz.de:2222/torsten/singlechat}" +BRANCH="${BRANCH:-main}" +SERVICE_NAME="${SERVICE_NAME:-ypchat}" +RUN_USER="${RUN_USER:-www-data}" +RUN_GROUP="${RUN_GROUP:-www-data}" +LOCK_FILE="${LOCK_FILE:-/tmp/actualize-singlechat.lock}" +NPM_CACHE_DIR="${NPM_CACHE_DIR:-$APP_DIR/.npm-cache}" + +log() { + printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" +} + +run_as_app_user() { + if [ "$(id -u)" -eq 0 ]; then + sudo -u "$RUN_USER" env HOME="$APP_DIR" npm_config_cache="$NPM_CACHE_DIR" "$@" + else + env HOME="$APP_DIR" npm_config_cache="$NPM_CACHE_DIR" "$@" + fi +} + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "FEHLER: $1 ist nicht installiert." >&2 + exit 1 + fi +} + +need_cmd git +need_cmd npm +need_cmd node +need_cmd rsync +need_cmd flock +need_cmd openssl +if [ "$(id -u)" -ne 0 ]; then + need_cmd sudo +fi + +exec 9>"$LOCK_FILE" +if ! flock -n 9; then + echo "Deployment laeuft bereits: $LOCK_FILE" >&2 + exit 1 +fi + +log "Starte SingleChat Rollout" +log "APP_DIR=$APP_DIR" +log "REPO_URL=$REPO_URL" +log "BRANCH=$BRANCH" +log "SERVICE_NAME=$SERVICE_NAME" + +mkdir -p "$APP_DIR" "$NPM_CACHE_DIR" + +if [ ! -d "$APP_DIR/.git" ]; then + log "Erstelle initialen Git-Checkout fuer $APP_DIR" + tmp_checkout="$(mktemp -d)" + git clone --branch "$BRANCH" --single-branch "$REPO_URL" "$tmp_checkout" + rsync -a --delete \ + --exclude '.env' \ + --exclude '.npm-cache/' \ + --exclude 'logs/' \ + --exclude 'tmp/' \ + "$tmp_checkout/" "$APP_DIR/" + rm -rf "$tmp_checkout" +fi + +if [ "$(id -u)" -eq 0 ]; then + chown -R "$RUN_USER:$RUN_GROUP" "$APP_DIR" "$NPM_CACHE_DIR" +fi + +cd "$APP_DIR" + +if ! git remote get-url origin >/dev/null 2>&1; then + git remote add origin "$REPO_URL" +fi + +current_origin="$(git remote get-url origin)" +if [ "$current_origin" != "$REPO_URL" ]; then + log "Setze Git-Origin von $current_origin auf $REPO_URL" + git remote set-url origin "$REPO_URL" +fi + +log "Hole neuesten Stand" +git fetch --prune origin "$BRANCH" +git reset --hard "origin/$BRANCH" +git clean -fd \ + -e .env \ + -e .npm-cache/ \ + -e logs/ \ + -e tmp/ \ + -e docroot/dist/ + +if [ "$(id -u)" -eq 0 ]; then + chown -R "$RUN_USER:$RUN_GROUP" "$APP_DIR" "$NPM_CACHE_DIR" +fi + +log "Installiere Root-Dependencies" +run_as_app_user npm ci + +log "Installiere Client-Dependencies" +run_as_app_user npm --prefix client ci + +if [ ! -f "$APP_DIR/.env" ]; then + log "Erstelle .env" + session_secret="$(openssl rand -hex 32)" + cat > "$APP_DIR/.env" </dev/null 2>&1; then + log "Starte Service neu: $SERVICE_NAME" + if [ "$(id -u)" -eq 0 ]; then + systemctl restart "$SERVICE_NAME" + systemctl --no-pager --lines=20 status "$SERVICE_NAME" + else + sudo systemctl restart "$SERVICE_NAME" + sudo systemctl --no-pager --lines=20 status "$SERVICE_NAME" + fi +else + log "systemctl nicht gefunden, Service-Neustart uebersprungen" +fi + +log "SingleChat Rollout abgeschlossen"