Compare commits
47
Commits
e6033c8b12
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb19385ab4 | ||
|
|
a1c0ef25ef | ||
|
|
609da6b6bc | ||
|
|
402499d109 | ||
|
|
1389d5faa3 | ||
|
|
59e6c5c6a1 | ||
|
|
4f8a975337 | ||
|
|
c251867f05 | ||
|
|
b06cddca4a | ||
|
|
19aface599 | ||
|
|
528a109d53 | ||
|
|
97b635e8c6 | ||
|
|
99e438d9c5 | ||
|
|
8e850f05c4 | ||
|
|
6c2a4fc9d2 | ||
|
|
3e92f68d78 | ||
|
|
2050129062 | ||
|
|
a6148753ca | ||
|
|
d02989f1a7 | ||
|
|
6e317eb05a | ||
|
|
4e114edb89 | ||
|
|
49123c08b3 | ||
|
|
539b5b8320 | ||
|
|
39df52aab7 | ||
|
|
b326911478 | ||
|
|
8242325ffd | ||
|
|
c075cf282b | ||
|
|
93936efd40 | ||
|
|
5f66cfc12b | ||
|
|
cc8c3f1bf3 | ||
|
|
0c36007da4 | ||
|
|
b7d02647d9 | ||
|
|
ade2798ded | ||
|
|
ef14ca431f | ||
|
|
f681e624b3 | ||
|
|
fdbdcbc1ec | ||
|
|
b7b60e9125 | ||
|
|
722709f6fb | ||
|
|
d20643fbbd | ||
|
|
7da1727603 | ||
|
|
9e9fd229d0 | ||
|
|
40095f75f4 | ||
|
|
3781a8882d | ||
|
|
da2f4a2f2c | ||
|
|
ad22a39a9e | ||
|
|
2f969972a1 | ||
|
|
620f06fbe0 |
@@ -25,5 +25,49 @@
|
|||||||
"filePattern": ".claude/skills/deploy-workflow.md",
|
"filePattern": ".claude/skills/deploy-workflow.md",
|
||||||
"description": "CI/CD pipeline, Docker patterns, deployment guide"
|
"description": "CI/CD pipeline, Docker patterns, deployment guide"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "serena-hooks remind --client=claude-code"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": "mcp__serena__*",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "serena-hooks auto-approve --client=claude-code"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "serena-hooks activate --client=claude-code"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SessionEnd": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "serena-hooks cleanup --client=claude-code"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
name: Deploy Docker to VPS
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ['Build and Push Docker Images']
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'infra/**'
|
|
||||||
- '.github/workflows/deploy-docker.yml'
|
|
||||||
- '.github/workflows/docker-build-push.yml'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# Prevent multiple deployments from running simultaneously
|
|
||||||
concurrency:
|
|
||||||
group: deploy-vps
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push' || github.event.workflow_run.conclusion == 'success'
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
submodules: false
|
|
||||||
|
|
||||||
- name: Deploy to VPS
|
|
||||||
env:
|
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
||||||
VPS_HOST: ${{ secrets.VPS_HOST }}
|
|
||||||
VPS_USER: ${{ secrets.VPS_USER }}
|
|
||||||
VPS_TARGET_DIR: ${{ secrets.VPS_TARGET_DIR }}
|
|
||||||
ENV_FILE_PRODUCTION: ${{ secrets.ENV_FILE_PRODUCTION }}
|
|
||||||
GHCR_USERNAME: ${{ github.actor }}
|
|
||||||
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "Deploy event: ${{ github.event_name }}"
|
|
||||||
echo "Deploy ref: ${{ github.ref }}"
|
|
||||||
echo "Deploy sha: ${{ github.sha }}"
|
|
||||||
|
|
||||||
if [ -z "${SSH_PRIVATE_KEY:-}" ] || [ -z "${VPS_HOST:-}" ] || [ -z "${VPS_USER:-}" ] || [ -z "${VPS_TARGET_DIR:-}" ]; then
|
|
||||||
echo "❌ Deployment secrets are not fully configured. Please set SSH_PRIVATE_KEY, VPS_HOST, VPS_USER, and VPS_TARGET_DIR."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
|
||||||
chmod 600 ~/.ssh/id_rsa
|
|
||||||
ssh-keyscan -H -t ed25519,rsa "$VPS_HOST" >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
# Use SSH multiplexing for faster subsequent commands
|
|
||||||
SSH_OPTS=(-o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=600 -o StrictHostKeyChecking=yes)
|
|
||||||
|
|
||||||
ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" "mkdir -p $VPS_TARGET_DIR && mkdir -p $VPS_TARGET_DIR/infra/compose"
|
|
||||||
echo "$ENV_FILE_PRODUCTION" > .env.prod
|
|
||||||
scp "${SSH_OPTS[@]}" .env.prod "$VPS_USER@$VPS_HOST:$VPS_TARGET_DIR/.env"
|
|
||||||
|
|
||||||
echo "🔐 Logging in to GitHub Container Registry..."
|
|
||||||
printf '%s' "$GHCR_TOKEN" | ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" "docker login ghcr.io -u '$GHCR_USERNAME' --password-stdin"
|
|
||||||
|
|
||||||
ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" "export VPS_TARGET_DIR=$VPS_TARGET_DIR; bash -s" <<'EOF'
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$VPS_TARGET_DIR"
|
|
||||||
|
|
||||||
# Ensure shared network exists
|
|
||||||
docker network inspect app-shared-net >/dev/null 2>&1 || docker network create app-shared-net
|
|
||||||
|
|
||||||
echo "🔄 Synchronizing repository..."
|
|
||||||
if [ ! -d ".git" ]; then
|
|
||||||
echo "Initializing git repository..."
|
|
||||||
git init
|
|
||||||
git remote add origin https://github.com/asepharyana/asepharyana-hub.git
|
|
||||||
fi
|
|
||||||
git fetch origin main --depth=1 || true
|
|
||||||
|
|
||||||
# Detect changed files before resetting
|
|
||||||
ALL_COMPOSE_FILES="infra/compose/traefik.yml infra/compose/shared.yml infra/compose/scraper.yml infra/compose/hub.yml infra/compose/tools.yml infra/compose/llm-api.yml infra/compose/nats.yml infra/compose/dapr.yml infra/compose/observability.yml"
|
|
||||||
TRAEFIK_DYNAMIC_DIR="infra/traefik/dynamic"
|
|
||||||
|
|
||||||
if git rev-parse HEAD >/dev/null 2>&1; then
|
|
||||||
BEFORE_REV=$(git rev-parse HEAD)
|
|
||||||
git reset --hard FETCH_HEAD
|
|
||||||
AFTER_REV=$(git rev-parse HEAD)
|
|
||||||
|
|
||||||
if [ "$BEFORE_REV" = "$AFTER_REV" ]; then
|
|
||||||
echo "ℹ️ No new commits detected. Using full file list for safety."
|
|
||||||
TARGET_COMPOSE=""
|
|
||||||
else
|
|
||||||
CHANGED=$(git diff --name-only "$BEFORE_REV" "$AFTER_REV" || true)
|
|
||||||
echo "📄 Changed files:"
|
|
||||||
echo "$CHANGED"
|
|
||||||
|
|
||||||
# Detect compose stack changes
|
|
||||||
CHANGED_COMPOSE=$(echo "$CHANGED" | grep '^infra/compose/.*\.yml$' || true)
|
|
||||||
TARGET_COMPOSE=""
|
|
||||||
for f in $CHANGED_COMPOSE; do
|
|
||||||
case " $ALL_COMPOSE_FILES " in
|
|
||||||
*" $f "*) TARGET_COMPOSE="$TARGET_COMPOSE $f" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
TARGET_COMPOSE=$(printf '%s' "$TARGET_COMPOSE" | xargs || true)
|
|
||||||
if [ -n "$TARGET_COMPOSE" ]; then
|
|
||||||
echo "🎯 Detected compose stack changes in: $TARGET_COMPOSE"
|
|
||||||
else
|
|
||||||
echo "ℹ️ No stack compose files changed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Detect Traefik dynamic config changes
|
|
||||||
CHANGED_TRAEFIK=$(echo "$CHANGED" | grep "^$TRAEFIK_DYNAMIC_DIR/" || true)
|
|
||||||
if [ -n "$CHANGED_TRAEFIK" ]; then
|
|
||||||
echo "🎯 Detected Traefik dynamic config changes:"
|
|
||||||
echo "$CHANGED_TRAEFIK"
|
|
||||||
RELOAD_TRAEFIK="true"
|
|
||||||
else
|
|
||||||
echo "ℹ️ No Traefik dynamic config changes."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Detect infra file changes (Dockerfiles, config, traefik static)
|
|
||||||
CHANGED_INFRA=$(echo "$CHANGED" | grep '^infra/' | grep -v '^infra/compose/' || true)
|
|
||||||
if [ -n "$CHANGED_INFRA" ]; then
|
|
||||||
echo "📦 Detected other infra file changes:"
|
|
||||||
echo "$CHANGED_INFRA"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
git reset --hard FETCH_HEAD
|
|
||||||
TARGET_COMPOSE=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if command -v "docker" >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
|
||||||
COMPOSE_CMD="docker compose"
|
|
||||||
elif command -v docker-compose >/dev/null 2>&1; then
|
|
||||||
COMPOSE_CMD="docker-compose"
|
|
||||||
else
|
|
||||||
echo "❌ docker compose is not installed on the remote host."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Always include ALL compose files for dependency resolution
|
|
||||||
COMPOSE_ARGS=""
|
|
||||||
for f in $ALL_COMPOSE_FILES; do
|
|
||||||
if [ -f "$f" ]; then
|
|
||||||
COMPOSE_ARGS="$COMPOSE_ARGS -f $f"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -n "$TARGET_COMPOSE" ]; then
|
|
||||||
# Extract service names from target compose file(s) for selective up
|
|
||||||
TARGET_SERVICES=""
|
|
||||||
for f in $TARGET_COMPOSE; do
|
|
||||||
if [ -f "$f" ]; then
|
|
||||||
svcs=$($COMPOSE_CMD -f "$f" config --services 2>/dev/null | tr '\n' ' ' | xargs)
|
|
||||||
TARGET_SERVICES="$TARGET_SERVICES $svcs"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
TARGET_SERVICES=$(echo "$TARGET_SERVICES" | xargs) # trim whitespace
|
|
||||||
echo "🎯 Selective update for services: $TARGET_SERVICES"
|
|
||||||
else
|
|
||||||
echo "🚀 Performing full deployment of all services..."
|
|
||||||
TARGET_SERVICES=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "📥 Pulling images for target services..."
|
|
||||||
export DOCKER_CLI_EXPERIMENTAL=enabled
|
|
||||||
PULL_SUCCESS=false
|
|
||||||
# Retry pull up to 3 times to handle transient Docker attestation lease errors
|
|
||||||
for attempt in 1 2 3; do
|
|
||||||
echo "Pull attempt $attempt/3..."
|
|
||||||
if $COMPOSE_CMD $COMPOSE_ARGS --env-file .env pull $TARGET_SERVICES; then
|
|
||||||
echo "✅ Pull succeeded on attempt $attempt"
|
|
||||||
PULL_SUCCESS=true
|
|
||||||
break
|
|
||||||
else
|
|
||||||
echo "⚠️ Pull attempt $attempt failed. Retrying in 5s..."
|
|
||||||
sleep 5
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$PULL_SUCCESS" != "true" ]; then
|
|
||||||
echo "❌ Failed to pull images after 3 attempts."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🧹 Clearing Git locks..."
|
|
||||||
rm -f .git/shallow.lock || true
|
|
||||||
|
|
||||||
echo "🧹 Removing stale target containers by container_name..."
|
|
||||||
# Extract all explicitly defined container_names from compose files and remove them to prevent conflicts
|
|
||||||
if [ -n "$TARGET_COMPOSE" ]; then
|
|
||||||
for f in $TARGET_COMPOSE; do
|
|
||||||
if [ -f "$f" ]; then
|
|
||||||
grep "container_name:" "$f" | awk '{print $2}' | while read -r cname; do
|
|
||||||
docker rm -f "$cname" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
for f in $ALL_COMPOSE_FILES; do
|
|
||||||
if [ -f "$f" ]; then
|
|
||||||
grep "container_name:" "$f" | awk '{print $2}' | while read -r cname; do
|
|
||||||
docker rm -f "$cname" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🆙 Starting services..."
|
|
||||||
echo "🔍 Debug: Current docker containers:"
|
|
||||||
docker ps -a
|
|
||||||
if [ -n "$TARGET_SERVICES" ]; then
|
|
||||||
$COMPOSE_CMD $COMPOSE_ARGS --env-file .env up -d $TARGET_SERVICES
|
|
||||||
else
|
|
||||||
$COMPOSE_CMD $COMPOSE_ARGS --env-file .env up -d --remove-orphans
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Traefik reload ──
|
|
||||||
if [ "${RELOAD_TRAEFIK:-false}" = "true" ]; then
|
|
||||||
echo "🔄 Traefik dynamic config changed — reloading Traefik..."
|
|
||||||
# Traefik watches the dynamic config dir (providers.file.watch=true),
|
|
||||||
# but send SIGHUP as insurance
|
|
||||||
docker kill --signal HUP traefik 2>/dev/null || docker exec traefik kill -HUP 1 2>/dev/null || true
|
|
||||||
echo "✅ Traefik reload signal sent"
|
|
||||||
fi
|
|
||||||
EOF
|
|
||||||
|
|
||||||
@@ -1,338 +0,0 @@
|
|||||||
name: Build and Push Docker Images
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'apps/scraper/**'
|
|
||||||
- 'apps/hub/**'
|
|
||||||
- 'apps/tools/**'
|
|
||||||
- 'apps/llm-api/**'
|
|
||||||
- '.github/workflows/docker-build-push.yml'
|
|
||||||
- 'infra/**'
|
|
||||||
- '!infra/compose/**'
|
|
||||||
repository_dispatch:
|
|
||||||
types: [submodule-updated]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME_PREFIX: asepharyana/asepharyana-hub
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
# Phase 1: Detect which services have changed
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
changes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
outputs:
|
|
||||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
|
||||||
scraper-api: ${{ steps.filter.outputs['scraper-api'] == 'true' || steps.dispatch.outputs['scraper-api'] == 'true' || github.event_name == 'workflow_dispatch' }}
|
|
||||||
hub: ${{ steps.filter.outputs['hub'] == 'true' || steps.dispatch.outputs['hub'] == 'true' || github.event_name == 'workflow_dispatch' }}
|
|
||||||
tools: ${{ steps.filter.outputs['tools'] == 'true' || steps.dispatch.outputs['tools'] == 'true' || github.event_name == 'workflow_dispatch' }}
|
|
||||||
llm-api: ${{ steps.filter.outputs['llm-api'] == 'true' || steps.dispatch.outputs['llm-api'] == 'true' || github.event_name == 'workflow_dispatch' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
submodules: false
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Detect changed services
|
|
||||||
id: filter
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
env:
|
|
||||||
BEFORE: ${{ github.event.before }}
|
|
||||||
AFTER: ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if [ -z "${BEFORE:-}" ] || [[ "$BEFORE" =~ ^0+$ ]]; then
|
|
||||||
CHANGED_FILES=$(git ls-files)
|
|
||||||
else
|
|
||||||
git fetch --no-tags --depth=2 origin "$BEFORE" || true
|
|
||||||
CHANGED_FILES=$(git diff --name-only "$BEFORE" "$AFTER")
|
|
||||||
fi
|
|
||||||
|
|
||||||
changed() {
|
|
||||||
printf '%s\n' "$CHANGED_FILES" | grep -Eq "$1" && echo true || echo false
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "scraper-api=$(changed '^(apps/scraper(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/scraper\.Dockerfile$)')" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "hub=$(changed '^(apps/hub(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/hub\.Dockerfile$)')" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "tools=$(changed '^(apps/tools(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/tools\.Dockerfile$)')" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "llm-api=$(changed '^(apps/llm-api(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/llm-api\.Dockerfile$)')" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Parse repository_dispatch payload
|
|
||||||
id: dispatch
|
|
||||||
if: github.event_name == 'repository_dispatch'
|
|
||||||
env:
|
|
||||||
SERVICE: ${{ github.event.client_payload.service }}
|
|
||||||
SHA: ${{ github.event.client_payload.sha }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ -z "${SERVICE:-}" ]; then
|
|
||||||
echo "::error::repository_dispatch payload missing service"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${SHA:-}" ]; then
|
|
||||||
echo "::error::repository_dispatch payload missing sha"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$SERVICE" in
|
|
||||||
scraper-api|hub|tools|llm-api) ;;
|
|
||||||
*)
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if ! [[ "$SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then echo "::error::Invalid sha '$SHA'. Expected 40 hex characters"; fi
|
|
||||||
|
|
||||||
SERVICES=(scraper-api hub tools llm-api)
|
|
||||||
for svc in "${SERVICES[@]}"; do
|
|
||||||
if [ "$SERVICE" = "$svc" ]; then
|
|
||||||
echo "${svc}=true" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "${svc}=false" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Set matrix
|
|
||||||
id: set-matrix
|
|
||||||
run: |
|
|
||||||
SERVICES=()
|
|
||||||
add_service() {
|
|
||||||
SERVICES+=("{\"id\":\"$1\",\"target\":\"$2\",\"path\":\"$3\"}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "${{ steps.filter.outputs['scraper-api'] == 'true' || steps.dispatch.outputs['scraper-api'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "scraper-api" "docker-scraper" "apps/scraper"; fi
|
|
||||||
if [ "${{ steps.filter.outputs['hub'] == 'true' || steps.dispatch.outputs['hub'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "hub" "docker-hub" "apps/hub"; fi
|
|
||||||
if [ "${{ steps.filter.outputs['tools'] == 'true' || steps.dispatch.outputs['tools'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "tools" "docker-tools" "apps/tools"; fi
|
|
||||||
if [ "${{ steps.filter.outputs['llm-api'] == 'true' || steps.dispatch.outputs['llm-api'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "llm-api" "docker-llm-api" "apps/llm-api"; fi
|
|
||||||
|
|
||||||
JSON_ARRAY="[$(IFS=,; echo "${SERVICES[*]}")]"
|
|
||||||
echo "matrix=$JSON_ARRAY" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
wait-submodule-ref:
|
|
||||||
needs: [changes]
|
|
||||||
if: github.event_name == 'repository_dispatch'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
|
||||||
- name: Wait for submodule ref
|
|
||||||
env:
|
|
||||||
SERVICE: ${{ github.event.client_payload.service }}
|
|
||||||
SHA: ${{ github.event.client_payload.sha }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
case "$SERVICE" in
|
|
||||||
"scraper-api") REPO="https://github.com/asepharyana/asepharyana-hub-scraper.git" ;;
|
|
||||||
"hub") REPO="https://github.com/asepharyana/asepharyana-hub-hub.git" ;;
|
|
||||||
"tools") echo "tools is built from monorepo, no submodule wait needed"; exit 0 ;;
|
|
||||||
"llm-api") REPO="https://github.com/asepharyana/asepharyana-hub-llm-api.git" ;;
|
|
||||||
*)
|
|
||||||
echo "::error::Unsupported service '$SERVICE'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "Waiting for $SERVICE commit $SHA in $REPO"
|
|
||||||
TMPDIR=$(mktemp -d)
|
|
||||||
git init "$TMPDIR/probe" >/dev/null
|
|
||||||
git -C "$TMPDIR/probe" remote add origin "$REPO"
|
|
||||||
for attempt in {1..30}; do
|
|
||||||
if git -C "$TMPDIR/probe" fetch --depth=1 origin "$SHA" >/dev/null 2>&1; then
|
|
||||||
echo "Submodule commit $SHA is fetchable for $SERVICE"
|
|
||||||
rm -rf "$TMPDIR"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Attempt $attempt/30: $SHA not fetchable yet; waiting 10s"
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
rm -rf "$TMPDIR"
|
|
||||||
|
|
||||||
echo "::error::Submodule commit $SHA for $SERVICE was not fetchable after 300s"
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────
|
|
||||||
# Phase 2: Build and Push Images (Matrix)
|
|
||||||
# ─────────────────────────────────────────────────
|
|
||||||
build:
|
|
||||||
needs: [changes, wait-submodule-ref]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include: ${{ fromJson(needs.changes.outputs.matrix) }}
|
|
||||||
if: |
|
|
||||||
always() &&
|
|
||||||
needs.changes.result == 'success' &&
|
|
||||||
(needs.wait-submodule-ref.result == 'success' || needs.wait-submodule-ref.result == 'skipped') &&
|
|
||||||
needs.changes.outputs.matrix != '[]'
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
submodules: false
|
|
||||||
|
|
||||||
- name: Sync submodule locally
|
|
||||||
env:
|
|
||||||
EVENT_NAME: ${{ github.event_name }}
|
|
||||||
DISPATCH_SHA: ${{ github.event.client_payload.sha }}
|
|
||||||
SUBMODULE_PATH: ${{ matrix.path }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
git submodule update --init --recursive "$SUBMODULE_PATH"
|
|
||||||
if [ "$EVENT_NAME" = "repository_dispatch" ] && [ -n "${DISPATCH_SHA:-}" ]; then
|
|
||||||
cd "$SUBMODULE_PATH"
|
|
||||||
git fetch origin "$DISPATCH_SHA"
|
|
||||||
git checkout "$DISPATCH_SHA"
|
|
||||||
cd "${GITHUB_WORKSPACE}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: docker/login-action@v4
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
|
|
||||||
- name: Docker metadata
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
SVC_NAME="${{ matrix.id }}"
|
|
||||||
SHORT=${GITHUB_SHA:0:7}
|
|
||||||
echo "image=${REGISTRY}/${IMAGE_NAME_PREFIX}/${SVC_NAME}" >> $GITHUB_OUTPUT
|
|
||||||
echo "tags=${REGISTRY}/${IMAGE_NAME_PREFIX}/${SVC_NAME}:sha-${SHORT}" >> $GITHUB_OUTPUT
|
|
||||||
echo "cache-registry=${REGISTRY}/${IMAGE_NAME_PREFIX}/${SVC_NAME}:buildcache" >> $GITHUB_OUTPUT
|
|
||||||
case "$SVC_NAME" in
|
|
||||||
"scraper-api") echo "dockerfile=infra/docker/scraper.Dockerfile" >> $GITHUB_OUTPUT ;;
|
|
||||||
"hub") echo "dockerfile=infra/docker/hub.Dockerfile" >> $GITHUB_OUTPUT ;;
|
|
||||||
"tools") echo "dockerfile=infra/docker/tools.Dockerfile" >> $GITHUB_OUTPUT ;;
|
|
||||||
"llm-api") echo "dockerfile=infra/docker/llm-api.Dockerfile" >> $GITHUB_OUTPUT ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Build and Push Docker image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ${{ steps.meta.outputs.dockerfile }}
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
build-args: |
|
|
||||||
COMMIT_COUNT=${{ env.NR_COMMIT_COUNT || github.run_number }}
|
|
||||||
COMMIT_SHA=${{ env.NR_COMMIT_SHA || github.sha }}
|
|
||||||
cache-from: type=registry,ref=${{ steps.meta.outputs['cache-registry'] }}
|
|
||||||
cache-to: type=registry,ref=${{ steps.meta.outputs['cache-registry'] }},mode=max
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
# Phase 3: Update Manifests and Submodule Refs
|
|
||||||
# ──────────────────────────────────────────────
|
|
||||||
update-manifest:
|
|
||||||
needs: [changes, wait-submodule-ref, build]
|
|
||||||
if: |
|
|
||||||
always() &&
|
|
||||||
needs.changes.result == 'success' &&
|
|
||||||
(needs.wait-submodule-ref.result == 'success' || needs.wait-submodule-ref.result == 'skipped') &&
|
|
||||||
(needs.build.result == 'success' || needs.build.result == 'skipped')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
with:
|
|
||||||
submodules: false
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
ref: main
|
|
||||||
|
|
||||||
- name: Update tags and submodules
|
|
||||||
run: |
|
|
||||||
SHORT_SHA=${GITHUB_SHA:0:7}
|
|
||||||
TAG="sha-$SHORT_SHA"
|
|
||||||
CHANGED=false
|
|
||||||
|
|
||||||
declare -A SERVICES
|
|
||||||
SERVICES["scraper-api"]="scraper.yml"
|
|
||||||
SERVICES["hub"]="hub.yml"
|
|
||||||
SERVICES["tools"]="tools.yml"
|
|
||||||
SERVICES["llm-api"]="llm-api.yml"
|
|
||||||
|
|
||||||
declare -A PATHS
|
|
||||||
PATHS["scraper-api"]="apps/scraper"
|
|
||||||
PATHS["hub"]="apps/hub"
|
|
||||||
PATHS["tools"]="apps/tools"
|
|
||||||
PATHS["llm-api"]="apps/llm-api"
|
|
||||||
|
|
||||||
# Use git config for possible commits
|
|
||||||
git config --local user.email "action@github.com"
|
|
||||||
git config --local user.name "GitHub Action"
|
|
||||||
|
|
||||||
for id in "${!SERVICES[@]}"; do
|
|
||||||
SHOULD_HAVE_RUN=false
|
|
||||||
if [ "${{ needs.changes.outputs['scraper-api'] }}" == "true" ] && [ "$id" == "scraper-api" ]; then SHOULD_HAVE_RUN=true; fi
|
|
||||||
if [ "${{ needs.changes.outputs['hub'] }}" == "true" ] && [ "$id" == "hub" ]; then SHOULD_HAVE_RUN=true; fi
|
|
||||||
if [ "${{ needs.changes.outputs['tools'] }}" == "true" ] && [ "$id" == "tools" ]; then SHOULD_HAVE_RUN=true; fi
|
|
||||||
if [ "${{ needs.changes.outputs['llm-api'] }}" == "true" ] && [ "$id" == "llm-api" ]; then SHOULD_HAVE_RUN=true; fi
|
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then SHOULD_HAVE_RUN=true; fi
|
|
||||||
|
|
||||||
if [ "$SHOULD_HAVE_RUN" == "true" ]; then
|
|
||||||
COMPOSE_FILE="infra/compose/${SERVICES[$id]}"
|
|
||||||
if [ -f "$COMPOSE_FILE" ]; then
|
|
||||||
echo "Updating $COMPOSE_FILE to $TAG"
|
|
||||||
sed -i "s|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_PREFIX }}/$id:.*|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_PREFIX }}/$id:$TAG|g" "$COMPOSE_FILE"
|
|
||||||
git add "$COMPOSE_FILE"
|
|
||||||
CHANGED=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If it's a repository_dispatch for this specific service, update its submodule pointer
|
|
||||||
if [ "${{ github.event_name }}" == "repository_dispatch" ] && [ "${{ github.event.client_payload.service }}" == "$id" ]; then
|
|
||||||
SHA_DISPATCH="${{ github.event.client_payload.sha }}"
|
|
||||||
SUB_PATH="${PATHS[$id]}"
|
|
||||||
if [ -n "$SHA_DISPATCH" ]; then
|
|
||||||
echo "Updating submodule $SUB_PATH to $SHA_DISPATCH"
|
|
||||||
git submodule update --init "$SUB_PATH"
|
|
||||||
git -C "$SUB_PATH" fetch origin "$SHA_DISPATCH"
|
|
||||||
git -C "$SUB_PATH" checkout "$SHA_DISPATCH"
|
|
||||||
git add "$SUB_PATH"
|
|
||||||
CHANGED=true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$CHANGED" == "true" ]; then
|
|
||||||
git commit -m "chore: update manifests and submodules [skip ci]"
|
|
||||||
|
|
||||||
for attempt in {1..3}; do
|
|
||||||
if git pull --rebase origin main && git push origin main; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Manifest push attempt $attempt/3 failed; retrying"
|
|
||||||
git rebase --abort || true
|
|
||||||
git pull --rebase origin main || true
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "::error::Failed to push manifest update after 3 attempts"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "No changes detected."
|
|
||||||
fi
|
|
||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- uses: DeterminateSystems/determinate-nix-action@main
|
- uses: DeterminateSystems/determinate-nix-action@main
|
||||||
- uses: DeterminateSystems/flakehub-push@main
|
- uses: DeterminateSystems/flakehub-push@main
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
name: Nix Build & Deploy — All Services
|
name: Nix Build & Deploy — All Services
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
# No `paths` filter: GitHub's path filters do not match submodule gitlink
|
||||||
|
# changes, so a submodule pointer update (e.g. from update-submodule.yml)
|
||||||
|
# would never trigger this deploy. Run on every push to main instead.
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
|
||||||
- 'apps/**'
|
|
||||||
- 'infra/**'
|
|
||||||
- 'flake.nix'
|
|
||||||
- 'flake.lock'
|
|
||||||
- '.github/workflows/nix-build.yml'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -17,6 +14,7 @@ concurrency:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VPS_HOST: ${{ secrets.VPS_HOST }}
|
VPS_HOST: ${{ secrets.VPS_HOST }}
|
||||||
@@ -47,6 +45,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache Nix
|
- name: Cache Nix
|
||||||
uses: DeterminateSystems/magic-nix-cache-action@v14
|
uses: DeterminateSystems/magic-nix-cache-action@v14
|
||||||
|
with:
|
||||||
|
use-flakehub: false
|
||||||
|
|
||||||
- name: Build ${{ matrix.service }}
|
- name: Build ${{ matrix.service }}
|
||||||
id: build
|
id: build
|
||||||
@@ -82,3 +82,22 @@ jobs:
|
|||||||
ssh "$VPS_USER@$VPS_HOST" "sudo systemctl restart ${{ matrix.service }}" || echo " ⚠️ restart failed (may not be enabled yet)"
|
ssh "$VPS_USER@$VPS_HOST" "sudo systemctl restart ${{ matrix.service }}" || echo " ⚠️ restart failed (may not be enabled yet)"
|
||||||
|
|
||||||
echo "✅ ${{ matrix.service }} deployed"
|
echo "✅ ${{ matrix.service }} deployed"
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
# Bersihkan sampah Nix di VPS SETELAH deploy: hapus generasi profile lama
|
||||||
|
# + nix store gc. Profil yang sedang dipakai tidak disentuh.
|
||||||
|
needs: build-and-deploy
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Nix GC on VPS
|
||||||
|
env:
|
||||||
|
VPS_HOST: ${{ secrets.VPS_HOST }}
|
||||||
|
VPS_USER: ${{ secrets.VPS_USER }}
|
||||||
|
SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$SSH_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
ssh "$VPS_USER@$VPS_HOST" "sudo /usr/local/bin/nix-gc-vps.sh" || echo "⚠️ Nix GC gagal (non-fatal)"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ node_modules
|
|||||||
.turbo/
|
.turbo/
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
/.idea
|
/.idea
|
||||||
|
.serena/
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
.c9/
|
.c9/
|
||||||
|
|||||||
+1
-1
Submodule apps/hub updated: b007ced615...ed9fa16b57
+1
-1
Submodule apps/llm-api updated: 67861f384b...5f7ead5503
@@ -61,10 +61,10 @@
|
|||||||
# ── Fetch submodule source ──
|
# ── Fetch submodule source ──
|
||||||
submoduleSrc = name: builtins.fetchGit {
|
submoduleSrc = name: builtins.fetchGit {
|
||||||
url = submoduleRepos.${name};
|
url = submoduleRepos.${name};
|
||||||
rev = if name == "hub" then "b007ced615dcc78392fc938350a23e8f022d83b7"
|
rev = if name == "hub" then "a90d0c43336a5f000b5856003d2293c420e7d595"
|
||||||
else if name == "scraper" then "62aa5b0e52859afe3ba9de1c7b11cfe2dacf6c2c"
|
else if name == "scraper" then "62aa5b0e52859afe3ba9de1c7b11cfe2dacf6c2c"
|
||||||
else if name == "tools" then "3956b90c3ce39ffa7ffba8084937f20e11364d6b"
|
else if name == "tools" then "3956b90c3ce39ffa7ffba8084937f20e11364d6b"
|
||||||
else if name == "llm-api" then "67861f384bd4f64d5236a9608b33c965fbebbc7f"
|
else if name == "llm-api" then "5f7ead5503082a71d41a36fd1727325c784e4b79"
|
||||||
else "HEAD";
|
else "HEAD";
|
||||||
submodules = true;
|
submodules = true;
|
||||||
};
|
};
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
cp -r .next $out/share/hub/
|
cp -r .next $out/share/hub/
|
||||||
cp -r public $out/share/hub/ 2>/dev/null || true
|
cp -r public $out/share/hub/ 2>/dev/null || true
|
||||||
cp package.json $out/share/hub/
|
cp package.json $out/share/hub/
|
||||||
|
cp next.config.{ts,mjs,js} $out/share/hub/ 2>/dev/null || true
|
||||||
cp -r node_modules $out/share/hub/
|
cp -r node_modules $out/share/hub/
|
||||||
cat > $out/bin/hub << WRAPPER
|
cat > $out/bin/hub << WRAPPER
|
||||||
#!${pkgs.runtimeShell}
|
#!${pkgs.runtimeShell}
|
||||||
|
|||||||
+37
-18
@@ -40,6 +40,14 @@ asepharyana.my.id, www.asepharyana.my.id, asepharyana.web.id, www.asepharyana.we
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboard.asepharyana.my.id {
|
dashboard.asepharyana.my.id {
|
||||||
|
import proxy 4013
|
||||||
|
}
|
||||||
|
|
||||||
|
imphnen.asepharyana.my.id {
|
||||||
|
import proxy 4009
|
||||||
|
}
|
||||||
|
|
||||||
|
scraper.asepharyana.my.id, api.asepharyana.my.id, scraper.asepharyana.web.id, api.asepharyana.web.id {
|
||||||
import proxy 4091
|
import proxy 4091
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +61,23 @@ tools.asepharyana.my.id, tools.asepharyana.web.id {
|
|||||||
|
|
||||||
9router.asepharyana.my.id {
|
9router.asepharyana.my.id {
|
||||||
# LLM streaming: 9router combo models punya TTFT sampe 30-40s (deepseek,
|
# LLM streaming: 9router combo models punya TTFT sampe 30-40s (deepseek,
|
||||||
}
|
# fallback chain). Default (proxy) response_header_timeout 30s / read 60s
|
||||||
|
# bikin false-positive 504 walau 9router masih ngolah. Longgarkan khusus
|
||||||
|
# biar health-check & request PR-Agent real gak kena timeout transient.
|
||||||
|
encode zstd gzip
|
||||||
|
header {
|
||||||
|
-Server
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
}
|
||||||
|
reverse_proxy 127.0.0.1:4014 {
|
||||||
|
transport http {
|
||||||
|
dial_timeout 3s
|
||||||
|
response_header_timeout 120s
|
||||||
|
read_timeout 300s
|
||||||
|
write_timeout 300s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pr-agent.asepharyana.my.id {
|
pr-agent.asepharyana.my.id {
|
||||||
import proxy 4002
|
import proxy 4002
|
||||||
@@ -80,26 +104,21 @@ ml-zeavisedu.asepharyana.my.id {
|
|||||||
}
|
}
|
||||||
|
|
||||||
upload.asepharyana.my.id, upload.asepharyana.web.id {
|
upload.asepharyana.my.id, upload.asepharyana.web.id {
|
||||||
import proxy 4013
|
|
||||||
}
|
|
||||||
|
|
||||||
imphnen.asepharyana.my.id {
|
|
||||||
import proxy 4009
|
|
||||||
}
|
|
||||||
|
|
||||||
upload.asepharyana.my.id, upload.asepharyana.web.id {
|
|
||||||
# Upload/download besar: JANGAN kompres, JANGAN limit body, flush instan (no buffering)
|
# Upload/download besar: JANGAN kompres, JANGAN limit body, flush instan (no buffering)
|
||||||
reverse_proxy 127.0.0.1:4000 {
|
header {
|
||||||
transport http {
|
-Server
|
||||||
keepalive 120s
|
X-Content-Type-Options "nosniff"
|
||||||
keepalive_interval 30s
|
}
|
||||||
max_conns_per_host 100
|
|
||||||
dial_timeout 3s
|
|
||||||
}
|
|
||||||
flush_interval -1
|
|
||||||
}
|
|
||||||
request_body {
|
request_body {
|
||||||
max_size 0
|
max_size 0
|
||||||
}
|
}
|
||||||
reverse_proxy 127.0.0.1:4000 {
|
reverse_proxy 127.0.0.1:4000 {
|
||||||
|
transport http {
|
||||||
|
dial_timeout 3s
|
||||||
|
read_timeout 300s
|
||||||
|
write_timeout 300s
|
||||||
|
}
|
||||||
|
flush_interval -1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,30 @@
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# firewall.sh — deny-by-default firewall untuk orangevps
|
# firewall.sh — deny-by-default firewall untuk orangevps
|
||||||
# Public: 22 (SSH), 80/443 (Caddy), 4013 (hermes dashboard)
|
# Public: 22 (SSH), 80/443 (Caddy), 4013 (hermes dashboard)
|
||||||
|
# 25565 (Minecraft) — WHITELIST TCPShield proxy only
|
||||||
# Tailscale CGNAT 100.64/10: semua port (imrnes & node lain)
|
# Tailscale CGNAT 100.64/10: semua port (imrnes & node lain)
|
||||||
# Localhost: semua
|
# Localhost: semua
|
||||||
# Sisanya: DROP + log
|
# Sisanya: DROP + log
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
# TCPShield proxy ranges (https://tcpshield.com/v4/ + /v4-cf/)
|
||||||
|
# Update saat TCPShield publish range baru.
|
||||||
|
TCPSHIELD_V4=(
|
||||||
|
198.178.119.0/24
|
||||||
|
104.234.6.0/24
|
||||||
|
)
|
||||||
|
TCPSHIELD_V4_CF=(
|
||||||
|
89.222.122.36/31
|
||||||
|
152.233.22.8/31
|
||||||
|
89.222.108.246/31
|
||||||
|
84.17.55.186/31
|
||||||
|
51.79.45.52/31
|
||||||
|
5.135.84.92/30
|
||||||
|
51.75.35.44/30
|
||||||
|
51.161.27.110/31
|
||||||
|
152.233.30.16/31
|
||||||
|
152.233.30.232/31
|
||||||
|
203.205.31.160/31
|
||||||
|
)
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
### IPv4 ###
|
### IPv4 ###
|
||||||
@@ -33,6 +53,10 @@ iptables -A INPUT -p tcp --dport 80 -j ACCEPT
|
|||||||
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
|
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
|
||||||
# Public: hermes dashboard (auth-protected)
|
# Public: hermes dashboard (auth-protected)
|
||||||
iptables -A INPUT -p tcp --dport 4013 -j ACCEPT
|
iptables -A INPUT -p tcp --dport 4013 -j ACCEPT
|
||||||
|
# Public: Minecraft (FTB sky) — hanya dari proxy TCPShield
|
||||||
|
for cidr in "${TCPSHIELD_V4[@]}" "${TCPSHIELD_V4_CF[@]}"; do
|
||||||
|
iptables -A INPUT -s "$cidr" -p tcp --dport 25565 -j ACCEPT
|
||||||
|
done
|
||||||
|
|
||||||
# ICMP (ping, PMTU)
|
# ICMP (ping, PMTU)
|
||||||
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 5/sec --limit-burst 10 -j ACCEPT
|
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 5/sec --limit-burst 10 -j ACCEPT
|
||||||
@@ -66,9 +90,10 @@ ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
|
|||||||
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
|
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
|
||||||
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
|
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
|
||||||
ip6tables -A INPUT -p tcp --dport 4013 -j ACCEPT
|
ip6tables -A INPUT -p tcp --dport 4013 -j ACCEPT
|
||||||
|
# Minecraft 25565: TCPShield IPv4 only — tidak ada range IPv6 publik
|
||||||
ip6tables -A INPUT -p icmpv6 -j ACCEPT
|
ip6tables -A INPUT -p icmpv6 -j ACCEPT
|
||||||
ip6tables -A INPUT -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "FW6-DROP " --log-level 4
|
ip6tables -A INPUT -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "FW6-DROP " --log-level 4
|
||||||
ip6tables -A INPUT -j DROP
|
ip6tables -A INPUT -j DROP
|
||||||
|
|
||||||
echo "Firewall applied:"
|
echo "Firewall applied:"
|
||||||
iptables -L INPUT -n --line-numbers | head -20
|
iptables -L INPUT -n --line-numbers | head -24
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Nix service targets (file_sd) — 2026-08-02
|
||||||
|
# Service yang expose /metrics langsung:
|
||||||
|
- targets: ['127.0.0.1:4016']
|
||||||
|
labels:
|
||||||
|
service: gmw-discord-gateway
|
||||||
|
- targets: ['127.0.0.1:4008']
|
||||||
|
labels:
|
||||||
|
service: tools-gateway
|
||||||
|
# GMW backend expose /api/metrics (bukan /metrics):
|
||||||
|
- targets: ['127.0.0.1:4001']
|
||||||
|
labels:
|
||||||
|
service: gmw-backend
|
||||||
|
__metrics_path__: /api/metrics
|
||||||
|
# llm-api expose /metrics sejak 2026-08-03 (feat metrics):
|
||||||
|
- targets: ['127.0.0.1:4010']
|
||||||
|
labels:
|
||||||
|
service: llm-api
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[Service]
|
||||||
|
Environment=OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317
|
||||||
|
Environment=OTEL_SERVICE_NAME=scraper
|
||||||
|
Environment=OTEL_METRICS_EXPORT_INTERVAL=5000
|
||||||
+1
-1
Submodule plugins/hub-guide updated: abd60c607d...ffcd3ba8e3
Reference in New Issue
Block a user