diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..60bf18b --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(rm -f deploy/myansuran-webhook.service deploy/hooks.json deploy/README.md deploy/deploy.sh)", + "Bash(rmdir deploy *)", + "Bash(mkdir -p docker/webhook)" + ] + } +} diff --git a/.gitattributes b/.gitattributes index 047d409..7e3ecdd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ # Pastikan skrip shell sentiasa guna line ending LF walaupun di-commit dari Windows. *.sh text eol=lf -deploy/deploy.sh text eol=lf +deploy.sh text eol=lf docker/php-apache/entrypoint.sh text eol=lf diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..15e37bb --- /dev/null +++ b/deploy.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# myAnsuran KIK-JPPH — Production Deploy Script +# Dipanggil oleh webhook selepas git push ke Gitea (branch master) +set -e + +PROJECT_DIR="/srv/myansuran" +LOG="$PROJECT_DIR/deploy.log" + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG"; } + +log "=== Deploy dimulakan ===" + +cd "$PROJECT_DIR" + +log "git pull..." +git pull origin master + +# Kod di-bind-mount (./src:/var/www/html) jadi perubahan terus aktif — +# tak perlu rebuild image untuk perubahan kod biasa. +log "composer install..." +docker exec myansuran-app composer install --no-dev --optimize-autoloader --no-interaction + +log "migrate database..." +docker exec myansuran-app php artisan migrate --force + +log "optimize cache..." +docker exec myansuran-app php artisan optimize + +log "=== Deploy selesai ===" diff --git a/deploy/README.md b/deploy/README.md deleted file mode 100644 index 8bc9adb..0000000 --- a/deploy/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Auto-deploy via Git Webhook - -Bila ada `git push` ke Gitea, Gitea hantar webhook ke server. Server tarik kod -terbaru dan jalankan `docker compose up --build -d` secara automatik. - -Aliran: **push → Gitea webhook → `webhook` listener (port 9001) → `deploy.sh`** - -> Nota: port **9001** digunakan (bukan 9000 default) kerana server ini sudah -> ada satu lagi app guna webhook di port 9001. Pastikan tiada port bertindih. - ---- - -## 1. Setup di server production (sekali sahaja) - -Andaikan repo berada di `/opt/myansuran` (laraskan path dalam `hooks.json` -dan `myansuran-webhook.service` jika berbeza). - -```bash -# a) Pasang webhook listener -sudo apt-get update && sudo apt-get install -y webhook - -# b) Pastikan user 'deploy' wujud & ahli group docker (boleh tukar nama user) -sudo useradd -m -G docker deploy 2>/dev/null || sudo usermod -aG docker deploy - -# c) Pastikan skrip executable -chmod +x /opt/myansuran/deploy/deploy.sh - -# d) Set secret rahsia dalam hooks.json (ganti GANTI_DENGAN_SECRET_RAHSIA) -# Jana satu secret kuat: -openssl rand -hex 32 -# Lepas tu edit /opt/myansuran/deploy/hooks.json -> letak nilai itu pada "secret" - -# e) Pasang service systemd -sudo cp /opt/myansuran/deploy/myansuran-webhook.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable --now myansuran-webhook -sudo systemctl status myansuran-webhook -``` - -## 2. Setup webhook di Gitea - -Repo → **Settings → Webhooks → Add Webhook → Gitea**: - -- **Target URL:** `http://SERVER_IP:9001/hooks/myansuran-deploy` -- **HTTP Method:** `POST` -- **Content Type:** `application/json` -- **Secret:** secret SAMA seperti dalam `hooks.json` -- **Trigger:** Push events sahaja -- **Branch filter:** `main` - -Klik **Test Delivery** untuk uji. - -## 3. Uji manual - -```bash -# Jalankan deploy terus tanpa webhook (untuk debug) -/opt/myansuran/deploy/deploy.sh - -# Lihat log webhook -sudo journalctl -u myansuran-webhook -f -``` - ---- - -## Nota keselamatan - -- Port `9001` hanya perlu dicapai oleh Gitea. Hadkan dengan firewall: - `sudo ufw allow from to any port 9001 proto tcp` -- Atau letak di belakang Nginx/Apache reverse proxy dengan HTTPS. -- Signature HMAC-SHA256 menghalang sesiapa tanpa secret daripada trigger deploy. - -## Tukar branch - -Default deploy branch ialah `main`. Untuk branch lain, set dalam service: -`ExecStart=... ` tambah `Environment=BRANCH=staging`, dan tukar `refs/heads/main` -dalam `hooks.json` kepada branch berkenaan. diff --git a/deploy/deploy.sh b/deploy/deploy.sh deleted file mode 100644 index 822a5b8..0000000 --- a/deploy/deploy.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# -# Skrip deploy yang dipanggil oleh webhook bila ada push ke Gitea. -# Ia mengesan sendiri lokasi repo (folder induk kepada deploy/), -# tarik kod terbaru, dan bina semula container. -# -set -euo pipefail - -# Cabang yang akan di-deploy (boleh override: BRANCH=staging ./deploy.sh) -BRANCH="${BRANCH:-master}" - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(dirname "$SCRIPT_DIR")" -cd "$REPO_DIR" - -LOG() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } - -LOG "Mula deploy untuk $REPO_DIR (branch: $BRANCH)" - -# Tarik kod terbaru — reset --hard supaya server sentiasa padan dengan remote. -git fetch --prune origin -git reset --hard "origin/${BRANCH}" - -# Bina & start semula container -docker compose up --build -d - -# Bersihkan image lama supaya disk tak penuh -docker image prune -f >/dev/null 2>&1 || true - -# Optional: jalankan migration (buang komen kalau perlu) -# docker compose exec -T app php artisan migrate --force - -LOG "Deploy selesai." diff --git a/deploy/hooks.json b/deploy/hooks.json deleted file mode 100644 index d5df5ae..0000000 --- a/deploy/hooks.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "id": "myansuran-deploy", - "execute-command": "/opt/myansuran/deploy/deploy.sh", - "command-working-directory": "/opt/myansuran", - "response-message": "Deploy myansuran dicetuskan.", - "include-command-output-in-response": false, - "trigger-rule": { - "and": [ - { - "match": { - "type": "payload-hmac-sha256", - "secret": "GANTI_DENGAN_SECRET_RAHSIA", - "parameter": { "source": "header", "name": "X-Gitea-Signature" } - } - }, - { - "match": { - "type": "value", - "value": "refs/heads/master", - "parameter": { "source": "payload", "name": "ref" } - } - } - ] - } - } -] diff --git a/deploy/myansuran-webhook.service b/deploy/myansuran-webhook.service deleted file mode 100644 index cb84a96..0000000 --- a/deploy/myansuran-webhook.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=myAnsuran git deploy webhook listener -After=network.target docker.service -Requires=docker.service - -[Service] -# User mesti ahli group 'docker' supaya boleh jalankan docker compose tanpa sudo. -User=deploy -WorkingDirectory=/opt/myansuran -ExecStart=/usr/bin/webhook -hooks /opt/myansuran/deploy/hooks.json -port 9001 -verbose -hotreload -Restart=always -RestartSec=5 - -[Install] -WantedBy=multi-user.target diff --git a/docker-compose.yml b/docker-compose.yml index 5c6c439..1fb16e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,24 @@ services: networks: - backend_net + # ── Webhook Deploy (Gitea → auto pull + migrate) ─────────────────────────── + webhook: + build: + context: ./docker/webhook + container_name: myansuran-webhook + restart: always + environment: + WEBHOOK_SECRET: ${WEBHOOK_SECRET} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /root/.ssh:/root/.ssh:ro + - ./docker/webhook/hooks.json:/etc/webhook/hooks.json:ro + - ./deploy.sh:/deploy.sh:ro + - .:/srv/myansuran + command: -hooks=/etc/webhook/hooks.json -template -verbose + networks: + - backend_net + networks: backend_net: name: backend_net diff --git a/docker/php-apache/Dockerfile b/docker/php-apache/Dockerfile index 1d69b14..dcb190d 100644 --- a/docker/php-apache/Dockerfile +++ b/docker/php-apache/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install -y \ && docker-php-ext-install pdo_mysql gd intl zip \ && pecl install redis \ && docker-php-ext-enable redis \ - && a2enmod rewrite headers \ + && a2enmod rewrite headers proxy proxy_http \ && rm -rf /var/lib/apt/lists/* COPY --from=composer:2 /usr/bin/composer /usr/bin/composer diff --git a/docker/php-apache/vhost.conf b/docker/php-apache/vhost.conf index aec7291..5df2cd0 100644 --- a/docker/php-apache/vhost.conf +++ b/docker/php-apache/vhost.conf @@ -2,6 +2,12 @@ ServerAdmin webmaster@localhost DocumentRoot /var/www/html/public + # ── Webhook deploy (Gitea) — proxy ke container webhook ─────────────────── + # https://myansuran.mbip.my/hooks/myansuran-deploy → myansuran-webhook:9000 + ProxyPreserveHost On + ProxyPass /hooks/ http://myansuran-webhook:9000/hooks/ + ProxyPassReverse /hooks/ http://myansuran-webhook:9000/hooks/ + AllowOverride All Require all granted diff --git a/docker/webhook/Dockerfile b/docker/webhook/Dockerfile new file mode 100644 index 0000000..cc127b7 --- /dev/null +++ b/docker/webhook/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.23-alpine AS builder +RUN go install github.com/adnanh/webhook@2.8.1 + +FROM alpine:3.21 +RUN apk add --no-cache git openssh-client docker-cli +COPY --from=builder /go/bin/webhook /usr/local/bin/webhook +EXPOSE 9000 +ENTRYPOINT ["/usr/local/bin/webhook"] diff --git a/docker/webhook/hooks.json b/docker/webhook/hooks.json new file mode 100644 index 0000000..15efccd --- /dev/null +++ b/docker/webhook/hooks.json @@ -0,0 +1,32 @@ +[ + { + "id": "myansuran-deploy", + "execute-command": "/deploy.sh", + "command-working-directory": "/srv/myansuran", + "response-message": "Deploy myAnsuran dimulakan.", + "trigger-rule": { + "and": [ + { + "match": { + "type": "payload-hmac-sha256", + "secret": "{{ getenv "WEBHOOK_SECRET" }}", + "parameter": { + "source": "header", + "name": "X-Gitea-Signature" + } + } + }, + { + "match": { + "type": "value", + "value": "refs/heads/master", + "parameter": { + "source": "payload", + "name": "ref" + } + } + } + ] + } + } +] diff --git a/src/bootstrap/app.php b/src/bootstrap/app.php index e7d33d9..c18e0fa 100644 --- a/src/bootstrap/app.php +++ b/src/bootstrap/app.php @@ -11,6 +11,10 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { + // App berada di belakang reverse proxy (proxy host → Apache → Laravel), + // jadi percaya header X-Forwarded-* supaya HTTPS, host & IP klien betul. + $middleware->trustProxies(at: '*'); + $middleware->alias([ 'role' => \App\Http\Middleware\EnsureRole::class, 'profile.completed' => \App\Http\Middleware\EnsureProfileCompleted::class,