Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
324fe08913 | ||
|
|
e171201b1c | ||
|
|
ec1394bdc5 | ||
|
|
f0787b5f23 | ||
|
|
0df6c35d5a | ||
|
|
5d21848e87 | ||
|
|
b96548bffa | ||
|
|
e19fa76dc3 | ||
|
|
c535ee7858 | ||
|
|
80b5161630 | ||
|
|
1ccc0355ac | ||
|
|
12aed642a9 | ||
|
|
f8e3dccb44 | ||
|
|
1811c6adc2 | ||
|
|
ceeac5c02f | ||
|
|
d0c9f16d5d | ||
|
|
6cbafbfe2e | ||
|
|
d6b9a0806b | ||
|
|
60a1af436f | ||
|
|
2459541677 | ||
|
|
33b999d2b7 | ||
|
|
59f131f951 | ||
|
|
cc8c5088a4 | ||
|
|
d15e8621a9 | ||
|
|
33d5b12ec6 | ||
|
|
b5596e1398 | ||
|
|
46730ec07d | ||
|
|
1ae8a53cd4 | ||
|
|
ff52f8841c | ||
|
|
b747ae5c3b | ||
|
|
64ce812b4e | ||
|
|
9837d9dc50 | ||
|
|
86dda74174 | ||
|
|
d045602f3e | ||
|
|
32049c70ac | ||
|
|
be7348a27d | ||
|
|
43f0df4493 | ||
|
|
f0c7534152 | ||
|
|
a4687c9d5b | ||
|
|
e42e967b7f | ||
|
|
d99bc06df3 | ||
|
|
87df0d7ae2 | ||
|
|
518afa5645 | ||
|
|
2913f515f0 | ||
|
|
05e20df680 | ||
|
|
59e2f9d173 | ||
|
|
f78b826e59 | ||
|
|
d75424634d | ||
|
|
cc8cefb33a | ||
|
|
757b48b446 | ||
|
|
20c81b4018 | ||
|
|
ca81a8b7ee | ||
|
|
ac25a51113 | ||
|
|
10aeda4d4b |
@@ -0,0 +1,20 @@
|
|||||||
|
name: Publish to FlakeHub
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flakehub-publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: DeterminateSystems/determinate-nix-action@main
|
||||||
|
- uses: DeterminateSystems/flakehub-push@main
|
||||||
|
with:
|
||||||
|
visibility: public
|
||||||
|
rolling: true
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
name: Nix Build & Deploy — All Services
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'apps/**'
|
||||||
|
- 'infra/**'
|
||||||
|
- 'flake.nix'
|
||||||
|
- 'flake.lock'
|
||||||
|
- '.github/workflows/nix-build.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: nix-deploy
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
VPS_HOST: ${{ secrets.VPS_HOST }}
|
||||||
|
VPS_USER: ${{ secrets.VPS_USER }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
service: [hub, scraper, tools-gateway, tools-workers, tools-frontend, llm-api]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: DeterminateSystems/nix-installer-action@v22
|
||||||
|
with:
|
||||||
|
determinate: false
|
||||||
|
extra-conf: |
|
||||||
|
sandbox = false
|
||||||
|
accept-flake-config = true
|
||||||
|
|
||||||
|
- name: Cache Nix
|
||||||
|
uses: DeterminateSystems/magic-nix-cache-action@v14
|
||||||
|
|
||||||
|
- name: Build ${{ matrix.service }}
|
||||||
|
id: build
|
||||||
|
run: |
|
||||||
|
nix build .#${{ matrix.service }} --impure --option sandbox false --print-build-logs
|
||||||
|
STORE_PATH=$(readlink result)
|
||||||
|
echo "store-path=$STORE_PATH" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "✅ ${{ matrix.service }}: $STORE_PATH"
|
||||||
|
|
||||||
|
- name: Setup SSH key
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
env:
|
||||||
|
SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$SSH_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
sed -i 's/\r$//' ~/.ssh/id_ed25519
|
||||||
|
ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null 2>&1 || { echo "SSH key invalid"; exit 1; }
|
||||||
|
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
|
- name: Deploy ${{ matrix.service }} to VPS
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: |
|
||||||
|
STORE_PATH="${{ steps.build.outputs.store-path }}"
|
||||||
|
echo "=== Copying ${{ matrix.service }}: $STORE_PATH ==="
|
||||||
|
nix copy --to "ssh://$VPS_USER@$VPS_HOST" "$STORE_PATH"
|
||||||
|
|
||||||
|
echo "=== Updating profile ==="
|
||||||
|
ssh "$VPS_USER@$VPS_HOST" "sudo /nix/var/nix/profiles/default/bin/nix-env --profile /nix/var/nix/profiles/${{ matrix.service }} --set '$STORE_PATH'"
|
||||||
|
|
||||||
|
echo "=== Restarting service ==="
|
||||||
|
ssh "$VPS_USER@$VPS_HOST" "sudo systemctl restart ${{ matrix.service }}" || echo " ⚠️ restart failed (may not be enabled yet)"
|
||||||
|
|
||||||
|
echo "✅ ${{ matrix.service }} deployed"
|
||||||
@@ -63,3 +63,4 @@ docs/todo.md
|
|||||||
**/vendor/
|
**/vendor/
|
||||||
|
|
||||||
.codegraph/
|
.codegraph/
|
||||||
|
result
|
||||||
|
|||||||
@@ -1,181 +1,232 @@
|
|||||||
# Asepharyana Hub
|
# Architecture
|
||||||
|
|
||||||
Hub repo untuk ekosistem portfolio dan layanan pendukung milik Asep Haryana Saputra.
|
## Hub Repository Structure Overview
|
||||||
Aplikasi dipisah sebagai submodule agar frontend, API, dan service pendukung bisa dikembangkan serta di-deploy secara independen.
|
|
||||||
|
|
||||||
## Services
|
```diff
|
||||||
|
asepharyana-hub/
|
||||||
| Service | Path | Notes |
|
├── apps/ # Application services (Git submodules)
|
||||||
| :------ | :------------- | :----------------------------- |
|
│ └── scraper/ # Web scraper service
|
||||||
| Scraper | `apps/scraper` | Web scraper service + Dapr SDK |
|
├── docs/ # Documentation
|
||||||
| NATS | — | Message broker + JetStream |
|
│ ├── adr/ # Architecture Decision Records
|
||||||
| Dapr | — | Sidecar runtime (per service) |
|
│ ├── add-new-app.md # Guide for adding new services
|
||||||
|
│ └── superpowers/ # Project capabilities tracking
|
||||||
## Infrastructure
|
├── infra/ # Infrastructure as code
|
||||||
|
│ ├── compose/ # Docker Compose files per service
|
||||||
File compose berada di `infra/compose/`:
|
│ ├── config/ # Infrastructure configuration
|
||||||
|
│ ├── docker/ # Dockerfiles per service
|
||||||
- `traefik.yml`: reverse proxy Traefik untuk semua layanan.
|
│ └── traefik/ # Traefik reverse proxy config
|
||||||
- `shared.yml`: Redis (cache + Dapr state store).
|
│ └── dynamic/ # Dynamic routing rules (YAML)
|
||||||
- `nats.yml`: NATS message broker dengan JetStream persistence.
|
├── scripts/ # Utility scripts
|
||||||
- `dapr.yml`: Dapr placement service untuk koordinasi sidecar.
|
│ ├── git-hooks/ # Git hook scripts
|
||||||
- `scraper.yml`: manifest deploy per service (app + Dapr sidecar).
|
│ ├── cleanup-ghcr.sh # GHCR image cleanup
|
||||||
|
│ └── update-deps.sh # Dependency update helper
|
||||||
Dockerfile per service berada di `infra/docker/`.
|
├── .github/workflows/ # CI/CD pipelines
|
||||||
|
├── eslint.config.mjs # Root ESLint config
|
||||||
## Docker Image Builds
|
├── package.json # Root formatting/lint helper scripts
|
||||||
|
└── .prettierrc # Prettier formatting rules
|
||||||
Build image via Dockerfile:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build -f infra/docker/scraper.Dockerfile -t scraper-api:latest .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Tag and push:
|
## Technology Stack
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
|| Service | Path | Language/Runtime | Framework | Database | Key Libraries |
|
||||||
|
||---------|----------------|------------------|-----------|----------|---------------|
|
||||||
|
|| **scraper** | `apps/scraper` | — | — | — | — |
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
|
||||||
|
|| Component | Technology | Purpose |
|
||||||
|
||---------------------|-------------------------|------------------------------------------------------------------|
|
||||||
|
|| Reverse Proxy | Traefik v3.6 | TLS termination, routing, middleware (rate-limit, headers, auth) |
|
||||||
|
|| Container Runtime | Docker + Docker Compose | Service isolation and orchestration |
|
||||||
|
|| Container Registry | GHCR (ghcr.io) | Docker image storage |
|
||||||
|
|| Networking | Tailscale | Secure overlay network between VPS nodes |
|
||||||
|
|| Message Bus | NATS + JetStream | Event-driven pub/sub, job queues, streaming |
|
||||||
|
|| Runtime Sidecar | Dapr | Service invocation, pub/sub abstraction, state management |
|
||||||
|
|| Cache & State | Redis (Alpine) | Session store, rate limit counters, caching, Dapr state store |
|
||||||
|
|| CI/CD | GitHub Actions | Build, test, deploy automation |
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
|
||||||
|
### Traefik Reverse Proxy
|
||||||
|
|
||||||
|
Traefik runs as the entry point for all HTTP/S traffic. It is configured via:
|
||||||
|
|
||||||
|
- **Static config**: CLI arguments in `infra/compose/traefik.yml` — entry points, providers, plugins
|
||||||
|
- **Dynamic config**: `infra/traefik/dynamic/` — routers, services, middlewares, TLS
|
||||||
|
- **Docker provider**: Auto-discovers containers with `traefik.enable=true` labels
|
||||||
|
- **File provider**: Loads `apps.yaml` (routers/services), `middlewares.yaml`, `ssl.yaml`
|
||||||
|
|
||||||
|
Key middleware chains (`infra/traefik/dynamic/middlewares.yaml`):
|
||||||
|
|
||||||
|
- `secure-headers` — SSL redirect, HSTS, XSS protection, CSP
|
||||||
|
- `compress` — Gzip compression for responses over 256 bytes
|
||||||
|
- `rate-limit` — 100 avg / 50 burst requests
|
||||||
|
- `buffer` — 10MB request/response body limit
|
||||||
|
- `block-sensitive-paths` — blocks `.env`, `.git`, `/wp-admin` etc.
|
||||||
|
- `common-chain` — composes secure-headers + compress + retry + rate-limit + buffer
|
||||||
|
|
||||||
|
All services route through Traefik on port 443 (TLS), with automatic HTTP-to-HTTPS redirect.
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
Each service has its own Compose file under `infra/compose/`. All services join the `app-shared-net` external Docker network, enabling inter-service communication by container name.
|
||||||
|
|
||||||
|
Shared services:
|
||||||
|
|
||||||
|
- `infra/compose/shared.yml` — Redis (alias: `redis`)
|
||||||
|
- `infra/compose/traefik.yml` — Traefik reverse proxy
|
||||||
|
|
||||||
|
Service compose files are combined during deployment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
docker compose -f traefik.yml -f shared.yml -f scraper.yml up -d
|
||||||
|
|
||||||
docker tag scraper-api:latest ghcr.io/asepharyana/asepharyana-hub/scraper-api:sha-$SHORT_SHA
|
|
||||||
docker push ghcr.io/asepharyana/asepharyana-hub/scraper-api:sha-$SHORT_SHA
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Local Development
|
### Tailscale Networking
|
||||||
|
|
||||||
### 1) Jalankan dependency bersama
|
### Arsitektur
|
||||||
|
|
||||||
|
Semua VPS terhubung via **Tailscale**. Setiap VPS punya IP Tailscale dan service berkomunikasi antar VPS melalui Tailscale network (`100.64.0.0/10`). Container-to-Tailscale connectivity requires a systemd service that adds a route to the main routing table:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose -f infra/compose/shared.yml up -d
|
ip route add 100.64.0.0/10 dev tailscale0 table main
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2) Jalankan service yang dibutuhkan
|
This is managed by `/etc/systemd/system/tailscale-routes.service` on the `orangevps` VPS.
|
||||||
|
|
||||||
Refer to each service's own documentation for development setup.
|
### Data Flow
|
||||||
|
|
||||||
## API Docs and Monitoring
|
### Request Flow (Production)
|
||||||
|
|
||||||
Refer to each service's own documentation for API docs.
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User as Browser/Client
|
||||||
|
participant DNS as Cloudflare DNS
|
||||||
|
participant Traefik as Traefik Proxy
|
||||||
|
participant App as Application Container
|
||||||
|
participant DB as PostgreSQL (imrnes via Tailscale)
|
||||||
|
participant Redis as Redis (imrnes via Tailscale)
|
||||||
|
|
||||||
|
User->>DNS: asepharyana.my.id
|
||||||
|
DNS->>User: A/AAAA record → orangevps VPS IP
|
||||||
|
User->>Traefik: HTTPS request :443
|
||||||
|
Traefik->>Traefik: TLS termination
|
||||||
|
Traefik->>Traefik: Middleware chain (headers, rate-limit, buffer)
|
||||||
|
Traefik->>App: HTTP reverse-proxy (internal network)
|
||||||
|
|
||||||
|
alt Database query
|
||||||
|
App->>DB: sqlx/Drizzle query via Tailscale
|
||||||
|
DB-->>App: Result set
|
||||||
|
else Cache lookup
|
||||||
|
App->>Cache: GET/SET via Tailscale
|
||||||
|
Cache-->>App: Cached value
|
||||||
|
end
|
||||||
|
|
||||||
|
App-->>Traefik: HTTP response
|
||||||
|
Traefik-->>User: HTTPS response
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Pipeline
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Push to main] --> B{Changed paths?}
|
||||||
|
B -->|apps/** or infra/docker/**| C[Build Docker Images]
|
||||||
|
B -->|infra/compose/**| D[Deploy to VPS]
|
||||||
|
B -->|apps/*/src/**/*.ts| E[Lint + TypeCheck]
|
||||||
|
|
||||||
|
C --> F[Push to GHCR]
|
||||||
|
F --> G[Update Compose tags]
|
||||||
|
G --> D
|
||||||
|
|
||||||
|
D --> H[SSH into VPS]
|
||||||
|
H --> I[Pull images]
|
||||||
|
I --> J[docker compose up -d]
|
||||||
|
|
||||||
|
subgraph "Build Phase"
|
||||||
|
C
|
||||||
|
F
|
||||||
|
G
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Deploy Phase"
|
||||||
|
D
|
||||||
|
H
|
||||||
|
I
|
||||||
|
J
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deployment Architecture
|
||||||
|
|
||||||
|
### Image Tags
|
||||||
|
|
||||||
|
- `latest` — mutable, for convenience
|
||||||
|
- `sha-<short-sha>` — immutable, for deterministic rollbacks
|
||||||
|
- Build cache: `sha-<short>-buildcache`
|
||||||
|
|
||||||
|
Registry: `ghcr.io/asepharyana/asepharyana-hub/<service>`
|
||||||
|
|
||||||
## Deployment Notes
|
## Deployment Notes
|
||||||
|
|
||||||
- Pipeline memakai image tag berbasis commit SHA (`sha-<short-sha>`), bukan `latest`.
|
- Pipeline memakai image tag berbasis commit SHA (`sha-<short-sha>`), bukan `latest`.
|
||||||
- Deploy Compose sekarang mencakup `infra/compose/*.yml` dan `deploy-docker.yml` akan berjalan langsung ketika `infra/compose/**` berubah.
|
- Deploy Compose sekarang mencakup `infra/compose/*.yml` dan `deploy-docker.yml` akan berjalan langsung ketika `infra/compose/**` berubah.
|
||||||
|
- Selective deployment: hanya compose file yg berubah yang di-redeploy.
|
||||||
|
|
||||||
## Networking & Tailscale
|
## Networking & Tailscale
|
||||||
|
|
||||||
### Arsitektur
|
### Arsitektur
|
||||||
|
|
||||||
Semua VPS terhubung via **Tailscale**. Setiap VPS punya IP Tailscale dan service berkomunikasi antar VPS melalui Tailscale network (`100.64.0.0/10`).
|
Semua VPS terhubung via **Tailscale**. Setiap VPS punya IP Tailscale dan service berkomunikasi antar VPS melalui Tailscale network (`100.64.0.0/10`). Container-to-Tailscale connectivity requires a systemd service that adds a route to the main routing table:
|
||||||
|
|
||||||
| VPS | Tailscale IP | Service |
|
|
||||||
| :--------- | :-------------- | :------------------------------------- |
|
|
||||||
| `imrnes` | `100.121.180.82` | PostgreSQL, Redis |
|
|
||||||
| `orangevps` | `100.79.111.61` | App containers (Traefik, scraper-api) |
|
|
||||||
| `archlinux` | `100.84.39.83` | _(development machine)_ |
|
|
||||||
|
|
||||||
### Container → Tailscale Connectivity
|
|
||||||
|
|
||||||
Docker containers di bridge network (`app-shared-net`) **tidak otomatis bisa access Tailscale IPs** karena Tailscale menggunakan **custom policy routing** (routes di `table 52`, bukan `main` table).
|
|
||||||
|
|
||||||
#### Fix: Tailscale Route di Main Table
|
|
||||||
|
|
||||||
Agar container bisa reach Tailscale IPs (untuk DB, Redis, dll), tambahkan route ke `main` routing table:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Manual (hilang setelah reboot)
|
|
||||||
ip route add 100.64.0.0/10 dev tailscale0 table main
|
ip route add 100.64.0.0/10 dev tailscale0 table main
|
||||||
|
|
||||||
# Persistent (systemd service)
|
|
||||||
# Sudah dikonfigurasi sebagai /etc/systemd/system/tailscale-routes.service
|
|
||||||
# Service ini berjalan otomatis setelah tailscaled start
|
|
||||||
systemctl enable tailscale-routes.service
|
|
||||||
systemctl start tailscale-routes.service
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Environment Variables
|
This is managed by `/etc/systemd/system/tailscale-routes.service` on the `orangevps` VPS.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
Service yang connect ke Tailscale IP:
|
Service yang connect ke Tailscale IP:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
# PostgreSQL di imrnes
|
# PostgreSQL di imrnes
|
||||||
DATABASE_URL=postgres://user:pass@100.121.180.82:5432/dbname
|
DATABASE_URL=postgres://user:***@100.121.180.82:6432/dbname
|
||||||
|
|
||||||
# Redis di imrnes
|
# Redis di imrnes
|
||||||
REDIS_URL=redis://100.121.180.82:6379
|
REDIS_URL=redis://100.121.180.82:6379
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Persistent Systemd Service
|
## Submodule Strategy
|
||||||
|
|
||||||
File: `/etc/systemd/system/tailscale-routes.service`
|
Each application lives in its own Git repository and is imported as a submodule into `apps/`. This approach:
|
||||||
|
|
||||||
```ini
|
- **Enables independent development** — each service can be developed, tested, and versioned separately
|
||||||
[Unit]
|
- **Pins exact commits** — the super-repository tracks exact submodule SHAs, enabling reproducible deployments
|
||||||
Description=Add Tailscale routes to main routing table
|
- **Supports `repository_dispatch`** — when a submodule receives a push, it can trigger the super-repository to build and deploy only that service
|
||||||
After=tailscaled.service
|
|
||||||
Requires=tailscaled.service
|
|
||||||
|
|
||||||
[Service]
|
### Submodule Lifecycle
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/bin/bash -c '/usr/sbin/ip route add 100.64.0.0/10 dev tailscale0 table main 2>/dev/null || /usr/sbin/ip route replace 100.64.0.0/10 dev tailscale0 table main'
|
|
||||||
RemainAfterExit=yes
|
|
||||||
|
|
||||||
[Install]
|
1. Developer pushes to a submodule (e.g., `apps/scraper`)
|
||||||
WantedBy=multi-user.target
|
2. Submodule's GitHub Action dispatches `repository_dispatch` to the super-repo with the service name and new SHA
|
||||||
```
|
3. Super-repo detects the dispatch, waits for the SHA to be fetchable, then builds only that service
|
||||||
|
4. The compose manifest is updated and committed with the new SHA tag
|
||||||
|
5. The deploy workflow runs and updates only the changed containers
|
||||||
|
|
||||||
Install & enable:
|
### Updating Submodules
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo tee /etc/systemd/system/tailscale-routes.service > /dev/null << 'EOF'
|
# Update a single submodule to latest
|
||||||
[Unit]
|
cd apps/scraper
|
||||||
Description=Add Tailscale routes to main routing table
|
git checkout main
|
||||||
After=tailscaled.service
|
git pull
|
||||||
Requires=tailscaled.service
|
cd ../..
|
||||||
|
git add apps/scraper
|
||||||
[Service]
|
git commit -m "chore(scraper): update submodule to latest"
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/bin/bash -c '/usr/sbin/ip route add 100.64.0.0/10 dev tailscale0 table main 2>/dev/null || /usr/sbin/ip route replace 100.64.0.0/10 dev tailscale0 table main'
|
|
||||||
RemainAfterExit=yes
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable tailscale-routes.service
|
|
||||||
sudo systemctl start tailscale-routes.service
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Troubleshooting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Cek Tailscale peers
|
|
||||||
tailscale status
|
|
||||||
|
|
||||||
# Cek route table 52 (Tailscale internal)
|
|
||||||
ip route show table 52
|
|
||||||
|
|
||||||
# Cek route table main (yang dipakai container)
|
|
||||||
ip route show table main | grep 100.
|
|
||||||
|
|
||||||
# Test connectivity dari dalam container
|
|
||||||
docker exec <container> node -e "
|
|
||||||
const net = require('net');
|
|
||||||
const c = new net.Socket();
|
|
||||||
c.setTimeout(5000);
|
|
||||||
c.connect(5432, '100.121.180.82', () => { console.log('OK'); c.end(); });
|
|
||||||
c.on('error', e => { console.log('FAIL:', e.code); });
|
|
||||||
c.on('timeout', () => { console.log('TIMEOUT'); c.destroy(); });
|
|
||||||
"
|
|
||||||
|
|
||||||
# Cek service tailscale-routes
|
|
||||||
systemctl status tailscale-routes.service
|
|
||||||
```
|
|
||||||
|
|
||||||
## Menambahkan Aplikasi Baru
|
|
||||||
|
|
||||||
Panduan langkah demi langkah untuk menambahkan aplikasi baru ada di `docs/add-new-app.md`.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
+1
-1
Submodule apps/hub updated: 51798f483f...b007ced615
+1
-1
Submodule apps/llm-api updated: d8425ea3b3...81c5177249
+1
-1
Submodule apps/scraper updated: bc782ae4f8...62aa5b0e52
+1
-1
Submodule apps/tools updated: 3956b90c3c...036f67d05a
Generated
+61
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1785301185,
|
||||||
|
"narHash": "sha256-eoS3KQTO0aPWXZvIaRbRAzSSHW3l5wdMFXtT1ISfoKA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9bc02893134c733dd85de46ee4fb2fac696b5529",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
{
|
||||||
|
description = "Asepharyana Hub — Nix builds for infrastructure and app services";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ── mkApp generator ──
|
||||||
|
mkApp = { name, src, buildScript, installScript, nativeBuildInputs ? [], buildInputs ? [] }:
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
inherit name src;
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
cacert curl gcc gnumake openssl pkg-config python3 libclang
|
||||||
|
] ++ nativeBuildInputs;
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
nodejs openssl stdenv.cc.cc.lib libffi
|
||||||
|
] ++ buildInputs;
|
||||||
|
|
||||||
|
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||||
|
LD_LIBRARY_PATH = "${pkgs.libclang.lib}/lib:${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.libffi}/lib";
|
||||||
|
NIX_ENFORCE_PURITY = "0";
|
||||||
|
|
||||||
|
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
|
NODE_EXTRA_CA_CERTS = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
|
NODE_ENV = "production";
|
||||||
|
|
||||||
|
phases = [ "unpackPhase" "buildPhase" "installPhase" ];
|
||||||
|
buildPhase = ''
|
||||||
|
export HOME="$TMPDIR" CARGO_HOME="$TMPDIR/.cargo-${name}"
|
||||||
|
SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
|
||||||
|
'' + buildScript;
|
||||||
|
installPhase = installScript;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ── Node.js ──
|
||||||
|
nodejs = pkgs.nodejs-slim_22;
|
||||||
|
pnpm = pkgs.pnpm.override { inherit nodejs; };
|
||||||
|
|
||||||
|
# ── Common Rust build deps ──
|
||||||
|
cargoDeps = with pkgs; [ rustc cargo clang cmake pkg-config openssl.dev zlib ];
|
||||||
|
|
||||||
|
# ── Submodule repos — URLs from .gitmodules ──
|
||||||
|
submoduleRepos = {
|
||||||
|
hub = "https://github.com/asepharyana/asepharyana-hub-hub.git";
|
||||||
|
scraper = "https://github.com/asepharyana/asepharyana-hub-scraper.git";
|
||||||
|
tools = "https://github.com/asepharyana/asepharyana-hub-tools.git";
|
||||||
|
llm-api = "https://github.com/asepharyana/asepharyana-hub-llm-api.git";
|
||||||
|
};
|
||||||
|
|
||||||
|
# ── Fetch submodule source ──
|
||||||
|
submoduleSrc = name: builtins.fetchGit {
|
||||||
|
url = submoduleRepos.${name};
|
||||||
|
rev = if name == "hub" then "b007ced615dcc78392fc938350a23e8f022d83b7"
|
||||||
|
else if name == "scraper" then "62aa5b0e52859afe3ba9de1c7b11cfe2dacf6c2c"
|
||||||
|
else if name == "tools" then "3956b90c3ce39ffa7ffba8084937f20e11364d6b"
|
||||||
|
else if name == "llm-api" then "67861f384bd4f64d5236a9608b33c965fbebbc7f"
|
||||||
|
else "HEAD";
|
||||||
|
submodules = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ─── App Derivations ───
|
||||||
|
hub = mkApp {
|
||||||
|
name = "hub-0.1.0";
|
||||||
|
src = submoduleSrc "hub";
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [ bun ];
|
||||||
|
|
||||||
|
buildScript = ''
|
||||||
|
echo "=== Installing dependencies ==="
|
||||||
|
bun install 2>&1
|
||||||
|
echo "=== Building Next.js ==="
|
||||||
|
bun run build 2>&1
|
||||||
|
'';
|
||||||
|
|
||||||
|
installScript = ''
|
||||||
|
mkdir -p $out/share/hub $out/bin
|
||||||
|
cp -r .next $out/share/hub/
|
||||||
|
cp -r public $out/share/hub/ 2>/dev/null || true
|
||||||
|
cp package.json $out/share/hub/
|
||||||
|
cp -r node_modules $out/share/hub/
|
||||||
|
cat > $out/bin/hub << WRAPPER
|
||||||
|
#!${pkgs.runtimeShell}
|
||||||
|
exec ${pkgs.bun}/bin/bun run --cwd $out/share/hub start
|
||||||
|
WRAPPER
|
||||||
|
chmod +x $out/bin/hub
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
scraper = mkApp {
|
||||||
|
name = "scraper-0.1.0";
|
||||||
|
src = submoduleSrc "scraper";
|
||||||
|
nativeBuildInputs = cargoDeps;
|
||||||
|
|
||||||
|
buildScript = ''
|
||||||
|
echo "=== Building scraper ==="
|
||||||
|
cargo build --release 2>&1
|
||||||
|
'';
|
||||||
|
|
||||||
|
installScript = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp target/release/scraper $out/bin/scraper
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
tools-gateway = mkApp {
|
||||||
|
name = "tools-gateway-0.1.0";
|
||||||
|
src = submoduleSrc "tools";
|
||||||
|
nativeBuildInputs = cargoDeps ++ [ pkgs.tesseract ];
|
||||||
|
|
||||||
|
buildScript = ''
|
||||||
|
cd backend
|
||||||
|
echo "=== Building tools-gateway ==="
|
||||||
|
cargo build --release --features tesseract --bin tools-gateway 2>&1
|
||||||
|
'';
|
||||||
|
|
||||||
|
installScript = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp target/release/tools-gateway $out/bin/tools-gateway
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
tools-workers = mkApp {
|
||||||
|
name = "tools-workers-0.1.0";
|
||||||
|
src = submoduleSrc "tools";
|
||||||
|
nativeBuildInputs = cargoDeps ++ [ pkgs.tesseract pkgs.leptonica ];
|
||||||
|
|
||||||
|
buildScript = ''
|
||||||
|
cd backend
|
||||||
|
echo "=== Building tools-workers ==="
|
||||||
|
cargo build --release --features tesseract --bin tools-workers 2>&1
|
||||||
|
'';
|
||||||
|
installScript = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp target/release/tools-workers $out/bin/tools-workers
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
tools-frontend = mkApp {
|
||||||
|
name = "tools-frontend-0.1.0";
|
||||||
|
src = submoduleSrc "tools";
|
||||||
|
nativeBuildInputs = with pkgs; [ bun ];
|
||||||
|
|
||||||
|
buildScript = ''
|
||||||
|
cd frontend
|
||||||
|
echo "=== Installing dependencies ==="
|
||||||
|
bun install 2>&1
|
||||||
|
echo "=== Building Next.js ==="
|
||||||
|
bun run build 2>&1
|
||||||
|
'';
|
||||||
|
|
||||||
|
installScript = ''
|
||||||
|
mkdir -p $out/share/tools-frontend $out/bin
|
||||||
|
cp -r .next $out/share/tools-frontend/
|
||||||
|
cp -r public $out/share/tools-frontend/ 2>/dev/null || true
|
||||||
|
cp package.json $out/share/tools-frontend/
|
||||||
|
cp -r node_modules $out/share/tools-frontend/
|
||||||
|
cat > $out/bin/tools-frontend << WRAPPER
|
||||||
|
#!${pkgs.runtimeShell}
|
||||||
|
exec ${pkgs.bun}/bin/bun run --cwd $out/share/tools-frontend start
|
||||||
|
WRAPPER
|
||||||
|
chmod +x $out/bin/tools-frontend
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
llm-api = mkApp {
|
||||||
|
name = "llm-api-0.1.0";
|
||||||
|
src = submoduleSrc "llm-api";
|
||||||
|
nativeBuildInputs = cargoDeps ++ [ pkgs.cmake pkgs.gcc ];
|
||||||
|
|
||||||
|
buildScript = ''
|
||||||
|
echo "=== Building llm-api ==="
|
||||||
|
cargo build --release 2>&1
|
||||||
|
'';
|
||||||
|
|
||||||
|
installScript = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp target/release/llm-api $out/bin/llm-api
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages = {
|
||||||
|
inherit hub scraper tools-gateway tools-workers tools-frontend llm-api;
|
||||||
|
default = hub;
|
||||||
|
};
|
||||||
|
|
||||||
|
apps.hub = {
|
||||||
|
type = "app";
|
||||||
|
program = "${hub}/bin/hub";
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [ nodejs-slim_22 bun pnpm rustc cargo ];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
hub:
|
hub:
|
||||||
container_name: hub
|
container_name: hub
|
||||||
image: ghcr.io/asepharyana/asepharyana-hub/hub:sha-22e8a87
|
image: ghcr.io/asepharyana/asepharyana-hub/hub:sha-ac25a51
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
app-shared-net:
|
app-shared-net:
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
llm-api:
|
llm-api:
|
||||||
container_name: llm-api
|
container_name: llm-api
|
||||||
image: ghcr.io/asepharyana/asepharyana-hub/llm-api:sha-e953c3c
|
image: ghcr.io/asepharyana/asepharyana-hub/llm-api:sha-43f0df4
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
app-shared-net:
|
app-shared-net:
|
||||||
@@ -10,7 +10,7 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- ../../.env
|
- ../../.env
|
||||||
environment:
|
environment:
|
||||||
- MODEL_PATH=/models/MiniCPM-V-4.6-Q4_K_M.gguf
|
- MODEL_PATH=/models/MiniCPM5-1B-Claude-Opus-Fable5-V2-Thinking-Q8_0.gguf
|
||||||
- API_KEY=${LLM_API_KEY:-}
|
- API_KEY=${LLM_API_KEY:-}
|
||||||
volumes:
|
volumes:
|
||||||
- /root/models/gguf:/models:ro
|
- /root/models/gguf:/models:ro
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
scraper-api:
|
scraper-api:
|
||||||
container_name: scraper-api
|
container_name: scraper-api
|
||||||
image: ghcr.io/asepharyana/asepharyana-hub/scraper-api:sha-e953c3c
|
image: ghcr.io/asepharyana/asepharyana-hub/scraper-api:sha-2459541
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
nats:
|
nats:
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
tools:
|
tools:
|
||||||
container_name: tools
|
container_name: tools
|
||||||
image: ghcr.io/asepharyana/asepharyana-hub/tools:sha-e953c3c
|
image: ghcr.io/asepharyana/asepharyana-hub/tools:sha-ac25a51
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
app-shared-net:
|
app-shared-net:
|
||||||
@@ -13,6 +13,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- '80:80'
|
- '80:80'
|
||||||
- '443:443'
|
- '443:443'
|
||||||
|
- '443:443/udp'
|
||||||
networks:
|
networks:
|
||||||
- app-shared-net
|
- app-shared-net
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
@@ -45,6 +46,13 @@ services:
|
|||||||
- '--entryPoints.websecure.transport.lifeCycle.requestAcceptGraceTimeout=15s'
|
- '--entryPoints.websecure.transport.lifeCycle.requestAcceptGraceTimeout=15s'
|
||||||
- '--entryPoints.websecure.transport.lifeCycle.graceTimeOut=10s'
|
- '--entryPoints.websecure.transport.lifeCycle.graceTimeOut=10s'
|
||||||
- '--entryPoints.websecure.address=:443'
|
- '--entryPoints.websecure.address=:443'
|
||||||
|
- '--entryPoints.websecure.http3=true'
|
||||||
|
# ── Response speed tuning ──
|
||||||
|
- '--serversTransport.maxIdleConnsPerHost=100'
|
||||||
|
- '--serversTransport.forwardingTimeouts.dialTimeout=3s'
|
||||||
|
- '--serversTransport.forwardingTimeouts.idleConnTimeout=180s'
|
||||||
|
- '--global.checkNewVersion=false'
|
||||||
|
- '--global.sendAnonymousUsage=false'
|
||||||
- '--experimental.plugins.real-ip.moduleName=github.com/soulbalz/traefik-real-ip'
|
- '--experimental.plugins.real-ip.moduleName=github.com/soulbalz/traefik-real-ip'
|
||||||
- '--experimental.plugins.real-ip.version=v1.0.3'
|
- '--experimental.plugins.real-ip.version=v1.0.3'
|
||||||
- '--experimental.plugins.blockpath.moduleName=github.com/traefik/plugin-blockpath'
|
- '--experimental.plugins.blockpath.moduleName=github.com/traefik/plugin-blockpath'
|
||||||
@@ -68,7 +76,7 @@ services:
|
|||||||
- GOGC=200
|
- GOGC=200
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ${TRAEFIK_CONFIG_PATH:-/root/asepharyana-hub/infra/traefik/dynamic}:/etc/traefik/dynamic:ro
|
- ${TRAEFIK_CONFIG_PATH:-/home/code/asepharyana-hub/infra/traefik/dynamic}:/etc/traefik/dynamic:ro
|
||||||
- ${TRAEFIK_CERT_MY_ID_PEM:-/root/asepharyana.my.id.pem}:/etc/traefik/certs/asepharyana.my.id.pem:ro
|
- ${TRAEFIK_CERT_MY_ID_PEM:-/root/asepharyana.my.id.pem}:/etc/traefik/certs/asepharyana.my.id.pem:ro
|
||||||
- ${TRAEFIK_CERT_MY_ID_KEY:-/root/asepharyana.my.id.key}:/etc/traefik/certs/asepharyana.my.id.key:ro
|
- ${TRAEFIK_CERT_MY_ID_KEY:-/root/asepharyana.my.id.key}:/etc/traefik/certs/asepharyana.my.id.key:ro
|
||||||
- ${TRAEFIK_CERT_WEB_ID_PEM:-/root/asepharyana.web.id.pem}:/etc/traefik/certs/asepharyana.web.id.pem:ro
|
- ${TRAEFIK_CERT_WEB_ID_PEM:-/root/asepharyana.web.id.pem}:/etc/traefik/certs/asepharyana.web.id.pem:ro
|
||||||
|
|||||||
@@ -54,3 +54,7 @@ service:
|
|||||||
receivers: [otlp]
|
receivers: [otlp]
|
||||||
processors: [memory_limiter, batch, attributes]
|
processors: [memory_limiter, batch, attributes]
|
||||||
exporters: [prometheus, debug]
|
exporters: [prometheus, debug]
|
||||||
|
logs:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [memory_limiter, batch, attributes]
|
||||||
|
exporters: [debug]
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ http:
|
|||||||
- websecure
|
- websecure
|
||||||
tls: {}
|
tls: {}
|
||||||
middlewares:
|
middlewares:
|
||||||
- common-chain@file
|
- llm-chain@file
|
||||||
service: llm-api-service
|
service: llm-api-service
|
||||||
|
|
||||||
# ── Tools (Document Scanner & Media Processing) ──
|
# ── Tools (Document Scanner & Media Processing) ──
|
||||||
@@ -50,28 +50,178 @@ http:
|
|||||||
- common-chain@file
|
- common-chain@file
|
||||||
service: jaeger-service
|
service: jaeger-service
|
||||||
|
|
||||||
|
# ── 9Router (AI routing gateway) ──
|
||||||
|
9router:
|
||||||
|
rule: 'Host(`9router.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: 9router-service
|
||||||
|
|
||||||
|
# ── PR-Agent (GitHub App webhook) ──
|
||||||
|
pr-agent:
|
||||||
|
rule: 'Host(`pr-agent.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: pr-agent-service
|
||||||
|
|
||||||
|
# ── LIDM Frontend ──
|
||||||
|
lidm-frontend:
|
||||||
|
rule: 'Host(`lidm.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: lidm-frontend-service
|
||||||
|
|
||||||
|
# ── LIDM Backend API ──
|
||||||
|
lidm-backend:
|
||||||
|
rule: 'Host(`lidm-api.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: lidm-backend-service
|
||||||
|
|
||||||
|
# ── ZeaVis Edu Frontend ──
|
||||||
|
zeavisedu:
|
||||||
|
rule: 'Host(`zeavisedu.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: zeavisedu-service
|
||||||
|
|
||||||
|
# ── ZeaVis Edu API ──
|
||||||
|
api-zeavisedu:
|
||||||
|
rule: 'Host(`api-zeavisedu.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: api-zeavisedu-service
|
||||||
|
|
||||||
|
# ── ZeaVis Edu ML Service ──
|
||||||
|
ml-zeavisedu:
|
||||||
|
rule: 'Host(`ml-zeavisedu.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: ml-zeavisedu-service
|
||||||
|
|
||||||
|
# ── Hermes Dashboard ──
|
||||||
|
hermes-dashboard:
|
||||||
|
rule: 'Host(`dashboard.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: hermes-dashboard-service
|
||||||
|
|
||||||
|
# ── GMW Discord Automod Dashboard (Nix: gmw-proxy on 8080) ──
|
||||||
|
gmw:
|
||||||
|
rule: 'Host(`imphnen.asepharyana.my.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- common-chain@file
|
||||||
|
service: gmw-service
|
||||||
|
|
||||||
|
# ── TeleUploader (S3 to Telegram Bridge, Nix: bun on 3000) ──
|
||||||
|
teleuploader:
|
||||||
|
rule: 'Host(`upload.asepharyana.my.id`) || Host(`upload.asepharyana.web.id`)'
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls: {}
|
||||||
|
middlewares:
|
||||||
|
- upload-chain@file
|
||||||
|
service: teleuploader-service
|
||||||
|
|
||||||
services:
|
services:
|
||||||
hub-service:
|
hub-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: 'http://hub:3000'
|
- url: 'http://host.docker.internal:3099'
|
||||||
|
|
||||||
scraper-service:
|
scraper-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: 'http://scraper-api:4091'
|
- url: 'http://host.docker.internal:4091'
|
||||||
|
|
||||||
tools-service:
|
tools-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: 'http://tools:3000'
|
- url: 'http://host.docker.internal:3500'
|
||||||
|
|
||||||
llm-api-service:
|
llm-api-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: 'http://llm-api:8080'
|
- url: 'http://host.docker.internal:8082'
|
||||||
|
|
||||||
jaeger-service:
|
jaeger-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: 'http://jaeger:16686'
|
- url: 'http://jaeger:16686'
|
||||||
|
|
||||||
|
9router-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:20128'
|
||||||
|
|
||||||
|
pr-agent-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:3002'
|
||||||
|
|
||||||
|
lidm-frontend-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:3100'
|
||||||
|
|
||||||
|
lidm-backend-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:3101'
|
||||||
|
|
||||||
|
zeavisedu-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:8088'
|
||||||
|
|
||||||
|
api-zeavisedu-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:3200'
|
||||||
|
|
||||||
|
ml-zeavisedu-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:8200'
|
||||||
|
|
||||||
|
hermes-dashboard-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:9119'
|
||||||
|
|
||||||
|
gmw-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:8080'
|
||||||
|
|
||||||
|
teleuploader-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: 'http://host.docker.internal:3000'
|
||||||
|
|||||||
@@ -61,6 +61,14 @@ http:
|
|||||||
- "^/wp-login\\.php"
|
- "^/wp-login\\.php"
|
||||||
- "^/config\\.php"
|
- "^/config\\.php"
|
||||||
|
|
||||||
|
# ── LLM Stream Chain (no buffer/compress — SSE needs real-time) ──
|
||||||
|
llm-chain:
|
||||||
|
chain:
|
||||||
|
middlewares:
|
||||||
|
- secure-headers
|
||||||
|
- retry
|
||||||
|
- rate-limit
|
||||||
|
|
||||||
# ── Common Chain ──
|
# ── Common Chain ──
|
||||||
common-chain:
|
common-chain:
|
||||||
chain:
|
chain:
|
||||||
@@ -70,3 +78,23 @@ http:
|
|||||||
- retry
|
- retry
|
||||||
- rate-limit
|
- rate-limit
|
||||||
- buffer
|
- buffer
|
||||||
|
|
||||||
|
# ── TeleUploader Chain (2GB body buffer — large file uploads) ──
|
||||||
|
upload-buffer:
|
||||||
|
buffering:
|
||||||
|
maxRequestBodyBytes: 2147483648
|
||||||
|
maxResponseBodyBytes: 2147483648
|
||||||
|
memRequestBodyBytes: 1048576
|
||||||
|
memResponseBodyBytes: 1048576
|
||||||
|
upload-rate-limit:
|
||||||
|
rateLimit:
|
||||||
|
average: 300
|
||||||
|
burst: 100
|
||||||
|
period: 1m
|
||||||
|
upload-chain:
|
||||||
|
chain:
|
||||||
|
middlewares:
|
||||||
|
- secure-headers
|
||||||
|
- retry
|
||||||
|
- upload-rate-limit
|
||||||
|
- upload-buffer
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ── Nix Deploy — VPS-side script ──
|
||||||
|
# Runs after `nix copy --to ssh://VPS ./result` from CI
|
||||||
|
# Usage: sudo ./deploy.sh <service-name>
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SERVICE="$1"
|
||||||
|
PROFILE="/nix/var/nix/profiles/${SERVICE}"
|
||||||
|
|
||||||
|
# Find the latest store path for this service
|
||||||
|
LATEST=$(ls -1d /nix/store/*-"${SERVICE}"-* 2>/dev/null | tail -1)
|
||||||
|
if [ -z "$LATEST" ]; then
|
||||||
|
echo "ERROR: No store path found for ${SERVICE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update profile
|
||||||
|
/nix/var/nix/profiles/default/bin/nix-env --profile "$PROFILE" --set "$LATEST"
|
||||||
|
|
||||||
|
# Restart service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now "${SERVICE}" 2>/dev/null || systemctl restart "${SERVICE}"
|
||||||
|
|
||||||
|
echo "Deployed ${SERVICE}: ${LATEST}"
|
||||||
|
systemctl is-active "${SERVICE}"
|
||||||
|
|
||||||
|
# Collect garbage (safe: only removes unreachable paths)
|
||||||
|
# nix-collect-garbage -d 2>/dev/null || true
|
||||||
Reference in New Issue
Block a user