Author SHA1 Message Date
asepharyana 5319971bfd fix: update llm-api MODEL_PATH for new MiniCPM5 GGUF 2026-07-26 15:39:58 +07:00
19 changed files with 135 additions and 778 deletions
@@ -1,20 +0,0 @@
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
-84
View File
@@ -1,84 +0,0 @@
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"
-1
View File
@@ -63,4 +63,3 @@ docs/todo.md
**/vendor/ **/vendor/
.codegraph/ .codegraph/
result
+121 -172
View File
@@ -1,232 +1,181 @@
# Architecture # Asepharyana Hub
## Hub Repository Structure Overview Hub repo untuk ekosistem portfolio dan layanan pendukung milik Asep Haryana Saputra.
Aplikasi dipisah sebagai submodule agar frontend, API, dan service pendukung bisa dikembangkan serta di-deploy secara independen.
```diff ## Services
asepharyana-hub/
├── apps/ # Application services (Git submodules)
│ └── scraper/ # Web scraper service
├── docs/ # Documentation
│ ├── adr/ # Architecture Decision Records
│ ├── add-new-app.md # Guide for adding new services
│ └── superpowers/ # Project capabilities tracking
├── infra/ # Infrastructure as code
│ ├── compose/ # Docker Compose files per service
│ ├── config/ # Infrastructure configuration
│ ├── docker/ # Dockerfiles per service
│ └── traefik/ # Traefik reverse proxy config
│ └── dynamic/ # Dynamic routing rules (YAML)
├── scripts/ # Utility scripts
│ ├── git-hooks/ # Git hook scripts
│ ├── cleanup-ghcr.sh # GHCR image cleanup
│ └── update-deps.sh # Dependency update helper
├── .github/workflows/ # CI/CD pipelines
├── eslint.config.mjs # Root ESLint config
├── package.json # Root formatting/lint helper scripts
└── .prettierrc # Prettier formatting rules
```
## Technology Stack | Service | Path | Notes |
| :------ | :------------- | :----------------------------- |
| Scraper | `apps/scraper` | Web scraper service + Dapr SDK |
| NATS | — | Message broker + JetStream |
| Dapr | — | Sidecar runtime (per service) |
### Services ## Infrastructure
|| Service | Path | Language/Runtime | Framework | Database | Key Libraries | File compose berada di `infra/compose/`:
||---------|----------------|------------------|-----------|----------|---------------|
|| **scraper** | `apps/scraper` | — | — | — | — |
### Infrastructure - `traefik.yml`: reverse proxy Traefik untuk semua layanan.
- `shared.yml`: Redis (cache + Dapr state store).
- `nats.yml`: NATS message broker dengan JetStream persistence.
- `dapr.yml`: Dapr placement service untuk koordinasi sidecar.
- `scraper.yml`: manifest deploy per service (app + Dapr sidecar).
|| Component | Technology | Purpose | Dockerfile per service berada di `infra/docker/`.
||---------------------|-------------------------|------------------------------------------------------------------|
|| 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 ## Docker Image Builds
### Traefik Reverse Proxy Build image via Dockerfile:
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
docker compose -f traefik.yml -f shared.yml -f scraper.yml up -d docker build -f infra/docker/scraper.Dockerfile -t scraper-api:latest .
``` ```
### Tailscale Networking Tag and push:
### 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
ip route add 100.64.0.0/10 dev tailscale0 table main SHORT_SHA=$(git rev-parse --short HEAD)
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
``` ```
This is managed by `/etc/systemd/system/tailscale-routes.service` on the `orangevps` VPS. ## Local Development
### Data Flow ### 1) Jalankan dependency bersama
### Request Flow (Production) ```bash
docker compose -f infra/compose/shared.yml up -d
```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 ### 2) Jalankan service yang dibutuhkan
```mermaid Refer to each service's own documentation for development setup.
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] ## API Docs and Monitoring
F --> G[Update Compose tags]
G --> D
D --> H[SSH into VPS] Refer to each service's own documentation for API docs.
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`). Container-to-Tailscale connectivity requires a systemd service that adds a route to the main routing table: Semua VPS terhubung via **Tailscale**. Setiap VPS punya IP Tailscale dan service berkomunikasi antar VPS melalui Tailscale network (`100.64.0.0/10`).
| 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
``` ```
This is managed by `/etc/systemd/system/tailscale-routes.service` on the `orangevps` VPS. #### Environment Variables
### 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:***@100.121.180.82:6432/dbname DATABASE_URL=postgres://user:pass@100.121.180.82:5432/dbname
# Redis di imrnes # Redis di imrnes
REDIS_URL=redis://100.121.180.82:6379 REDIS_URL=redis://100.121.180.82:6379
``` ```
## Submodule Strategy #### Persistent Systemd Service
Each application lives in its own Git repository and is imported as a submodule into `apps/`. This approach: File: `/etc/systemd/system/tailscale-routes.service`
- **Enables independent development** — each service can be developed, tested, and versioned separately ```ini
- **Pins exact commits** — the super-repository tracks exact submodule SHAs, enabling reproducible deployments [Unit]
- **Supports `repository_dispatch`** — when a submodule receives a push, it can trigger the super-repository to build and deploy only that service Description=Add Tailscale routes to main routing table
After=tailscaled.service
Requires=tailscaled.service
### Submodule Lifecycle [Service]
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
1. Developer pushes to a submodule (e.g., `apps/scraper`) [Install]
2. Submodule's GitHub Action dispatches `repository_dispatch` to the super-repo with the service name and new SHA WantedBy=multi-user.target
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
### Updating Submodules Install & enable:
```bash ```bash
# Update a single submodule to latest sudo tee /etc/systemd/system/tailscale-routes.service > /dev/null << 'EOF'
cd apps/scraper [Unit]
git checkout main Description=Add Tailscale routes to main routing table
git pull After=tailscaled.service
cd ../.. Requires=tailscaled.service
git add apps/scraper
git commit -m "chore(scraper): update submodule to latest" [Service]
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
Generated
-61
View File
@@ -1,61 +0,0 @@
{
"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
}
-208
View File
@@ -1,208 +0,0 @@
{
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-ac25a51 image: ghcr.io/asepharyana/asepharyana-hub/hub:sha-22e8a87
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-43f0df4 image: ghcr.io/asepharyana/asepharyana-hub/llm-api:sha-e953c3c
restart: always restart: always
networks: networks:
app-shared-net: app-shared-net:
@@ -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-2459541 image: ghcr.io/asepharyana/asepharyana-hub/scraper-api:sha-e953c3c
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-ac25a51 image: ghcr.io/asepharyana/asepharyana-hub/tools:sha-e953c3c
restart: always restart: always
networks: networks:
app-shared-net: app-shared-net:
+1 -9
View File
@@ -13,7 +13,6 @@ 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:
@@ -46,13 +45,6 @@ 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'
@@ -76,7 +68,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:-/home/code/asepharyana-hub/infra/traefik/dynamic}:/etc/traefik/dynamic:ro - ${TRAEFIK_CONFIG_PATH:-/root/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
-4
View File
@@ -54,7 +54,3 @@ 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]
+5 -155
View File
@@ -27,7 +27,7 @@ http:
- websecure - websecure
tls: {} tls: {}
middlewares: middlewares:
- llm-chain@file - common-chain@file
service: llm-api-service service: llm-api-service
# ── Tools (Document Scanner & Media Processing) ── # ── Tools (Document Scanner & Media Processing) ──
@@ -50,178 +50,28 @@ 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://host.docker.internal:3099' - url: 'http://hub:3000'
scraper-service: scraper-service:
loadBalancer: loadBalancer:
servers: servers:
- url: 'http://host.docker.internal:4091' - url: 'http://scraper-api:4091'
tools-service: tools-service:
loadBalancer: loadBalancer:
servers: servers:
- url: 'http://host.docker.internal:3500' - url: 'http://tools:3000'
llm-api-service: llm-api-service:
loadBalancer: loadBalancer:
servers: servers:
- url: 'http://host.docker.internal:8082' - url: 'http://llm-api:8080'
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'
-28
View File
@@ -61,14 +61,6 @@ 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:
@@ -78,23 +70,3 @@ 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
-28
View File
@@ -1,28 +0,0 @@
#!/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