Compare commits
20 Commits
nico/2-apr
...
tasks/auth
| Author | SHA1 | Date | |
|---|---|---|---|
| e73798a0f2 | |||
| b403bc754c | |||
| 67b87f145e | |||
| dd09d7c90a | |||
| d1d54e5c25 | |||
| 0a4b85fd82 | |||
|
|
6064ef0759 | ||
| 1c00c326c9 | |||
| 4eba96140d | |||
| 4dfcf20322 | |||
|
|
6d26ace8ab | ||
|
|
f5566bca2c | ||
|
|
ba964df32c | ||
|
|
df3f382a97 | ||
|
|
7368a367f4 | ||
|
|
ed664d5b10 | ||
|
|
0ba30aa5b2 | ||
|
|
790d6535e5 | ||
|
|
46ce16ae97 | ||
| d66a952d4c |
@@ -11,6 +11,9 @@ SEAFILE_PUBLIC_SHARE_TOKEN=your_seafile_public_share_token
|
||||
WIBU_UPLOAD_DIR=uploads
|
||||
WIBU_DOWNLOAD_DIR=./download
|
||||
|
||||
# WhatsApp Server Configuration
|
||||
WA_SERVER_TOKEN=your_whatsapp_server_token
|
||||
|
||||
# Application Configuration
|
||||
# IMPORTANT: For staging/production, set this to your actual domain
|
||||
# Local development: NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||
|
||||
219
.github/workflows/build.yml
vendored
219
.github/workflows/build.yml
vendored
@@ -1,219 +0,0 @@
|
||||
name: Build And Save Log
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: "Target environment (e.g., staging, production)"
|
||||
required: true
|
||||
default: "staging"
|
||||
version:
|
||||
description: "Version to deploy"
|
||||
required: false
|
||||
default: "latest"
|
||||
|
||||
env:
|
||||
APP_NAME: desa-darmasaba-action
|
||||
WA_PHONE: "6289697338821,6289697338822"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14
|
||||
env:
|
||||
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd="pg_isready"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
|
||||
steps:
|
||||
# Checkout kode sumber
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Setup Bun
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
# Cache dependencies
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
# Step 1: Set BRANCH_NAME based on event type
|
||||
- name: Set BRANCH_NAME
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
echo "BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "BRANCH_NAME=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Step 2: Generate APP_VERSION dynamically
|
||||
- name: Set APP_VERSION
|
||||
run: echo "APP_VERSION=${{ github.sha }}---$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV
|
||||
|
||||
# Step 3: Kirim notifikasi ke API build Start
|
||||
- name: Notify start build
|
||||
run: |
|
||||
IFS=',' read -ra PHONES <<< "${{ env.WA_PHONE }}"
|
||||
for PHONE in "${PHONES[@]}"; do
|
||||
ENCODED_TEXT=$(bun -e "console.log(encodeURIComponent('Build:start\nApp:${{ env.APP_NAME }}\nBranch:${{ env.BRANCH_NAME }}\nVersion:${{ env.APP_VERSION }}'))")
|
||||
curl -X GET "https://wa.wibudev.com/code?text=$ENCODED_TEXT&nom=$PHONE"
|
||||
done
|
||||
|
||||
# Install dependencies
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
# Konfigurasi environment variable untuk PostgreSQL dan variabel tambahan
|
||||
- name: Set up environment variables
|
||||
run: |
|
||||
echo "DATABASE_URL=postgresql://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}?schema=public" >> .env
|
||||
echo "PORT=3000" >> .env
|
||||
echo "NEXT_PUBLIC_WIBU_URL=localhost:3000" >> .env
|
||||
echo "WIBU_UPLOAD_DIR=/uploads" >> .env
|
||||
|
||||
# Create log file
|
||||
- name: Create log file
|
||||
run: touch build.txt
|
||||
|
||||
# Migrasi database menggunakan Prisma
|
||||
- name: Apply Prisma schema to database
|
||||
run: bun prisma db push >> build.txt 2>&1
|
||||
|
||||
# Seed database (opsional)
|
||||
- name: Seed database
|
||||
run: |
|
||||
bun prisma db seed >> build.txt 2>&1 || echo "Seed failed or no seed data found. Continuing without seed." >> build.txt
|
||||
|
||||
# Build project
|
||||
- name: Build project
|
||||
run: bun run build >> build.txt 2>&1
|
||||
|
||||
# Ensure project directory exists
|
||||
- name: Ensure /var/www/projects/${{ env.APP_NAME }} exists
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USERNAME }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
script: |
|
||||
mkdir -p /var/www/projects/${{ env.APP_NAME }}
|
||||
|
||||
# Deploy to a new version directory
|
||||
- name: Deploy to VPS (New Version)
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USERNAME }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
source: "."
|
||||
target: "/var/www/projects/${{ env.APP_NAME }}/releases/${{ env.APP_VERSION }}"
|
||||
|
||||
# Set up environment variables
|
||||
- name: Set up environment variables
|
||||
run: |
|
||||
rm -r .env
|
||||
echo "DATABASE_URL=postgresql://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5433/${{ secrets.POSTGRES_DB }}?schema=public" >> .env
|
||||
echo "NEXT_PUBLIC_WIBU_URL=${{ env.APP_NAME }}" >> .env
|
||||
echo "WIBU_UPLOAD_DIR=/var/www/projects/${{ env.APP_NAME }}/uploads" >> .env
|
||||
|
||||
# Kirim file .env ke server
|
||||
- name: Upload .env to server
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USERNAME }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
source: ".env"
|
||||
target: "/var/www/projects/${{ env.APP_NAME }}/releases/${{ env.APP_VERSION }}/"
|
||||
|
||||
# manage deployment
|
||||
- name: manage deployment
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USERNAME }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
script: |
|
||||
|
||||
# Source ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# Find an available port
|
||||
PORT=$(curl -s -X GET https://wibu-bot.wibudev.com/api/find-port | jq -r '.[0]')
|
||||
if [ -z "$PORT" ] || ! [[ "$PORT" =~ ^[0-9]+$ ]]; then
|
||||
echo "Invalid or missing port from API."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# manage deployment
|
||||
cd /var/www/projects/${{ env.APP_NAME }}/releases/${{ env.APP_VERSION }}
|
||||
|
||||
# Create uploads directory
|
||||
mkdir -p /var/www/projects/${{ env.APP_NAME }}/uploads
|
||||
|
||||
# Install dependencies
|
||||
bun install --production
|
||||
|
||||
# Apply database schema
|
||||
if ! bun prisma db push; then
|
||||
echo "Database migration failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Seed database (optional)
|
||||
bun prisma db seed || echo "tidak membutuhkan seed"
|
||||
|
||||
# Restart the application
|
||||
pm2 reload ${{ env.APP_NAME }} || pm2 start "bun run start --port $PORT" --name "${{ env.APP_NAME }}-$PORT" --namespace "${{ env.APP_NAME }}"
|
||||
|
||||
# Step 4: Set BUILD_STATUS based on success or failure
|
||||
- name: Set BUILD_STATUS
|
||||
if: success()
|
||||
run: echo "BUILD_STATUS=success" >> $GITHUB_ENV
|
||||
|
||||
- name: Set BUILD_STATUS on failure
|
||||
if: failure()
|
||||
run: echo "BUILD_STATUS=failed" >> $GITHUB_ENV
|
||||
|
||||
# Update status log
|
||||
- name: Update status log
|
||||
if: always()
|
||||
run: |
|
||||
echo "=====================" >> build.txt
|
||||
echo "BUILD_STATUS=${{ env.BUILD_STATUS }}" >> build.txt
|
||||
echo "APP_NAME=${{ env.APP_NAME }}" >> build.txt
|
||||
echo "APP_VERSION=${{ env.APP_VERSION }}" >> build.txt
|
||||
echo "=====================" >> build.txt
|
||||
|
||||
# Upload log to 0x0.st
|
||||
- name: Upload log to 0x0.st
|
||||
id: upload_log
|
||||
if: always()
|
||||
run: |
|
||||
LOG_URL=$(curl -F "file=@build.txt" https://wibu-bot.wibudev.com/api/file )
|
||||
echo "LOG_URL=$LOG_URL" >> $GITHUB_ENV
|
||||
|
||||
# Kirim notifikasi ke API
|
||||
- name: Notify build success via API
|
||||
if: always()
|
||||
run: |
|
||||
IFS=',' read -ra PHONES <<< "${{ env.WA_PHONE }}"
|
||||
for PHONE in "${PHONES[@]}"; do
|
||||
ENCODED_TEXT=$(bun -e "console.log(encodeURIComponent('Build:${{ env.BUILD_STATUS }}\nApp:${{ env.APP_NAME }}\nBranch:${{ env.BRANCH_NAME }}\nVersion:${{ env.APP_VERSION }}\nLog:${{ env.LOG_URL }}'))")
|
||||
curl -X GET "https://wa.wibudev.com/code?text=$ENCODED_TEXT&nom=$PHONE"
|
||||
done
|
||||
56
.github/workflows/docker-publish.yml
vendored
Normal file
56
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Publish Docker to GHCR
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
jobs:
|
||||
publish:
|
||||
name: Build & Push to GHCR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Free disk space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||||
sudo docker image prune --all --force
|
||||
df -h
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Extract tag name
|
||||
id: meta
|
||||
run: echo "tag=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
106
.github/workflows/publish.yml
vendored
Normal file
106
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
name: Publish Docker to GHCR
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stack_env:
|
||||
description: "stack env"
|
||||
required: true
|
||||
type: choice
|
||||
default: "dev"
|
||||
options:
|
||||
- dev
|
||||
- prod
|
||||
- stg
|
||||
tag:
|
||||
description: "Image tag (e.g. 1.0.0)"
|
||||
required: true
|
||||
default: "1.0.0"
|
||||
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Build & Push to GHCR ${{ github.repository }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ vars.PORTAINER_ENV || 'portainer' }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Free disk space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||||
sudo docker image prune --all --force
|
||||
df -h
|
||||
|
||||
- name: Checkout branch ${{ github.event.inputs.stack_env }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.stack_env }}
|
||||
|
||||
- name: Checkout scripts from main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
path: .ci
|
||||
sparse-checkout: .github/workflows/script
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}
|
||||
type=raw,value=${{ github.event.inputs.stack_env }}-latest
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
no-cache: true
|
||||
|
||||
- name: Notify success
|
||||
if: success()
|
||||
run: bash ./.ci/.github/workflows/script/notify.sh
|
||||
env:
|
||||
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
NOTIFY_STATUS: success
|
||||
NOTIFY_WORKFLOW: "Publish Docker"
|
||||
NOTIFY_DETAIL: "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}"
|
||||
|
||||
- name: Notify failure
|
||||
if: failure()
|
||||
run: bash ./.ci/.github/workflows/script/notify.sh
|
||||
env:
|
||||
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
NOTIFY_STATUS: failure
|
||||
NOTIFY_WORKFLOW: "Publish Docker"
|
||||
NOTIFY_DETAIL: "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}"
|
||||
60
.github/workflows/re-pull.yml
vendored
Normal file
60
.github/workflows/re-pull.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Re-Pull Docker
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stack_name:
|
||||
description: "stack name"
|
||||
required: true
|
||||
type: string
|
||||
stack_env:
|
||||
description: "stack env"
|
||||
required: true
|
||||
type: choice
|
||||
default: "dev"
|
||||
options:
|
||||
- dev
|
||||
- stg
|
||||
- prod
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Re-Pull Docker ${{ github.event.inputs.stack_name }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ vars.PORTAINER_ENV || 'portainer' }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout scripts from main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
sparse-checkout: .github/workflows/script
|
||||
|
||||
- name: Deploy ke Portainer
|
||||
run: bash ./.github/workflows/script/re-pull.sh
|
||||
env:
|
||||
PORTAINER_USERNAME: ${{ secrets.PORTAINER_USERNAME }}
|
||||
PORTAINER_PASSWORD: ${{ secrets.PORTAINER_PASSWORD }}
|
||||
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
|
||||
STACK_NAME: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}
|
||||
|
||||
- name: Notify success
|
||||
if: success()
|
||||
run: bash ./.github/workflows/script/notify.sh
|
||||
env:
|
||||
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
NOTIFY_STATUS: success
|
||||
NOTIFY_WORKFLOW: "Re-Pull Docker"
|
||||
NOTIFY_DETAIL: "Stack: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}"
|
||||
|
||||
- name: Notify failure
|
||||
if: failure()
|
||||
run: bash ./.github/workflows/script/notify.sh
|
||||
env:
|
||||
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
NOTIFY_STATUS: failure
|
||||
NOTIFY_WORKFLOW: "Re-Pull Docker"
|
||||
NOTIFY_DETAIL: "Stack: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}"
|
||||
26
.github/workflows/script/notify.sh
vendored
Normal file
26
.github/workflows/script/notify.sh
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
: "${TELEGRAM_TOKEN:?TELEGRAM_TOKEN tidak di-set}"
|
||||
: "${TELEGRAM_CHAT_ID:?TELEGRAM_CHAT_ID tidak di-set}"
|
||||
: "${NOTIFY_STATUS:?NOTIFY_STATUS tidak di-set}"
|
||||
: "${NOTIFY_WORKFLOW:?NOTIFY_WORKFLOW tidak di-set}"
|
||||
|
||||
if [ "$NOTIFY_STATUS" = "success" ]; then
|
||||
ICON="✅"
|
||||
TEXT="${ICON} *${NOTIFY_WORKFLOW}* berhasil!"
|
||||
else
|
||||
ICON="❌"
|
||||
TEXT="${ICON} *${NOTIFY_WORKFLOW}* gagal!"
|
||||
fi
|
||||
|
||||
if [ -n "$NOTIFY_DETAIL" ]; then
|
||||
TEXT="${TEXT}
|
||||
${NOTIFY_DETAIL}"
|
||||
fi
|
||||
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n \
|
||||
--arg chat_id "$TELEGRAM_CHAT_ID" \
|
||||
--arg text "$TEXT" \
|
||||
'{chat_id: $chat_id, text: $text, parse_mode: "Markdown"}')"
|
||||
120
.github/workflows/script/re-pull.sh
vendored
Normal file
120
.github/workflows/script/re-pull.sh
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
|
||||
: "${PORTAINER_URL:?PORTAINER_URL tidak di-set}"
|
||||
: "${PORTAINER_USERNAME:?PORTAINER_USERNAME tidak di-set}"
|
||||
: "${PORTAINER_PASSWORD:?PORTAINER_PASSWORD tidak di-set}"
|
||||
: "${STACK_NAME:?STACK_NAME tidak di-set}"
|
||||
|
||||
# Timeout total: MAX_RETRY * SLEEP_INTERVAL detik
|
||||
MAX_RETRY=60 # 60 × 10s = 10 menit
|
||||
SLEEP_INTERVAL=10
|
||||
|
||||
echo "🔐 Autentikasi ke Portainer..."
|
||||
TOKEN=$(curl -s -X POST "https://${PORTAINER_URL}/api/auth" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\": \"${PORTAINER_USERNAME}\", \"password\": \"${PORTAINER_PASSWORD}\"}" \
|
||||
| jq -r .jwt)
|
||||
|
||||
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
|
||||
echo "❌ Autentikasi gagal! Cek PORTAINER_URL, USERNAME, dan PASSWORD."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔍 Mencari stack: $STACK_NAME..."
|
||||
STACK=$(curl -s -X GET "https://${PORTAINER_URL}/api/stacks" \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
| jq ".[] | select(.Name == \"$STACK_NAME\")")
|
||||
|
||||
if [ -z "$STACK" ]; then
|
||||
echo "❌ Stack '$STACK_NAME' tidak ditemukan di Portainer!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STACK_ID=$(echo "$STACK" | jq -r .Id)
|
||||
ENDPOINT_ID=$(echo "$STACK" | jq -r .EndpointId)
|
||||
ENV=$(echo "$STACK" | jq '.Env // []')
|
||||
|
||||
# ── Catat container ID lama sebelum redeploy ──────────────────────────────────
|
||||
echo "📸 Mencatat container aktif sebelum redeploy..."
|
||||
CONTAINERS_BEFORE=$(curl -s -X GET \
|
||||
"https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=true&filters=%7B%22label%22%3A%5B%22com.docker.compose.project%3D${STACK_NAME}%22%5D%7D" \
|
||||
-H "Authorization: Bearer ${TOKEN}")
|
||||
|
||||
OLD_IDS=$(echo "$CONTAINERS_BEFORE" | jq -r '[.[] | .Id] | join(",")')
|
||||
echo " Container lama: $(echo "$CONTAINERS_BEFORE" | jq -r '[.[] | .Names[0]] | join(", ")')"
|
||||
|
||||
# ── Ambil compose file lalu trigger redeploy ─────────────────────────────────
|
||||
echo "📄 Mengambil compose file..."
|
||||
STACK_FILE=$(curl -s -X GET "https://${PORTAINER_URL}/api/stacks/${STACK_ID}/file" \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
| jq -r .StackFileContent)
|
||||
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg content "$STACK_FILE" \
|
||||
--argjson env "$ENV" \
|
||||
'{stackFileContent: $content, env: $env, pullImage: true}')
|
||||
|
||||
echo "🚀 Triggering redeploy $STACK_NAME (pull latest image)..."
|
||||
HTTP_STATUS=$(curl -s -o /tmp/portainer_response.json -w "%{http_code}" \
|
||||
-X PUT "https://${PORTAINER_URL}/api/stacks/${STACK_ID}?endpointId=${ENDPOINT_ID}" \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD")
|
||||
|
||||
if [ "$HTTP_STATUS" != "200" ]; then
|
||||
echo "❌ Redeploy gagal! HTTP Status: $HTTP_STATUS"
|
||||
cat /tmp/portainer_response.json | jq .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "⏳ Menunggu image selesai di-pull dan container baru running..."
|
||||
echo " (Timeout: $((MAX_RETRY * SLEEP_INTERVAL)) detik)"
|
||||
|
||||
COUNT=0
|
||||
while [ $COUNT -lt $MAX_RETRY ]; do
|
||||
sleep $SLEEP_INTERVAL
|
||||
COUNT=$((COUNT + 1))
|
||||
|
||||
CONTAINERS=$(curl -s -X GET \
|
||||
"https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=true&filters=%7B%22label%22%3A%5B%22com.docker.compose.project%3D${STACK_NAME}%22%5D%7D" \
|
||||
-H "Authorization: Bearer ${TOKEN}")
|
||||
|
||||
# Container baru = ID tidak ada di daftar container lama
|
||||
NEW_RUNNING=$(echo "$CONTAINERS" | jq \
|
||||
--arg old "$OLD_IDS" \
|
||||
'[.[] | select(.State == "running" and ((.Id) as $id | ($old | split(",") | index($id)) == null))] | length')
|
||||
|
||||
FAILED=$(echo "$CONTAINERS" | jq \
|
||||
'[.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not) and (.Names[0] | test("seed") | not))] | length')
|
||||
|
||||
echo "🔄 [$((COUNT * SLEEP_INTERVAL))s / $((MAX_RETRY * SLEEP_INTERVAL))s] Container baru running: ${NEW_RUNNING} | Gagal: ${FAILED}"
|
||||
echo "$CONTAINERS" | jq -r '.[] | " → \(.Names[0]) | \(.State) | \(.Status) | id: \(.Id[:12])"'
|
||||
|
||||
if [ "$FAILED" -gt "0" ]; then
|
||||
echo ""
|
||||
echo "❌ Ada container yang crash!"
|
||||
echo "$CONTAINERS" | jq -r '.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not) and (.Names[0] | test("seed") | not)) | " → \(.Names[0]) | \(.Status)"'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$NEW_RUNNING" -gt "0" ]; then
|
||||
# Cleanup dangling images setelah redeploy sukses
|
||||
echo "🧹 Membersihkan dangling images..."
|
||||
curl -s -X POST "https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/images/prune" \
|
||||
-H "Authorization: Bearer ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"filters":{"dangling":["true"]}}' | jq -r '" Reclaimed: \(.SpaceReclaimed // 0 | . / 1073741824 | tostring | .[0:5]) GB"'
|
||||
|
||||
echo "✅ Cleanup selesai!"
|
||||
echo ""
|
||||
echo "✅ Stack $STACK_NAME berhasil di-redeploy dengan image baru dan running!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "❌ Timeout $((MAX_RETRY * SLEEP_INTERVAL))s! Container baru tidak kunjung running."
|
||||
echo " Kemungkinan image masih dalam proses pull atau ada error di server."
|
||||
exit 1
|
||||
55
.github/workflows/test.yml
vendored
55
.github/workflows/test.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: test workflows
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: "Target environment (e.g., staging, production)"
|
||||
required: true
|
||||
default: "staging"
|
||||
version:
|
||||
description: "Version to deploy"
|
||||
required: false
|
||||
default: "latest"
|
||||
|
||||
env:
|
||||
APP_NAME: desa-darmasaba-action
|
||||
WA_PHONE: "6289697338821,6289697338822"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout kode sumber
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Setup Bun
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
# Create log file
|
||||
- name: Create log file
|
||||
run: touch build.txt
|
||||
|
||||
# Step 1: Set BRANCH_NAME based on event type
|
||||
- name: Set BRANCH_NAME
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
echo "BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "BRANCH_NAME=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Step 2: Generate APP_VERSION dynamically
|
||||
- name: Set APP_VERSION
|
||||
run: echo "APP_VERSION=${{ github.sha }}---$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV
|
||||
|
||||
# Step 3: Kirim notifikasi ke API build Start
|
||||
- name: Notify start build
|
||||
run: |
|
||||
IFS=',' read -ra PHONES <<< "${{ env.WA_PHONE }}"
|
||||
for PHONE in "${PHONES[@]}"; do
|
||||
ENCODED_TEXT=$(bun -e "console.log(encodeURIComponent('Build:start\nApp:${{ env.APP_NAME }}\nenv:${{ inputs.environment }}\nBranch:${{ env.BRANCH_NAME }}\nVersion:${{ env.APP_VERSION }}'))")
|
||||
curl -X GET "https://wa.wibudev.com/code?text=$ENCODED_TEXT&nom=$PHONE"
|
||||
done
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -55,7 +55,6 @@ next-env.d.ts
|
||||
# cache
|
||||
/cache
|
||||
|
||||
.github/
|
||||
|
||||
*.tar.gz
|
||||
|
||||
|
||||
@@ -57,4 +57,4 @@ COPY --from=build /app/prisma ./prisma
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the application
|
||||
CMD ["bun", "start"]
|
||||
CMD ["bun", "start"]
|
||||
32
src/app/api/auth/_lib/sendCodeOtp.ts
Normal file
32
src/app/api/auth/_lib/sendCodeOtp.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// app/api/auth/_lib/sendCodeOtp.ts
|
||||
|
||||
const sendCodeOtp = async ({
|
||||
nomor,
|
||||
codeOtp,
|
||||
newMessage,
|
||||
}: {
|
||||
nomor: string;
|
||||
codeOtp?: string | number;
|
||||
newMessage?: string;
|
||||
}) => {
|
||||
const msg =
|
||||
newMessage ||
|
||||
`Website Desa Darmasaba - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun Admin lainnya.\n\n>> Kode OTP anda: ${codeOtp}.`;
|
||||
const enCode = msg;
|
||||
|
||||
const res = await fetch(`https://otp.wibudev.com/api/wa/send-text`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
number: nomor,
|
||||
text: enCode,
|
||||
}),
|
||||
});
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export { sendCodeOtp };
|
||||
@@ -2,6 +2,7 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { NextResponse } from "next/server";
|
||||
import { randomOTP } from "../_lib/randomOTP";
|
||||
import { sendCodeOtp } from "../_lib/sendCodeOtp";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
@@ -35,18 +36,17 @@ export async function POST(req: Request) {
|
||||
|
||||
console.log(`🔑 DEBUG OTP [${nomor}]: ${codeOtp}`);
|
||||
|
||||
const waMessage = `Website Desa Darmasaba - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun Admin lainnya.\n\n>> Kode OTP anda: ${codeOtp}.`;
|
||||
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=${encodeURIComponent(waMessage)}`;
|
||||
|
||||
console.log("🔍 Debug WA URL:", waUrl);
|
||||
|
||||
try {
|
||||
const res = await fetch(waUrl);
|
||||
if (!res.ok) {
|
||||
console.error(`⚠️ WA Service HTTP Error: ${res.status} ${res.statusText}. Continuing since OTP is logged.`);
|
||||
console.log(`💡 Use this OTP to login: ${codeOtp}`);
|
||||
const waResponse = await sendCodeOtp({
|
||||
nomor,
|
||||
codeOtp,
|
||||
});
|
||||
|
||||
if (!waResponse.ok) {
|
||||
console.error(`⚠️ WA Service HTTP Error: ${waResponse.status} ${waResponse.statusText}. Continuing since OTP is logged.`);
|
||||
console.log(`💡 Use this OTP to login: ${codeOtp}`);
|
||||
} else {
|
||||
const sendWa = await res.json();
|
||||
const sendWa = await waResponse.json();
|
||||
console.log("📱 WA Response:", sendWa);
|
||||
if (sendWa.status !== "success") {
|
||||
console.error("⚠️ WA Service Logic Error:", sendWa);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Paper, Title, Progress, Stack, Text, Group, Box } from '@mantine/core'
|
||||
import { Paper, Title, Progress, Stack, Text, Group, Box, type MantineColor } from '@mantine/core'
|
||||
import { IconArrowUpRight, IconArrowDownRight } from '@tabler/icons-react'
|
||||
import { APBDes, APBDesItem } from '../types/apbdes'
|
||||
|
||||
@@ -107,7 +106,7 @@ function Summary({ title, data, icon }: SummaryProps) {
|
||||
|
||||
<Text
|
||||
fz="xs"
|
||||
c={statusMessage.color as any}
|
||||
c={statusMessage.color as MantineColor}
|
||||
fw={600}
|
||||
style={{
|
||||
backgroundColor: `var(--mantine-color-${statusMessage.color}-0)`,
|
||||
|
||||
Reference in New Issue
Block a user