323 Commits
Author SHA1 Message Date
asepharyanaandClaude Opus 5 21743cedfa chore(infra): remove llm-api service (retired from prod)
Deploy Infra Config / deploy-infra (push) Canceled after 0s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 18:53:29 +07:00
MythEclipse 63c3b87f39 fix(hamc): hermyhq port 4023→4024, add hermyhq-render worker unit
Deploy Infra Config / deploy-infra (push) Canceled after 0s
- hermyhq.service: PORT 4024 (4023 occupied by pr-agent-bun)
- Caddyfile.prod: hamc.* proxy → 4024
- hermyhq-render.service: new systemd unit for shorts render worker
  (Remotion + edge-tts, runs /opt/hermyhq-renderer)
2026-09-22 20:16:33 +07:00
asepharyana c5c0a961b0 chore(caddy): route pr-agent to Bun server (4023)
Deploy Infra Config / deploy-infra (push) Canceled after 0s
Cut-over pr-agent.asepharyana.my.id from the Python pr_agent server (4002)
to the Bun/TypeScript re-implementation (4023, pr-agent-bun.service).
Verified: webhook -> review -> LLM claude-opus-5 -> persistent comment
published (PR #19 nextjs-template).
2026-09-21 13:38:04 +07:00
asepharyana c25c645818 feat(dev): add dev.asepharyana.my.id Caddy route → 127.0.0.1:3000
Deploy Infra Config / deploy-infra (push) Canceled after 0s
Ephemeral dev preview of the Next.js template — no systemd service; dev
server runs on-demand (bun run dev), Caddy routes :3000, 502s when stopped.
2026-09-20 21:48:22 +07:00
MythEclipse 9e6078af9b feat(infra): add hamc.asepharyana.my.id (hermyhq) Caddy routing + systemd unit
Deploy Infra Config / deploy-infra (push) Canceled after 0s
- Caddy: hamc.asepharyana.my.id -> 127.0.0.1:4023 (import proxy)
- systemd: hermyhq.service (Next.js 16 standalone, profile /nix/var/nix/profiles/hermyhq, env /etc/hermyhq/hermyhq.env)
2026-09-20 15:51:34 +07:00
asepharyana bda852f287 feat(infra): add mcpedia.asepharyana.my.id Caddy block -> local mcpedia (wiki alias)
Deploy Infra Config / deploy-infra (push) Canceled after 0s
2026-09-02 12:31:18 +07:00
asepharyana 754bc2876f fix(caddy): serve gmw.asepharyana.my.id → :4009 (was 525)
Deploy Infra Config / deploy-infra (push) Canceled after 0s
GMW dashboard SSL was broken (Cloudflare 525) because gmw was not in
the Caddyfile — no Let's Encrypt cert / TLS origin for the hostname.
Add gmw.asepharyana.my.id block proxying to gmw-proxy nginx (4009),
same as imphnen. Public dashboard now reachable (200, valid cert).
2026-09-01 20:19:00 +07:00
asepharyana b37daa52b0 chore(infra): remove tools app — Caddy route, README, add-new-app docs
Deploy Infra Config / deploy-infra (push) Canceled after 0s
Project tools dihapus (2026-08-28): GitHub repo + local source + VPS deploy
(3 systemd units + Nix profiles) sudah dihapus. Caddy route
tools.asepharyana.my.id removed; README + docs updated.
ADR 0003 dibiarkan sebagai catatan historis rename.
2026-08-27 21:17:05 +07:00
asepharyana 56cee21e66 ci(infra): sync full infra config (caddy + systemd drop-ins + prometheus), rename workflow to infra-deploy 2026-08-27 20:43:06 +07:00
asepharyana 6ac562a869 ci: validate Caddyfile with real caddy binary instead of naive brace count
Deploy Caddy Config / deploy-caddy (push) Canceled after 0s
2026-08-27 19:50:24 +07:00
Asep Haryana 4c6c368764 merge: integrate remote dependabot/submodule bump history into infra-only repo 2026-08-27 19:44:55 +07:00
Asep Haryana 0194a865b7 refactor(infra): repurpose asepharyana-hub into infra-only reverse-proxy repo
- Remove app submodules (hub/scraper/tools/llm-api/plugins) — apps are now
  standalone repos with their own flake.nix + deploy.yml CI
- Delete monorepo CI: nix-build.yml matrix, update-submodule.yml, lint, security,
  flakehub-publish — replaced by infra-only caddy-deploy.yml
- Sync Caddyfile.prod with live /etc/caddy/Caddyfile (add wiki. + mcp. blocks)
- Prune legacy Docker/Traefik/Dapr/NATS/otel + scripts/root tooling
- Docs: rename ADR 0001 superseded, add ADR 0003 (repo rename + split CI),
  update add-new-app, infra README, troubleshooting
2026-08-27 19:44:10 +07:00
dependabot[bot]andAsep Hariyana 1f490cf9c2 chore(deps-dev): bump @biomejs/biome from 2.5.3 to 2.5.10 in the development group across 1 directory (#12)
* chore(deps-dev): bump @biomejs/biome

Bumps the development group with 1 update in the / directory: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome).


Updates `@biomejs/biome` from 2.5.3 to 2.5.10
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.10/packages/@biomejs/biome)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: sync lockfile for dependabot update

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Asep Hariyana <asepharyana.id@gmail.com>
2026-08-26 09:12:28 +07:00
monrepo-bot 851e82d625 chore: update scraper-api to 7ff47a79bcb4 2026-08-26 01:55:19 +00:00
monrepo-bot 8fe5a478b5 chore: update hub to 7b00c9deb2bc 2026-08-26 01:54:35 +00:00
asepharyana a2e65e3980 chore: update dependabot-auto-merge.yml 2026-08-26 08:53:32 +07:00
asepharyana 1bbba46b9a chore: update dependabot.yml 2026-08-26 08:53:30 +07:00
monrepo-bot 80ea5f0b2e chore: update hub to d3ae61b0c556 2026-08-25 13:33:16 +00:00
monrepo-bot 6cc583d3b9 chore: update scraper-api to c709c84a4d42 2026-08-25 13:33:10 +00:00
asepharyana 912fd6864f feat: add dependabot auto-merge workflow 2026-08-25 20:32:53 +07:00
monrepo-bot eba29a2fa1 chore: update hub to eb7b8a12fdd4 2026-08-25 13:22:06 +00:00
monrepo-bot 76df15616e chore: update hub to fab67538e15b 2026-08-25 11:20:26 +00:00
monrepo-bot 07a81df64d chore: update hub to 0de569cff475 2026-08-25 11:18:01 +00:00
monrepo-bot bbbb1d2cee chore: update scraper-api to 55cfd513e3f8 2026-08-25 11:17:49 +00:00
monrepo-bot f9d8445443 chore: update llm-api to 1916ebe40bcb 2026-08-25 11:17:38 +00:00
monrepo-bot 4f3442f97b chore: update tools to b35b00376dd1 2026-08-25 11:17:18 +00:00
asepharyana 93cf1ffb9b feat: update dependabot config 2026-08-25 18:16:34 +07:00
monrepo-bot 499721c5e3 chore: update hub to ac5ca405dda4 2026-08-25 11:16:15 +00:00
asepharyana 09c5311568 feat: update dependabot config 2026-08-25 18:12:46 +07:00
monrepo-bot e74a303b9d chore: update scraper-api to a23698a6c4ef 2026-08-25 11:08:57 +00:00
monrepo-bot 0bcd6582f4 chore: update hub to a0042942412a 2026-08-25 00:11:51 +00:00
monrepo-bot a38a99b6f4 chore: update hub to bc7279646715 2026-08-25 00:11:43 +00:00
monrepo-bot 53cc225bff chore: update hub to 90267abb9220 2026-08-25 00:11:35 +00:00
monrepo-bot a20b0be8da chore: update hub to 7e44d677aca4 2026-08-24 00:23:43 +00:00
monrepo-bot 76ccf5488b chore: update hub to d5cd4ece013b 2026-08-24 00:17:46 +00:00
monrepo-bot 29271ccc05 chore: update hub to e5fb73f61eba 2026-08-21 00:11:29 +00:00
monrepo-bot 6a1d199443 chore: update hub to 4a6b498879fa 2026-08-20 00:11:52 +00:00
monrepo-bot eb19385ab4 chore: update hub to ed9fa16b5760 2026-08-17 00:18:19 +00:00
monrepo-bot a1c0ef25ef chore: update hub to 1640230c66d3 2026-08-17 00:18:09 +00:00
monrepo-bot 609da6b6bc chore: update hub to c891439857df 2026-08-14 00:14:22 +00:00
monrepo-bot 402499d109 chore: update hub to bfbfc175ca82 2026-08-13 00:13:26 +00:00
monrepo-bot 1389d5faa3 chore: update hub to b30e6936b1df 2026-08-11 00:12:50 +00:00
monrepo-bot 59e6c5c6a1 chore: update hub to 9ea002a7a10d 2026-08-10 00:24:27 +00:00
monrepo-bot 4f8a975337 chore: update hub to e67db071bb0f 2026-08-10 00:24:15 +00:00
monrepo-bot c251867f05 chore: update hub to 17a785c9d277 2026-08-10 00:18:32 +00:00
monrepo-bot b06cddca4a chore: update hub to 915d8482947a 2026-08-10 00:18:27 +00:00
monrepo-bot 19aface599 chore: update hub to a5bf846e52fd 2026-08-10 00:18:19 +00:00
monrepo-bot 528a109d53 chore: update hub to eb80d4a0fd73 2026-08-07 00:10:49 +00:00
asepharyana 97b635e8c6 firewall: whitelist TCPShield proxy ranges untuk port 25565 (Minecraft) 2026-08-06 20:42:39 +07:00
mytheclipsebotreview[bot] 99e438d9c5 Auto-merge PR #10
chore(deps): bump actions/checkout from 6 to 7 in the github-actions group
2026-08-06 13:28:09 +00:00
dependabot[bot] 8e850f05c4 chore(deps): bump actions/checkout in the github-actions group
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-06 13:16:00 +00:00
asepharyana 6c2a4fc9d2 fix(infra): sync Caddyfile.prod with live — add dashboard.asepharyana.my.id block
dashboard was proxied via wildcard but missing from live Caddy site
blocks -> Cloudflare 525. Live /etc/caddy/Caddyfile is now the
authoritative copy; repo reference updated to match byte-for-byte so
the drift cannot recur.
2026-08-05 09:44:26 +07:00
Asep Haryana 3e92f68d78 fix(infra): revert imphnen block ke gmw-proxy 4009 (dimentorin gak dipublish via domain ini) 2026-08-04 23:12:02 +07:00
Asep Haryana 2050129062 fix(infra): imphnen site block — SPA :5173 + API /v1 → :4099 (was 525: no block on live caddy) 2026-08-04 22:51:09 +07:00
asepharyana a6148753ca fix(infra): 9router Caddy timeouts — false 504 on slow LLM TTFT
9router combo models (deepseek-v4-flash-free fallback) have TTFT up to
30-40s. Default (proxy) response_header_timeout 30s / read 60s caused
false-positive 504 'timeout awaiting response headers' even while 9router
was processing. Give the 9router route dedicated long timeouts
(response_header 120s, read/write 300s). Syncs /etc/caddy/Caddyfile.
2026-08-04 22:02:09 +07:00
asepharyana d02989f1a7 ci: add Nix GC cleanup job on VPS after deploy 2026-08-04 13:58:10 +07:00
monrepo-bot 6e317eb05a chore: update hub to 55310768a5ac 2026-08-04 02:39:29 +00:00
asepharyana 4e114edb89 chore: update settings.json with new hooks and commands; update .gitignore to include .serena/ directory 2026-08-03 16:49:28 +07:00
aseph 49123c08b3 ci: use free GHA Nix cache (disable FlakeHub cache, not subscribed) 2026-08-03 16:44:32 +07:00
asepharyana 539b5b8320 ci: enable FlakeHub Cache (id-token: write + use-flakehub) 2026-08-03 16:24:48 +07:00
monrepo-bot 39df52aab7 chore: update hub to ad6b1847c563 2026-08-03 09:23:16 +00:00
Asep Haryana b326911478 chore(infra): bump hub flake rev to a90d0c4 (error rate fix) 2026-08-03 13:07:38 +07:00
monrepo-bot 8242325ffd chore: update hub to a90d0c43336a 2026-08-03 06:06:50 +00:00
Asep Haryana c075cf282b fix(infra): correct hub flake rev to 62966fd (full sha) 2026-08-03 12:48:14 +07:00
Asep Haryana 93936efd40 chore(infra): bump hub flake rev to 62966fd (LLM dashboard card) 2026-08-03 12:46:11 +07:00
monrepo-bot 5f66cfc12b chore: update hub to 62966fd7cf45 2026-08-03 05:45:45 +00:00
Asep Haryana cc8c3f1bf3 chore(infra): add llm-api to prometheus targets.yml (now exposes /metrics) 2026-08-03 11:56:35 +07:00
Asep Haryana 0c36007da4 chore(infra): bump llm-api flake rev to 5f7ead5 2026-08-03 11:46:08 +07:00
monrepo-bot b7d02647d9 chore: update llm-api to 5f7ead550308 2026-08-03 04:45:04 +00:00
Asep HaryanaandClaude Opus 5 ade2798ded chore(infra): bump llm-api flake rev to 7cec411
Fixes streaming reasoning/content split (robust boundary detection,
flush un-tagged output) and renders reasoning in the chat UI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:04:18 +07:00
monrepo-bot ef14ca431f chore: update llm-api to 7cec411cba7c 2026-08-03 04:04:04 +00:00
Asep HaryanaandClaude Opus 5 f681e624b3 chore(infra): bump llm-api flake rev to b636496
Deploys the generation-flow refactor (unified generate, streaming fixes,
tool-calling, strict model validation, AddBos::Never).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 09:50:23 +07:00
Asep HaryanaandClaude Opus 5 fdbdcbc1ec fix(ci): trigger nix deploy on every push to main
GitHub path filters do not match submodule gitlink changes, so the
`paths: apps/**` filter meant a submodule pointer update never triggered
the deploy. Drop the filter so any push to main deploys (matches the
documented "Push to main -> nix build -> systemctl restart").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 09:35:56 +07:00
Asep HaryanaandClaude Opus 5 b7b60e9125 chore(ci): remove legacy Docker workflows (moved to Nix)
Docker was decommissioned 2026-08-02 but docker-build-push.yml and
deploy-docker.yml were left behind. They still listened to
repository_dispatch: [submodule-updated], so every app push queued a
redundant Docker build alongside the intended Nix deploy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 09:13:38 +07:00
monrepo-bot 722709f6fb chore: update llm-api to b63649649758 2026-08-03 01:58:50 +00:00
Asep Haryana d20643fbbd fix(infra): ship next.config.ts into Nix store for hub
next start re-reads next.config.ts at runtime; the OLD store had only
.next/public/package.json/node_modules, so images.maximumDiskCacheSize
fell back to defaults (undefined) and the image-optimizer disk LRU did
mkdir(<store>/.next/cache/images) -> EACCES on every optimized image.
Also now that next.config.ts sets maximumDiskCacheSize:0 +
isrFlushToDisk:false, shipping the file makes runtime match the baked
build config, eliminating the EACCES spike.
2026-08-03 04:33:46 +07:00
Asep Haryana 7da1727603 fix(firewall): accept IPv6 MLD/ND multicast (ff02::1, ff02::2, ff02::fb)
Neighbor multicast to ff02::1 (MLDv2 reports from other hosts) was
hitting the LOG+DROP tail — ~1800 FW6-DROP lines/6h in journald,
i.e. ~5/min of pure log spam from ICMPv6 layer-2 discovery. Accept
link-local multicast ranges before the logging rule.
2026-08-03 04:06:32 +07:00
Asep Haryana 9e9fd229d0 chore(infra): bump hub flake rev to 6829c7e (image disk cache fix) 2026-08-03 04:05:23 +07:00
Asep Haryana 40095f75f4 chore(infra): bump hub submodule to 6829c7efe2a735d249f5135bff4a0fc28411a295 (disable Next image disk cache) 2026-08-03 04:05:15 +07:00
asepharyana 3781a8882d chore(infra): scraper OTEL env drop-in (OTLP endpoint 127.0.0.1:4317) 2026-08-02 21:05:20 +07:00
asepharyana da2f4a2f2c chore(infra): prometheus file_sd targets for nix services (gmw-backend/api-metrics) 2026-08-02 21:05:12 +07:00
asepharyana ad22a39a9e chore(infra): use full sha for hub flake rev 2026-08-02 20:56:16 +07:00
asepharyana 2f969972a1 chore(infra): bump hub flake rev to f35edb0 (dashboard fix) 2026-08-02 20:55:55 +07:00
monrepo-bot 620f06fbe0 chore: update hub to f35edb00d726 2026-08-02 13:55:07 +00:00
asepharyana e6033c8b12 feat(infra): hardening firewall + sysctl + ssh
- deny-by-default iptables/ip6tables: public cuma 22/80/443/4013,
  sisanya localhost + tailscale CGNAT; log FW-DROP rate-limited
- persist via netfilter-persistent (rules.v4/v6) + /usr/local/bin/firewall.sh
- sysctl hardening: syncookies, rp_filter, redirect off, kptr/dmesg
  restrict, protected links, somaxconn, tcp retries tuner
- sshd: PasswordAuthentication no (fix 50-cloud-init.conf override),
  PermitRootLogin prohibit-password, MaxAuthTries 4, LoginGraceTime 30,
  X11Forwarding no
2026-08-02 20:22:49 +07:00
asepharyana 500af023de fix(infra): otel collector no self-loop trace export
- hapus otlp/jaeger exporter (jaeger mati sejak Docker dihapus) yang
  nunjuk ke 127.0.0.1:4317 (port receiver sendiri) -> loop trace tanpa
  henti, ~1.8jt baris log/hari di journal
- traces pipeline -> nop exporter, metrics -> prometheus
- logs pipeline dihapus (tidak ada konsumen)
2026-08-02 19:59:31 +07:00
Asep Haryana e280d196f9 chore: update llm-api to 344bc195fa95 2026-08-02 17:02:59 +07:00
Asep Haryana 4927338c98 docs: sync infra docs to Caddy+Nix 4000s (Traefik/Docker legacy) 2026-08-02 16:54:17 +07:00
monrepo-bot 54ee903647 chore: update llm-api to 344bc195fa95 2026-08-02 09:45:06 +00:00
Asep Haryana 35cbcdcc92 chore(infra): add tuned Caddyfile.prod reference 2026-08-02 16:37:05 +07:00
monrepo-bot 324fe08913 chore: update llm-api to 81c51772495c 2026-08-02 09:18:56 +00:00
Asep Haryana e171201b1c chore: sync port references and docs to 4000s infra 2026-08-02 16:16:40 +07:00
Asep Haryana ec1394bdc5 chore: bump tools submodule (ports 4007/4008) 2026-08-02 14:31:47 +07:00
asepharyana f0787b5f23 fix(nix): restrict flake to x86_64-linux (nixpkgs 26.11 dropped darwin) 2026-08-01 18:03:44 +07:00
asepharyana 0df6c35d5a ci: publish flake to FlakeHub (rolling) 2026-08-01 17:58:22 +07:00
Asep Haryana 5d21848e87 perf(infra): traefik response speed - HTTP/3 + backend conn pooling
- entryPoints.websecure.http3: enable QUIC (UDP 443, alt-svc h3)
- serversTransport maxIdleConnsPerHost 2->100: reuse backend keep-alive
- forwardingTimeouts.dialTimeout 30s->3s: fail fast on dead backends
- disable version-check/anonymous-usage network chatter
2026-08-01 13:12:50 +07:00
Asep Haryana b96548bffa fix(infra): raise upload-buffer response cap to 2GB
Chunked file streaming (TeleUploader) serves multi-part bodies up to
120MB+; 10MB maxResponseBodyBytes made every big download 500.
2026-08-01 12:52:34 +07:00
Asep Haryana e19fa76dc3 fix(infra): add teleuploader traefik router for upload hostnames
Restore upload.asepharyana.my.id/.web.id routing lost during Nix migration
(docker label router removed with container). Upload chain uses 2GB body
buffer + dedicated 300/100 rate limit, service targets bun on host:3000.
2026-08-01 12:34:22 +07:00
Asep Haryana c535ee7858 ci(otel): add logs pipeline to otel-collector (OTLP /v1/logs 404 fix) 2026-07-31 14:13:56 +07:00
Asep Haryana 80b5161630 ci(infra): traefik dynamic config path -> /home/code (drop /root dependency) 2026-07-31 14:02:07 +07:00
Asep Haryana 1ccc0355ac ci(traefik): add hermes dashboard route via file provider 2026-07-31 13:50:46 +07:00
asepharyana 12aed642a9 ci(traefik): add zeavis routes via file provider (Nix migration) 2026-07-31 12:42:14 +07:00
asepharyana f8e3dccb44 ci(traefik): lidm routes -> host ports 3100/3101 (3000/3001 occupied by teleuploader+gmw) 2026-07-31 11:37:13 +07:00
asepharyana 1811c6adc2 ci(traefik): add lidm frontend+backend routes via file provider (Nix migration) 2026-07-31 11:29:47 +07:00
dependabot[bot] ceeac5c02f chore(deps): bump the github-actions group across 1 directory with 3 updates (#9)
Bumps the github-actions group with 3 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) and [DeterminateSystems/magic-nix-cache-action](https://github.com/determinatesystems/magic-nix-cache-action).


Updates `actions/checkout` from 4 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

Updates `DeterminateSystems/nix-installer-action` from 16 to 22
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v16...v22)

Updates `DeterminateSystems/magic-nix-cache-action` from 8 to 14
- [Release notes](https://github.com/determinatesystems/magic-nix-cache-action/releases)
- [Commits](https://github.com/determinatesystems/magic-nix-cache-action/compare/v8...v14)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: DeterminateSystems/magic-nix-cache-action
  dependency-version: '14'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-version: '22'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-30 22:34:36 +07:00
Asep Haryana d0c9f16d5d chore(infra): disable Docker compose for Nix-migrated services (hub, scraper, tools, llm-api) 2026-07-30 22:29:20 +07:00
Asep Haryana 6cbafbfe2e fix(flake): update scraper pinned rev for config fix 2026-07-30 22:28:24 +07:00
GitHub Action d6b9a0806b chore: update manifests and submodules [skip ci] 2026-07-30 15:23:34 +00:00
Asep Haryana 60a1af436f fix(infra): llm-api port 8080→8082 (conflict with gmw-proxy nginx) 2026-07-30 22:18:56 +07:00
Asep Haryana 2459541677 fix(scraper): update submodule - config list_separator fix 2026-07-30 22:18:03 +07:00
Asep Haryana 33b999d2b7 fix(ci): disable Determinate Nix / FlakeHub (no flakehub flakes used)
DeterminateSystems/nix-installer-action defaults to determinate:true
which tries to auth with FlakeHub via GitHub JWT. We don't use any
FlakeHub flakes, so disable it — skipping the auth entirely.
2026-07-30 22:05:41 +07:00
Asep Haryana 59f131f951 fix(ci): combine build+deploy per-service, pass exact store path
Separate deploy job failed because it used ls to find store paths
by name, finding OLD local paths instead of the freshly copied CI
paths. Now each service builds, copies, and updates its profile
in a single job using the exact store path from the build output.
No more guessing which path is the right one.
2026-07-30 21:42:39 +07:00
Asep Haryana cc8c5088a4 fix(ci): use SSH_PRIVATE_KEY secret, sanitize key format
- Ganti secret name: VPS_SSH_KEY → SSH_PRIVATE_KEY (nama yg ada)
- Fix nix copy URL: ***@ → $VPS_USER@
- Sanitize SSH key: strip \r\n, validasi dengan ssh-keygen
- Cegah libcrypto error dari key format broken
2026-07-30 21:32:40 +07:00
Asep Haryana d15e8621a9 fix(ci): proper SSH user in nix copy, deploy job structure
- Fix nix copy URL: ***@ → $VPS_USER@
- Store path from build output, passed across jobs
- Deploy job waits for all builds via needs: build
- SSH key setup in its own step, guarded by main branch
- Only deploy on main branch pushes
- Remote deploy script fetches from VPS nix store
2026-07-30 21:14:20 +07:00
Asep Haryana 33d5b12ec6 fix: tools-workers install path (no cd backend in installPhase) 2026-07-30 20:11:31 +07:00
Asep Haryana b5596e1398 feat(infra): full Nix migration — all 6 services + CI/CD
- flake.nix: 6 derivations (hub, scraper, tools-gateway, tools-workers, tools-frontend, llm-api)
- Fetch submodule source via builtins.fetchGit with pinned revs
- Fix cargo HOME/TMPDIR for Nix sandbox permission issues
- Fix llm-api: CMake/Clang deps for llama.cpp-sys2 bindgen
- Add LIBCLANG_PATH, LD_LIBRARY_PATH for Rust bindgen builds
- Systemd units: tools-gateway (3501), tools-frontend (3500), tools-workers, llm-api (8080)
- tools.target for grouped management
- Env configs: /etc/tools/env, /etc/llm-api/env
- GitHub Actions: nix-build.yml — matrix build + nix copy + deploy
- Update Traefik apps.yaml: tools/host.docker.internal:3500, llm-api/host.docker.internal:8080
- iptables: allow Docker→host on 3099, 4091, 3500, 3501, 8080
- Add scripts/nix-deploy.sh for CI/CD deploy step
2026-07-30 19:45:43 +07:00
Asep Haryana 46730ec07d feat(infra): Nix build for scraper, GitHub Actions workflow
- Build scraper (Rust) with Nix — cargo build --release
- Create scraper systemd unit (port 4091), env from Docker config
- Fix HOME/CARGO_HOME for Rust/cargo in Nix sandbox
- Update Traefik apps.yaml: scraper -> host.docker.internal:4091
- Add iptables rules for port 4091 (Docker->host)
- Add GitHub Actions workflow: nix-build.yml (determinate-nix + deploy)
- Save iptables rules persistently
2026-07-30 18:46:45 +07:00
Asep Haryana 1ae8a53cd4 chore: ignore Nix build result symlink 2026-07-30 18:33:52 +07:00
Asep Haryana ff52f8841c feat(infra): Nix build for hub app, systemd deployment
- Add flake.nix with derivations for hub (Next.js), scraper, tools, llm-api
- Create hub systemd unit (port 3099)
- Update Traefik dynamic config to point to host hub service
- Add iptables rule for Docker-to-host communication
- Use fetchGit for submodule source resolution
2026-07-30 18:33:43 +07:00
monrepo-bot b747ae5c3b chore: update hub to b007ced615dc 2026-07-29 00:12:03 +00:00
monrepo-bot 64ce812b4e chore: update hub to 5370fd47954d 2026-07-29 00:10:06 +00:00
monrepo-bot 9837d9dc50 chore: update hub to 407aa7b8993b 2026-07-28 00:11:32 +00:00
monrepo-bot 86dda74174 chore: update hub to a803849cba6c 2026-07-27 00:21:20 +00:00
monrepo-bot d045602f3e chore: update hub to 6042a6056a99 2026-07-27 00:17:20 +00:00
GitHub Action 32049c70ac chore: update manifests and submodules [skip ci] 2026-07-26 12:18:15 +00:00
asepharyana be7348a27d fix(infra): remove buffer/compress from llm-api for real SSE streaming
- llm-api uses llm-chain instead of common-chain
- llm-chain excludes buffer & compress middlewares
- Prevents Traefik from buffering streaming SSE responses
- Each token is now flushed immediately
2026-07-26 19:14:52 +07:00
asepharyana 43f0df4493 fix(infra): remove buffer/compress from llm-api middleware chain
- Buffer middleware causes SSE stream buffering
- Compress middleware breaks real-time streaming
- Added llm-chain with only secure-headers + retry + rate-limit
- llm-api router now uses llm-chain@file instead of common-chain@file
2026-07-26 19:13:08 +07:00
GitHub Action f0c7534152 chore: update manifests and submodules [skip ci] 2026-07-26 12:09:58 +00:00
asepharyana a4687c9d5b fix(llm-api): update submodule - reasoning/content stream separation 2026-07-26 19:04:36 +07:00
GitHub Action e42e967b7f chore: update manifests and submodules [skip ci] 2026-07-26 11:27:08 +00:00
asepharyana d99bc06df3 fix(llm-api): update submodule to 6ff31b5 — Jinja template via minijinja
- Replaced llama-cpp-2 apply_chat_template with minijinja rendering
- reasoning_content separate from content in API response
- Works with MiniCPM5 thinking model natively
2026-07-26 18:21:43 +07:00
GitHub Action 87df0d7ae2 chore: update manifests and submodules [skip ci] 2026-07-26 10:15:32 +00:00
monrepo-bot 518afa5645 chore: update llm-api to 9a63ff16018f 2026-07-26 10:11:01 +00:00
GitHub Action 2913f515f0 chore: update manifests and submodules [skip ci] 2026-07-26 09:14:25 +00:00
monrepo-bot 05e20df680 chore: update llm-api to 59c77108a55b 2026-07-26 09:09:23 +00:00
GitHub Action 59e2f9d173 chore: update manifests and submodules [skip ci] 2026-07-26 09:08:26 +00:00
monrepo-bot f78b826e59 chore: update llm-api to 495b9ed126e6 2026-07-26 09:03:36 +00:00
GitHub Action d75424634d chore: update manifests and submodules [skip ci] 2026-07-26 08:58:06 +00:00
GitHub Action cc8cefb33a chore: update manifests and submodules [skip ci] 2026-07-26 08:53:27 +00:00
monrepo-bot 757b48b446 chore: update llm-api to 254532458b16 2026-07-26 08:49:23 +00:00
monrepo-bot 20c81b4018 chore: update llm-api to 7c8f747faf43 2026-07-26 08:49:00 +00:00
GitHub Action ca81a8b7ee chore: update manifests and submodules [skip ci] 2026-07-26 08:44:28 +00:00
GitHub Action ac25a51113 chore: update manifests and submodules [skip ci] 2026-07-26 08:42:08 +00:00
asepharyana 10aeda4d4b fix: update llm-api MODEL_PATH for new MiniCPM5 GGUF
Updates MODEL_PATH to MiniCPM5-1B-Claude-Opus-Fable5-V2-Thinking-Q8_0.gguf
2026-07-26 15:40:14 +07:00
monrepo-bot 5a0737dc74 chore: update llm-api to d8425ea3b316 2026-07-26 08:38:25 +00:00
GitHub Action 1e4b17afb0 chore: update manifests and submodules [skip ci] 2026-07-25 12:06:23 +00:00
asepharyana 22e8a872cd fix(infra): replace short -S flags with long --system/--ingroup in hub Dockerfile
The oven/bun:1.2 base image was updated and -S flag on adduser is
now ambiguous between --shell and --system, causing build failure.
2026-07-25 19:02:00 +07:00
asepharyana 18cabefe9c fix(ci): remove duplicate submodule sync code in docker-build-push
The Sync submodule locally step had an identical duplicate block: after
cd-ing into SUBNODULE_PATH in the first block, the second block tried
cd SUBNODULE_PATH again, causing 'No such file or directory' since
the working directory was already inside the submodule path.

Also added cd $GITHUB_WORKSPACE at the end of the first block to
ensure the next step starts from the workspace root.
2026-07-25 18:58:59 +07:00
monrepo-bot f66f25f120 chore: update tools to 3956b90c3ce3 2026-07-25 11:13:58 +00:00
monrepo-bot 865fbf9547 chore: update llm-api to 14032db705fa 2026-07-25 11:13:53 +00:00
monrepo-bot 1d50980eed chore: update scraper-api to bc782ae4f825 2026-07-25 11:13:44 +00:00
monrepo-bot ef29e0e4ef chore: update hub to 51798f483f59 2026-07-25 11:13:39 +00:00
asepharyana 16c0231b9f fix(hub): update subproject commits for hub and hub-guide 2026-07-25 17:54:01 +07:00
asepharyanaandClaude Code 3be2744070 fix(hub): switch Dockerfile from npm to bun
hub project uses bun.lock, but Dockerfile was using npm install which
resolves dependencies differently. Switched to oven/bun image and
bun install --frozen-lockfile for deterministic builds.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 17:43:11 +07:00
asepharyana 3dfde5e872 fix(ci): add tools to allowed services in update-submodule workflow 2026-07-25 16:39:38 +07:00
asepharyana 632868356d fix(tools): update submodule with corrected service name 2026-07-25 15:28:51 +07:00
asepharyanaandClaude Code 2e3d5c3de6 chore: add notify-parent workflows to hub and tools submodules
Add .github/workflows/notify-parent.yml to both submodules for
repository_dispatch to parent hub repo on push to main.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 15:28:11 +07:00
asepharyanaandClaude Code 6fcdf967b6 chore(llm-api): add notify-parent workflow
Add .github/workflows/notify-parent.yml for repository_dispatch.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 15:13:58 +07:00
asepharyanaandClaude Code ac01422d21 chore(llm-api): update submodule to refactor/clean-architecture
Update submodule pointer to latest refactored architecture.

- Clean architecture (domain/application/infrastructure/presentation)
- Web chat UI at GET /
- Consolidated sampler builder

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 15:07:43 +07:00
asepharyana 95a9921b7a fix(deploy): use 'docker compose config --services' for reliable service name extraction
Replace fragile grep-based extraction that matched volume names
(nats_data, tools_data) and 'null' as pseudo-services.
2026-07-25 14:21:48 +07:00
GitHub Action fde53f22b6 chore: update manifests and submodules [skip ci] 2026-07-25 06:45:09 +00:00
asepharyana e953c3cb67 ci(llm-api): integrate into hub CI/CD pipeline
- Add llm-api to docker-build-push workflow (build, push, deploy)
- Add llm-api compose file to deploy-docker ALL_COMPOSE_FILES
- Add llm-api to update-submodule workflow
- Add llm-api CodeQL build step in security workflow
- Fix typo in Traefik host rule (asepharya -> asepharyana)
- Update CLAUDE.md submodule list
- Fix .gitmodules URL for llm-api submodule
- Clean up standalone infra files from llm-api submodule repo
2026-07-25 13:39:35 +07:00
asepharyana cbad784bca chore: update submodule commits for hub and hub-guide 2026-07-25 12:06:49 +07:00
asepharyana 189311015c chore(hub-guide): rename repo to github.com/asepharyana/asepharyana-hub-guide 2026-07-25 12:06:16 +07:00
asepharyana ab3c95dae2 chore(hub-guide): rename repo to github.com/asepharyana/hub-guide 2026-07-25 12:04:52 +07:00
asepharyana 05d6b4a055 chore: update llm-api submodule to e1f5195 (SSE streaming) 2026-07-25 11:34:30 +07:00
GitHub Action 2033d9b2bc chore: update manifests and submodules [skip ci] 2026-07-25 04:22:20 +00:00
asepharyana 8fc4a15e0c feat(llm-api): add MiniCPM-V LLM API service with Traefik routing 2026-07-25 11:20:15 +07:00
asepharyana 88f6968654 feat(infra): move hub-guide submodule to plugins/hub-guide 2026-07-25 10:56:24 +07:00
asepharyana 08883ba1a6 feat(infra): add hub-guide Claude Code plugin submodule 2026-07-25 10:54:46 +07:00
GitHub Action 4fbca91c8c chore: update manifests and submodules [skip ci] 2026-07-24 12:59:04 +00:00
asepharyanaandKilo 1cdd849d89 feat(infra): update tools submodule with PDF split/compress frontend
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 19:54:37 +07:00
asepharyanaandKilo de161d95a8 feat(infra): update tools submodule with image+PDF tool implementations
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 19:52:50 +07:00
GitHub Action c4791b3160 chore: update manifests and submodules [skip ci] 2026-07-24 12:08:26 +00:00
asepharyanaandKilo e9f3cd458c feat(infra): update tools submodule with interactive resize/convert options
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 19:07:17 +07:00
asepharyanaandKilo 14c9668e85 fix(infra): update tools submodule with panic-safe contour detection
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 19:01:40 +07:00
GitHub Action 6f1dfea710 chore: update manifests and submodules [skip ci] 2026-07-24 11:54:28 +00:00
asepharyanaandKilo 3c52036f7e fix(infra): replace curl healthcheck with wget, install wget in runtime
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 18:52:40 +07:00
GitHub Action e10a2e498b chore: update manifests and submodules [skip ci] 2026-07-24 11:50:52 +00:00
GitHub Action 183659b56c chore: update manifests and submodules [skip ci] 2026-07-24 11:50:03 +00:00
asepharyanaandKilo 38873dd73e fix(infra): install leptonica/tesseract dev libs for tesseract feature build
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 18:44:22 +07:00
asepharyanaandKilo be2117aa16 fix(infra): audit — NATS subject, worker spawn, tesseract feature, pipeline PDF
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 18:24:35 +07:00
GitHub Action f92aa8ef30 chore: update manifests and submodules [skip ci] 2026-07-24 10:58:20 +00:00
GitHub Action 4be55b57bb chore: update manifests and submodules [skip ci] 2026-07-24 10:57:26 +00:00
asepharyanaandKilo 5c79a91a2e fix(infra): copy bun binary to runtime for Next.js frontend
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 17:55:59 +07:00
GitHub Action 13e2c38eef chore: update manifests and submodules [skip ci] 2026-07-24 10:53:19 +00:00
asepharyanaandKilo 8f62f955d1 fix(infra): route tools Traefik to Next.js frontend (port 3000)
Frontend serves UI, proxies /api/* to Rust gateway on port 3001.
Rust gateway remains internal, not exposed via Traefik.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 17:52:23 +07:00
GitHub Action d2f592261e chore: update manifests and submodules [skip ci] 2026-07-24 10:48:34 +00:00
GitHub Action 70f54c8c7a chore: update manifests and submodules [skip ci] 2026-07-24 10:47:39 +00:00
asepharyanaandKilo a35e19b546 fix(infra): update tools submodule entrypoint, use port 3002
Entrypoint now auto-discovers binary locations in various paths.
Host port changed to 3002 due to stale iptables rule on port 3001.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 17:47:24 +07:00
asepharyanaandKilo c25850b727 fix(infra): use unique binary names in Dockerfile builder stage
The cp destination /app/gateway conflicts with the source code
directory apps/tools/backend/gateway/ which is copied to /app/gateway/.
Use /app/tools-gateway-bin to avoid name collision.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 17:42:50 +07:00
asepharyanaandKilo 97b48ddb21 fix(infra): remove depends_on redis/nats from tools.yml
Shared services are in separate compose files. Tools depends on them
at runtime but Docker compose doesn't need to manage their lifecycle.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 17:40:35 +07:00
GitHub Action ab994ffd54 chore: update manifests and submodules [skip ci] 2026-07-24 07:17:21 +00:00
GitHub Action 9d0b075b92 chore: update manifests and submodules [skip ci] 2026-07-24 07:16:37 +00:00
asepharyanaandKilo c5a7ae247a fix(infra): copy binaries from builder's filesystem, not cache mount
The builder stage uses --mount=type=cache for /app/target, so binaries
are in the cache which isn't available to the runtime stage COPY.
Fix by copying from /app/gateway and /app/workers which were cp'd
to the builder's regular filesystem.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 14:10:13 +07:00
asepharyanaandKilo cbda54fdee fix(infra): update tools submodule to rust 1.89 toolchain
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:57:51 +07:00
asepharyanaandKilo 836ef6862e fix(infra): bump cargo-chef image to rust 1.89 for compatibility
cargo-chef with rust 1.85 has compatibility issues with some dependency crates.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:45:51 +07:00
asepharyanaandKilo bd751c0fca fix(infra): update tools submodule to fix AbortController type error
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:43:32 +07:00
asepharyanaandKilo ad7a0de37b fix(infra): update tools submodule to fix lucide icon imports
Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:41:07 +07:00
asepharyana 05a9179356 chore: update tools submodule to latest (add bun.lock) 2026-07-24 13:32:44 +07:00
asepharyanaandKilo 584a7155e6 fix(infra): use pre-built cargo-chef image in Dockerfile
Replace cargo install cargo-chef with pre-built image
lukemathwalker/cargo-chef:latest-rust-1.85.0 and add build
cache mounts for faster rebuilds.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:32:31 +07:00
asepharyanaandKilo a41b2d7312 fix(infra): remove bun.lock from Dockerfile, fix frontend build
The bun.lock doesn't exist yet - use bun install without frozen-lockfile.
This will be optimized once a lockfile is generated.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:20:30 +07:00
asepharyanaandKilo c8fcaecbdb fix(ci): make tools submodule public for CI access
Tools repo is now public so GITHUB_TOKEN can clone it.
Revert the token injection workaround since it's no longer needed.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:16:21 +07:00
asepharyanaandKilo d8b1457a11 fix(infra): convert apps/tools to submodule, fix Dockerfile paths
Move tools code to its own repo (asepharyana/asepharyana-hub-tools)
and add as git submodule following the existing app pattern.
Fix Dockerfile paths to use apps/tools/ prefix since build context
is the repo root.

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:11:54 +07:00
asepharyanaandKilo 67288c8723 feat(infra): add tools service with document scanner, image & PDF tools
Implement self-hosted document scanner and media processing tools as
an alternative to CamScanner/ilovepdf without third-party uploads.

Backend: Rust Axum gateway + worker pool with NATS JetStream queue
Frontend: Next.js 16 + shadcn/ui + Tailwind v4 + Framer Motion
Pipeline: Canny edge detection -> DLT homography warp -> Sauvola
binarization -> Hough deskew -> Tesseract OCR -> searchable PDF

Phase 1 (MVP) delivers:
- Document scanner with perspective correction and OCR
- Image compress/resize/convert tools
- PDF merge/split/compress tools
- Real-time WebSocket progress updates
- Rate limiting, auto-cleanup, Prometheus metrics
- Full CI/CD pipeline with Docker multi-stage build

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:08:09 +07:00
asepharyana 9c12d135e4 Implement code changes to enhance functionality and improve performance 2026-07-24 10:19:03 +07:00
asepharyana 68dea35d73 feat: add infrastructure documentation and processing pipeline for document scanner
- Introduced a comprehensive Docker image architecture for the project, detailing multi-stage builds for Rust backend and Next.js frontend.
- Added Docker Compose configuration for the tools service, including environment variables and volume management.
- Documented CI/CD integration steps for Docker build and deployment workflows.
- Implemented a detailed processing pipeline for document scanning, covering stages from image preprocessing to PDF generation.
- Included edge case handling and performance budget for each stage of the pipeline.
- Enhanced security considerations and rollback strategies for the tools service.
2026-07-24 10:19:03 +07:00
GitHub Action 447b22d9da chore: update manifests and submodules [skip ci] 2026-07-24 02:11:27 +00:00
mytheclipsebotreview[bot] e38fe75d3c Auto-merge PR #6
chore(deps): bump actions/checkout from 6 to 7 in the github-actions group across 1 directory
2026-07-24 02:09:41 +00:00
asepharyana ddc4c58243 chore: update subproject commit for hub application 2026-07-24 08:26:03 +07:00
Asepharyana 1e10973c86 ci: trigger fresh run for updated security.yml 2026-07-24 01:39:57 +07:00
dependabot[bot] 0242fc4b68 chore(deps): bump actions/checkout
Bumps the github-actions group with 1 update in the / directory: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-23 18:39:01 +00:00
Asepharyana b7e4203a15 fix(security): checkout git submodules for CodeQL Rust analysis 2026-07-24 01:36:17 +07:00
asepharyanaandClaude Opus 4.8 7181710aa7 chore: remove lidm routes from file config — now via Docker labels
Routing is handled by container labels (traefik.enable + router labels),
so file-level routes for lidm are no longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 19:19:30 +07:00
asepharyanaandClaude Opus 4.8 478a0f849f fix: use websecure + tls for lidm routes, same pattern as other services
Cloudflare Origin CA wildcard cert covers *.asepharyana.my.id,
so websecure entrypoint with tls works correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 19:07:22 +07:00
asepharyanaandClaude Opus 4.8 7fc863d611 feat: add lidm frontend and backend routes to traefik config
Add routers and services for lidm.asepharyana.my.id (→ lidm-frontend:3000)
and lidm-api.asepharyana.my.id (→ lidm-backend:3001) behind Traefik.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 19:02:13 +07:00
GitHub Action df7aaedfba chore: update manifests and submodules [skip ci] 2026-07-23 11:17:49 +00:00
asepharyanaandAsep Haryana Saputra 93d053b123 ci(deploy): remove :latest tag from Docker images to avoid cache bugs
Using only :sha-<short> tags ensures deterministic image references
and prevents stale :latest caches from serving wrong images.

Co-Authored-By: Asep Haryana Saputra <asepharyana@users.noreply.github.com>
2026-07-23 18:15:17 +07:00
asepharyana 401a26aea9 chore: update hub submodule to f3ddc62 2026-07-23 17:56:37 +07:00
asepharyana 0a4952ce7f chore: update hub submodule to 22cdc2e 2026-07-23 17:56:15 +07:00
asepharyana 23ba9c48fa chore: update hub submodule to e528b6a 2026-07-23 17:05:17 +07:00
asepharyana 7e92351f45 chore: update hub submodule to 4dbaba7 2026-07-23 15:54:01 +07:00
asepharyana 8e489b9f41 chore: update hub submodule to 6b725f3 2026-07-23 15:03:04 +07:00
asepharyana 0dbaf66743 chore: update hub submodule to e529e8c 2026-07-23 12:47:56 +07:00
asepharyana 6091d358e1 chore: update hub submodule to d94524b 2026-07-23 12:21:34 +07:00
asepharyanaandClaude Opus 4.8 c17f5f5b32 docs: update ARCHITECTURE.md and CLAUDE.md
- HTTP flow includes hub routes (/, /dashboard, /api/dashboard)
- Compose table adds hub.yml and observability.yml
- Prometheus auto-discovery section (Docker SD + labels)
- Traefik metrics query reference
- Docker socket access pattern (group_add: 988)
- Service registry table includes root domain, hub, jaeger
- CLAUDE.md monitoring section

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:11:30 +07:00
asepharyana 83b825b449 chore: bump hub submodule (latency metric fix) 2026-07-23 07:59:08 +07:00
asepharyana 48086de66b chore: bump hub submodule to f0bbd13 2026-07-23 07:52:41 +07:00
asepharyana 545397eb1f fix(infra): fix Prometheus Docker SD relabel + group_add 2026-07-23 07:50:20 +07:00
asepharyanaandClaude Opus 4.8 40e1a80f9f feat(infra): auto-discovery Prometheus + Traefik metrics
- Prometheus: add docker_sd_configs for auto-discovery via labels
- Prometheus: mount Docker socket for Docker SD
- Traefik: enable --metrics.prometheus with entrypoint/service labels
- Traefik: add prometheus.io/scrape=true label
- Observability: mount Docker socket for Prometheus container
- Bump hub submodule (Traefik metrics queries + sparkline charts)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 07:45:59 +07:00
asepharyana 688ac095f6 fix(infra): add group_add for docker socket access in hub 2026-07-23 07:35:58 +07:00
GitHub Action a4852f636a chore: update manifests and submodules [skip ci] 2026-07-23 00:33:56 +00:00
asepharyana 69cae1e73e fix(infra): chown .next dir in hub Dockerfile for runtime permissions 2026-07-23 07:32:59 +07:00
GitHub Action bd9cc1f4c7 chore: update manifests and submodules [skip ci] 2026-07-23 00:25:10 +00:00
asepharyanaandClaude Opus 4.8 aaaf5de70c feat(infra): remove Go dashboard, dashboard is now part of hub Next.js app
- Remove dashboard container from observability.yml
- Remove infra/docker/dashboard.Dockerfile
- Remove dashboard route from Traefik apps.yaml
- Mount Docker socket on hub container for /api/dashboard
- Remove dashboard build from CI/CD (docker-build-push.yml)
- Update submodule pointer to hub with Next.js dashboard rewrite

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 07:23:12 +07:00
asepharyana cea96fcfc2 fix(ci): update dashboard detection to apps/hub/dashboard/ 2026-07-23 07:19:55 +07:00
asepharyanaandClaude Opus 4.8 5ff8f9224f feat(infra): migrate dashboard into hub submodule
- Move infra/dashboard/ → apps/hub/dashboard/ (hub submodule repo)
- Update dashboard.Dockerfile to COPY from apps/hub/dashboard/
- Update go module path

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 07:19:55 +07:00
asepharyana caf3f1d79b fix(infra): remove hub subdomain from Traefik rule 2026-07-23 07:19:55 +07:00
asepharyana 4e4ed101c5 fix(infra): hub at root domain asepharyana.my.id 2026-07-23 07:19:55 +07:00
asepharyanaandClaude Opus 4.8 9db886e68b feat(infra): add hub service — Docker, Traefik, CI/CD
- Add infra/docker/hub.Dockerfile (Next.js multi-stage build)
- Add infra/compose/hub.yml with app-shared-net
- Add Traefik route for hub.asepharyana.{my,web}.id → hub:3000
- Update docker-build-push.yml: path detection, matrix build, dispatch, manifest
- Update deploy-docker.yml: include hub.yml in ALL_COMPOSE_FILES
- Update update-submodule.yml: allow hub service
- Update docs/ARCHITECTURE.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 07:19:55 +07:00
asepharyanaandClaude Opus 4.8 cdaa3c3d57 feat(root): add apps/hub as git submodule
- Created asepharyana/asepharyana-hub-hub repo
- Updated CLAUDE.md with both submodules

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 07:19:55 +07:00
GitHub Action f085375363 chore: update manifests and submodules [skip ci] 2026-07-22 23:03:10 +00:00
asepharyana 5999ac159a feat(infra): responsive UI with mobile-first design and blur header
- Mobile-first responsive: single column → 2 columns → auto-fill grid
- Sticky header with backdrop-filter blur
- Cards with hover glow and fade-in animation
- SVG charts scale with container (max-width constraint per breakpoint)
- Touch-friendly link targets on mobile
- Custom dark scrollbar
- 5 breakpoints: 480, 640, 768, 1024, 1280px
2026-07-23 06:02:10 +07:00
GitHub Action 28ba9dd78c chore: update manifests and submodules [skip ci] 2026-07-22 22:59:17 +00:00
asepharyana 4cc2ba1630 fix(infra): remove all fixed grid-column overrides, layout fully auto-flow
All cards now auto-arrange in the CSS grid — no hardcoded positions,
no fixed widths. Layout adapts naturally to content and screen size.
2026-07-23 05:58:17 +07:00
GitHub Action bf39884ea6 chore: update manifests and submodules [skip ci] 2026-07-22 22:39:21 +00:00
asepharyana 373cb4fc1a feat(infra): fully dynamic dashboard layout with auto-adaptive grid
- Replace fixed grid-column spans with auto-fill grid
- Charts auto-hide when no data available (no gaps)
- System Resources row spans full width only when data exists
- Responsive breakpoints for mobile, tablet, desktop
- SVG charts scale with container width
2026-07-23 05:38:27 +07:00
GitHub Action f96041a164 chore: update manifests and submodules [skip ci] 2026-07-22 22:32:10 +00:00
asepharyana 9a48a49f21 feat(infra): auto-discovered node metrics (CPU, RAM, Disk) and fully dynamic dashboard
- Add node-exporter container for host metrics (CPU, RAM, Disk)
- SVG gauge charts rendered server-side from Prometheus data
- Auto-detect compose project name from Docker labels
- Auto-detect web services via traefik.* labels for quick links
- Remove all hardcoded service name filters
- Dynamic system name based on compose project
2026-07-23 05:31:05 +07:00
GitHub Action 4f15cafc02 chore: update manifests and submodules [skip ci] 2026-07-22 22:21:46 +00:00
asepharyana 6c24508c77 fix(ci): add missing dashboard output in changes job so update-manifest runs 2026-07-23 05:20:59 +07:00
GitHub Action c909c4a9a1 chore: update manifests and submodules [skip ci] 2026-07-22 22:17:46 +00:00
asepharyana aa05820393 fix(ci): correct dashboard change detection regex to match files inside infra/dashboard/ 2026-07-23 05:16:47 +07:00
asepharyana ffe07ddef0 fix(infra): filter hub services by compose project label, fix TraceList field name collision 2026-07-22 18:46:16 +07:00
asepharyana 0bc61d8b1c fix(infra): downgrade go.mod to 1.24 for CI build compatibility 2026-07-22 18:37:13 +07:00
asepharyana afae724f6b refactor(infra): server-side SVG dashboard with Go
- Replace nginx+Chart.js client-side dashboard with Go server
- Server-side rendered SVG charts (donut, line charts)
- Direct Docker socket integration via Go stdlib
- Embedded HTML template via //go:embed
- Minimal scratch image (~9MB total)
- Remove nginx.conf and client-side Chart.js
2026-07-22 18:35:29 +07:00
asepharyana 9f54f90621 fix(infra): use anonymous Prometheus volume to avoid deploy script name collision 2026-07-22 18:28:16 +07:00
asepharyana ee4fd677ea feat(infra): add Prometheus and Chart.js dashboard with real-time charts
- Add Prometheus server scraping OTel collector
- Add Chart.js donut chart for service health distribution
- Add line charts for RPS, latency, error rate, trace volume
- Add Prometheus API proxy to nginx whitelist
- Restructure dashboard layout with 12-column responsive grid
2026-07-22 18:26:41 +07:00
asepharyana 1ee99ffc2c fix(infra): restrict Docker API proxy to whitelist only 2026-07-22 18:20:11 +07:00
asepharyana 46314e6d38 fix(infra): correct Traefik OTLP tracing flags 2026-07-22 18:16:18 +07:00
GitHub Action 362e9f4115 chore: update manifests and submodules [skip ci] 2026-07-22 11:13:25 +00:00
asepharyana da268f1671 feat: enhance observability with dynamic links, Docker API integration, and health checks 2026-07-22 18:12:44 +07:00
asepharyana 4ef4f615c1 feat: add dashboard service to Docker build and push workflow 2026-07-22 18:12:44 +07:00
GitHub Action 5d5a0f48b5 chore: update manifests and submodules [skip ci] 2026-07-22 09:44:30 +00:00
asepharyana dcb93035f3 feat(observability): add OpenTelemetry Collector, Jaeger, and custom dashboard for monitoring
fix(infra): update NATS configuration for OpenTelemetry tracing and JetStream support
refactor(dapr): modify Dapr sidecar configuration to use new resources path and config file
chore(docs): update deployment documentation to include observability services
docs(commit-convention): establish commit message guidelines for Asepharyana Hub
docs(deploy-workflow): outline CI/CD pipeline and manual deployment steps for Asepharyana Hub
feat(event-driven): implement event-driven architecture patterns with Dapr and NATS
docs(hub-rules): define repository structure, submodule strategy, and infrastructure patterns
2026-07-22 16:43:37 +07:00
asepharyana df71487563 Add comprehensive documentation for CI/CD pipeline, NATS, security practices, Tailscale networking, and troubleshooting guide
- Created CI/CD pipeline documentation detailing workflows, triggers, and actions for `asepharyana-hub`.
- Added NATS + JetStream guide covering architecture, configuration, CLI tools, and event topics.
- Introduced a security guide outlining best practices for secrets management, TLS, container security, and access control.
- Documented Tailscale networking setup and troubleshooting for connectivity between VPS and bare-metal nodes.
- Compiled a troubleshooting guide addressing common issues across deployment, Dapr, NATS, Traefik, Tailscale, Docker, database, and submodules.
2026-07-22 16:35:38 +07:00
asepharyana 5025d103e9 chore: update scraper submodule to 7640e66 and add deployment documentation 2026-07-22 15:16:45 +07:00
GitHub Action fd3c7f8c29 chore: update manifests and submodules [skip ci] 2026-07-22 08:08:45 +00:00
monrepo-bot 979a70b279 chore: update scraper-api to 7640e66c7673 2026-07-22 08:03:34 +00:00
GitHub Action a3c5d747ca chore: update manifests and submodules [skip ci] 2026-07-22 08:01:56 +00:00
GitHub Action ce7f630894 chore: update manifests and submodules [skip ci] 2026-07-22 07:56:42 +00:00
monrepo-bot ff047b44de chore: update scraper-api to 96ce29b0f349 2026-07-22 07:52:38 +00:00
monrepo-bot 8e128568e7 chore: update scraper-api to 5c51ec895f5f 2026-07-22 07:51:29 +00:00
GitHub Action 8f22d052f8 chore: update manifests and submodules [skip ci] 2026-07-22 07:30:23 +00:00
GitHub Action 005fe466ed chore: update manifests and submodules [skip ci] 2026-07-22 07:29:39 +00:00
asepharyana b7180ab30c chore: update .gitignore to include .playwright-mcp directory 2026-07-22 14:25:00 +07:00
monrepo-bot 299756d285 chore: update scraper-api to 891064354b49 2026-07-22 07:23:54 +00:00
GitHub Action ad67ed9711 chore: update manifests and submodules [skip ci] 2026-07-22 07:19:02 +00:00
asepharyana 2bb1f1e814 chore: update scraper submodule — remove 17 unused deps 2026-07-22 14:13:16 +07:00
GitHub Action aa83986b23 chore: update manifests and submodules [skip ci] 2026-07-22 07:10:32 +00:00
asepharyana 9b9ce9e59e chore: update scraper submodule — add episodes+downloads to detail 2026-07-22 14:04:58 +07:00
GitHub Action 73011d9d14 chore: update manifests and submodules [skip ci] 2026-07-22 07:02:50 +00:00
asepharyana a659f598ee chore: update scraper submodule — revert to alqanime.si 2026-07-22 13:56:38 +07:00
GitHub Action 1befc9285a chore: update manifests and submodules [skip ci] 2026-07-22 06:52:39 +00:00
GitHub Action 2f3bc950a6 chore: update manifests and submodules [skip ci] 2026-07-22 06:46:53 +00:00
asepharyana 7bf4d69f17 chore: update scraper submodule — CLAUDE.md cleanup 2026-07-22 13:43:20 +07:00
monrepo-bot e54c959765 chore: update scraper-api to c035601a3666 2026-07-22 06:41:21 +00:00
GitHub Action e825c553d6 chore: update manifests and submodules [skip ci] 2026-07-22 06:08:47 +00:00
monrepo-bot 61cd1a712a chore: update scraper-api to 562ada69ec9e 2026-07-22 06:03:20 +00:00
GitHub Action cc3e891960 chore: update manifests and submodules [skip ci] 2026-07-22 06:00:58 +00:00
asepharyana b960f9915c chore: remove EXTERNAL_BROWSERLESS env var from compose and docs 2026-07-22 12:55:50 +07:00
asepharyana 7e563b7a54 chore: update scraper submodule — remove browserless fallback
Dropped EXTERNAL_BROWSERLESS_WS feature; proxy-bun service now handles
all proxy needs.
2026-07-22 12:55:32 +07:00
asepharyana 64b8bb29a3 fix: include all compose files for dependency resolution on selective deploy
Scraper services depend on dapr-placement (dapr.yml) and nats (nats.yml).
When only scraper.yml changed, docker compose pull/up failed with
'undefined service' because dependent compose files were excluded.

Now always include ALL compose files for dependency resolution, but
selectively pull and up only the target services during partial updates.
2026-07-22 08:40:49 +07:00
GitHub Action 443ef9a8da chore: update manifests and submodules [skip ci] 2026-07-22 00:22:16 +00:00
GitHub Action 36de54dd85 chore: update manifests and submodules [skip ci] 2026-07-22 00:21:29 +00:00
asepharyana 79015974df fix: map scraper-api service name to correct submodule path
The update-submodule.yml workflow used apps/${SERVICE} directly,
but the submodule path is apps/scraper, not apps/scraper-api.
Added a case statement to map service names to correct paths.
2026-07-22 07:17:40 +07:00
asepharyana f7f8ee1beb chore: update scraper submodule to fix notify-parent target
Points to asepharyana/asepharyana-hub instead of the dead
MythEclipse/ultimate-asepharyana.tech.
2026-07-22 07:07:16 +07:00
asepharyana a6b822bbfe fix: add manual cargo build step for CodeQL Rust analysis
The security.yml CodeQL job fails because:
1. The Rust project is in apps/scraper/, not at the repo root
2. CodeQL v4's autobuilder can't find Cargo.toml at the root
3. CodeQL v4 requires the Rust code to be built for analysis

Also removes javascript-typescript language since this repo has no JS/TS code.
2026-07-22 07:02:25 +07:00
asepharyana 0ba5da78f9 fix: remove javascript-typescript from CodeQL languages in security.yml
The repo only has Rust source code (no JS/TS). CodeQL v4 fails when
a specified language has no code to analyze, which blocks PR #5
(dependabot bumping codeql-action from v3 to v4).
2026-07-22 06:59:17 +07:00
asepharyana 26d980fc00 refactor(infra): update Traefik configuration references and remove obsolete traefik.yaml 2026-07-21 19:18:42 +07:00
asepharyana 1098f97c05 fix: remove duplicate clean-code.md — only keep the symlink to kana-best-practice-engineering 2026-07-21 18:38:27 +07:00
asepharyana 3fdf1c670f refactor(clean-code): symlink to kana-best-practice-engineering for comprehensive clean code references (SOLID, TDD, architecture, paradigms, craft, oath) 2026-07-21 18:23:07 +07:00
asepharyana 3299759dae feat: add Claude Code project skills — clean-code, hub-rules, commit-convention, event-driven, deploy-workflow 2026-07-21 18:21:29 +07:00
asepharyana bc153b7491 fix(dapr): use pubsub.redis — Dapr 1.17 doesn't include built-in NATS pubsub. Redis already running. 2026-07-21 17:33:09 +07:00
asepharyana 05f3a65358 fix(dapr): use pubsub.nats instead of deprecated natsstreaming, use --resources-path 2026-07-21 17:31:57 +07:00
asepharyana 7b983daa4c fix(nats,dapr): remove healthchecks — scratch images have no shell/wget. Fix depends_on to use service_started 2026-07-21 17:30:21 +07:00
asepharyana 9a29d86fbd fix(nats): correct CLI flags — use -js/-sd instead of --store_dir/--max_payload 2026-07-21 17:27:48 +07:00
asepharyanaandClaude Opus 4.8 ea168b4b20 feat: add NATS + Dapr infrastructure for event-driven architecture
- infra/compose/nats.yml: NATS server with JetStream, persistence volume, healthcheck
- infra/compose/dapr.yml: Dapr placement service for sidecar coordination
- infra/dapr/: Dapr global config, pubsub component (NATS), statestore (Redis)
- infra/compose/scraper.yml: add Dapr sidecar container + depends_on nats/dapr
- docs/add-dapr-service.md: guide for integrating Dapr into new services
- docs/add-new-app.md: add Dapr sidecar step
- ARCHITECTURE.md: add NATS/Dapr to infra table + service mesh diagram
- infra/README.md: update layout and deployment order
- deploy-docker.yml: add nats.yml + dapr.yml to ALL_COMPOSE_FILES

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 17:26:02 +07:00
asepharyana d546a1acf9 fix(scraper): healthcheck tolerate 404 — server is up even if root returns 404 2026-07-21 15:52:51 +07:00
asepharyana 23269e1ab2 fix(scraper): fix healthcheck — use wget instead of ss (not available in slim image) 2026-07-21 15:36:42 +07:00
asepharyanaandClaude Opus 4.8 8f05d1df3d fix(scraper): add EXTERNAL_BROWSERLESS_WS back — service still running on imrnes
Also relax scraper healthcheck to use TCP fallback (ss) for when /health
endpoint doesn't respond in time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:32:25 +07:00
asepharyanaandClaude Opus 4.8 28934aaafc docs: update Tailscale IPs and node names to match live infrastructure
- ARCHITECTURE.md: orange → orangevps (100.79.111.61), update imrnes IP to 100.121.180.82, archlinux IP to 100.84.39.83, remove offline laptop node
- README.md: update VPS table, env examples, troubleshooting IPs
- CONTRIBUTING.md: fix VPS name and IP in deploy section

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:04:57 +07:00
GitHub Action 75da3af5b8 chore: update manifests and submodules [skip ci] 2026-07-21 06:34:29 +00:00
asepharyana c4ecf8f21b Merge commit 'f465ca2'
# Conflicts:
#	infra/compose/scraper.yml
2026-07-21 13:28:37 +07:00
asepharyanaandClaude Opus 4.8 f465ca261e refactor: clean up codebase — remove legacy references, optimize dockerfile, prune .env.example, simplify CI/CD
- Traefik: remove asephstech/asephscloud SSL certs, fix api.insecure, add ping healthcheck
- .env.example: prune from 145 to 30 vars (remove Firebase, Discord, Coolify, etc.)
- scraper.Dockerfile: remove Node.js & Chromium from runtime, add healthcheck
- CI/CD: remove orphan container ref, commented blocks, fix lint workflow
- infra: remove mysql config (no active service), clean up middleware config
- root: remove .nvmrc (dup), renovate.json (use dependabot), simplify Makefile
- scripts: fix legacy package refs, simplify update-deps to use bun
- docs: remove stale handoff-log, quality-gates, superpowers designs, archived files
- compose: add healthchecks to traefik, redis, and scraper-api services

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:27:45 +07:00
asepharyana 092c0b363d Refactor Dockerfile for scraper, update Traefik configuration, and clean up scripts
- Simplified Dockerfile for scraper by removing unnecessary Node.js installation and optimizing build stages.
- Updated Traefik environment configuration to use shorter variable names for certificate paths.
- Removed commented-out middleware configurations in dynamic middlewares.yaml.
- Cleaned up legacy SSL configurations in ssl.yaml.
- Disabled insecure API access in Traefik configuration.
- Deleted unused renovate.json file.
- Modified update environment script to only process apps directory.
- Updated GHCR cleanup script to target specific package.
- Streamlined dependency update script to focus on app submodules.
- Removed setup script for Prometheus as it is no longer needed.
2026-07-21 13:27:13 +07:00
GitHub Action 84d6056e5e chore: update manifests and submodules [skip ci] 2026-07-21 06:01:26 +00:00
asepharyana 80027ee5d3 chore: update subproject commit for scraper application 2026-07-21 12:54:57 +07:00
asepharyana 5d6bdf6ce3 refactor: update architecture and documentation for scraper service, remove deprecated services 2026-07-21 12:54:57 +07:00
asepharyana cdad9ae720 chore: remove unused configuration files and submodules 2026-07-21 12:54:57 +07:00
asepharyana 44a56fa0a9 feat: add comprehensive deployment guide for Docker and GitHub Actions 2026-07-21 12:54:57 +07:00
mytheclipsebotreview[bot] 8145aff114 Auto-merge PR #4
Test PR-Agent V3 - Model Fix
2026-07-17 02:02:32 +00:00
asepharyana 45ab3c8792 test: pr-agent v3 with model fix 2026-07-17 08:03:26 +07:00
90 changed files with 1687 additions and 7100 deletions
-1
View File
@@ -1 +0,0 @@
1.3.11
-7
View File
@@ -1,7 +0,0 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
"ghcr.io/devcontainers/features/rust:1": {},
"ghcr.io/devcontainers/features/node:2": {}
}
}
-32
View File
@@ -1,32 +0,0 @@
**/.git
**/.gitmodules
**/node_modules
**/dist
**/.output
**/target
**/.svelte-kit
**/.next
**/.DS_Store
**/build
!apps/*/scripts/build/
!apps/*/src/**/build/
**/*.log
**/*.pem
.env
.env.*
!.env.example
# IDE and temporary files
**/.vscode
**/.idea
**/tmp
**/temp
**/.cache
**/coverage
**/.npm
**/.bun
**/.pnpm-store
**/.yarn
**/.cargo-ok
**/*.swp
**/*~
+17
View File
@@ -0,0 +1,17 @@
name: Dependabot Auto-Merge
on: pull_request
permissions:
contents: write
pull-requests: write
jobs:
auto-merge:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Enable auto-merge for Dependabot PR
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+14 -17
View File
@@ -1,21 +1,18 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: 'devcontainers'
directory: '/'
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: weekly
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: weekly
interval: "daily"
open-pull-requests-limit: 10
groups:
github-actions:
patterns:
- '*'
production:
dependency-type: "production"
update-types:
- "minor"
- "patch"
development:
dependency-type: "development"
update-types:
- "minor"
- "patch"
-235
View File
@@ -1,235 +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@v6
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/elysia.yml infra/compose/react.yml infra/compose/rust-auth.yml infra/compose/llm-api.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
# Submodule update removed for faster VPS deployments
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
# Construct compose arguments
docker rm -f imphenbot-app || true
if [ -n "$TARGET_COMPOSE" ]; then
COMPOSE_ARGS=""
for f in $TARGET_COMPOSE; do
if [ -f "$f" ]; then
COMPOSE_ARGS="$COMPOSE_ARGS -f $f"
fi
done
UP_FLAGS="-d" # No --remove-orphans for selective updates to avoid killing other services
else
echo "🚀 Performing full deployment of all services..."
COMPOSE_ARGS=""
for f in $ALL_COMPOSE_FILES; do
COMPOSE_ARGS="$COMPOSE_ARGS -f $f"
done
UP_FLAGS="-d --remove-orphans"
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; 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
# Cooldown to allow daemon to settle (optional but kept for safety)
# echo "⏳ Waiting for Docker daemon to settle..."
# sleep 2
echo "🧹 Removing stale target containers by container_name..."
# Extract all explicitly defined container_names from compose files and remove them to prevent conflicts
remove_stale_containers() {
local files="$1"
local f
for f in $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
}
if [ -n "$TARGET_COMPOSE" ]; then
remove_stale_containers "$TARGET_COMPOSE"
else
remove_stale_containers "$ALL_COMPOSE_FILES"
fi
echo "🆙 Starting services..."
echo "🔍 Debug: Current docker containers:"
docker ps -a
$COMPOSE_CMD $COMPOSE_ARGS --env-file .env up $UP_FLAGS
# ── 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
-342
View File
@@ -1,342 +0,0 @@
name: Build and Push Docker Images
on:
push:
branches:
- main
paths:
- 'apps/**'
- '.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' }}
elysia-api: ${{ steps.filter.outputs['elysia-api'] == 'true' || steps.dispatch.outputs['elysia-api'] == 'true' || github.event_name == 'workflow_dispatch' }}
react-web: ${{ steps.filter.outputs['react-web'] == 'true' || steps.dispatch.outputs['react-web'] == 'true' || github.event_name == 'workflow_dispatch' }}
rust-auth: ${{ steps.filter.outputs['rust-auth'] == 'true' || steps.dispatch.outputs['rust-auth'] == '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@v6
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 "elysia-api=$(changed '^(apps/elysia(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/elysia\.Dockerfile$)')" >> "$GITHUB_OUTPUT"
echo "react-web=$(changed '^(apps/react(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/react\.Dockerfile$)')" >> "$GITHUB_OUTPUT"
echo "rust-auth=$(changed '^(apps/rust-auth(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/rust-auth\.Dockerfile$|\.gitmodules$')" >> "$GITHUB_OUTPUT"
echo "llm-api=$(changed '^(apps/llm-api(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/llm-api\.Dockerfile$|\.gitmodules$')" >> "$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|elysia-api|react-web|rust-auth|llm-api) ;;
*)
exit 1
;;
esac
if ! [[ "$SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then echo "::error::Invalid sha '$SHA'. Expected 40 hex characters"; exit 1; fi
SERVICES=(scraper-api elysia-api react-web rust-auth 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['elysia-api'] == 'true' || steps.dispatch.outputs['elysia-api'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "elysia-api" "docker-elysia" "apps/elysia"; fi
if [ "${{ steps.filter.outputs['react-web'] == 'true' || steps.dispatch.outputs['react-web'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "react-web" "docker-react" "apps/react"; fi
if [ "${{ steps.filter.outputs['rust-auth'] == 'true' || steps.dispatch.outputs['rust-auth'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "rust-auth" "docker-rust-auth" "apps/rust-auth"; 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" ;;
"elysia-api") REPO="https://github.com/asepharyana/asepharyana-hub-elysia.git" ;;
"react-web") REPO="https://github.com/asepharyana/asepharyana-hub-react.git" ;;
"rust-auth") REPO="https://github.com/asepharyana/asepharyana-hub-rust-auth.git" ;;
"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@v6
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"
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}:latest,${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 ;;
"elysia-api") echo "dockerfile=infra/docker/elysia.Dockerfile" >> $GITHUB_OUTPUT ;;
"react-web") echo "dockerfile=infra/docker/react.Dockerfile" >> $GITHUB_OUTPUT ;;
"rust-auth") echo "dockerfile=infra/docker/rust.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@v6
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["elysia-api"]="elysia.yml"
SERVICES["react-web"]="react.yml"
SERVICES["rust-auth"]="rust-auth.yml"
SERVICES["llm-api"]="llm-api.yml"
declare -A PATHS
PATHS["scraper-api"]="apps/scraper"
PATHS["elysia-api"]="apps/elysia"
PATHS["react-web"]="apps/react"
PATHS["rust-auth"]="apps/rust-auth"
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['elysia-api'] }}" == "true" ] && [ "$id" == "elysia-api" ]; then SHOULD_HAVE_RUN=true; fi
if [ "${{ needs.changes.outputs['react-web'] }}" == "true" ] && [ "$id" == "react-web" ]; then SHOULD_HAVE_RUN=true; fi
if [ "${{ needs.changes.outputs['rust-auth'] }}" == "true" ] && [ "$id" == "rust-auth" ]; 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
+101
View File
@@ -0,0 +1,101 @@
name: Deploy Infra Config
on:
push:
branches: [main]
paths:
- 'infra/caddy/**'
- 'infra/firewall/**'
- 'infra/systemd/**'
- 'infra/prometheus/**'
workflow_dispatch:
concurrency:
group: infra-deploy
cancel-in-progress: false
permissions:
contents: read
env:
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_USER: ${{ secrets.VPS_USER }}
jobs:
deploy-infra:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- name: Validate Caddyfile syntax
run: |
curl -fsSL "https://caddyserver.com/api/download?os=linux&arch=amd64" -o /tmp/caddy
chmod +x /tmp/caddy
/tmp/caddy validate --config infra/caddy/Caddyfile.prod --adapter caddyfile 2>&1 | tail -5
echo "✅ Caddyfile valid"
- name: Setup SSH key
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: Sync Caddyfile to VPS
run: |
set -e
ssh "$VPS_USER@$VPS_HOST" "sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak-previous"
scp -q infra/caddy/Caddyfile.prod "$VPS_USER@$VPS_HOST":/tmp/Caddyfile.new
ssh "$VPS_USER@$VPS_HOST" "sudo cp /tmp/Caddyfile.new /etc/caddy/Caddyfile && sudo rm -f /tmp/Caddyfile.new"
echo "✅ Caddyfile synced"
- name: Sync systemd drop-ins to VPS
run: |
set -e
if [ -d infra/systemd ]; then
for f in infra/systemd/*; do
[ -f "$f" ] || continue
base=$(basename "$f")
echo " syncing $base"
scp -q "$f" "$VPS_USER@$VPS_HOST":/tmp/"$base"
ssh "$VPS_USER@$VPS_HOST" "sudo mkdir -p /etc/systemd/system && sudo cp /tmp/$base /etc/systemd/system/$base && sudo rm -f /tmp/$base"
done
ssh "$VPS_USER@$VPS_HOST" "sudo systemctl daemon-reload"
echo "✅ systemd drop-ins synced"
else
echo "no infra/systemd/ files"
fi
- name: Sync prometheus targets to VPS
run: |
set -e
if [ -f infra/prometheus/targets.yml ]; then
scp -q infra/prometheus/targets.yml "$VPS_USER@$VPS_HOST":/tmp/targets.yml
ssh "$VPS_USER@$VPS_HOST" "sudo cp /tmp/targets.yml /etc/prometheus/targets.yml 2>/dev/null && sudo rm -f /tmp/targets.yml && sudo systemctl reload prometheus 2>/dev/null || true"
echo "✅ prometheus targets synced"
else
echo "no infra/prometheus/targets.yml"
fi
- name: Reload Caddy
run: |
ssh "$VPS_USER@$VPS_HOST" "sudo systemctl reload caddy || sudo systemctl restart caddy"
sleep 3
ssh "$VPS_USER@$VPS_HOST" "systemctl is-active caddy"
- name: Verify routes
run: |
set -e
for u in hub.asepharyana.my.id scraper.asepharyana.my.id tools.asepharyana.my.id wiki.asepharyana.my.id upload.asepharyana.my.id ai.asepharyana.my.id; do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "https://$u/" || true)
echo "$u -> $code"
case "$code" in
000|502|503|504) echo "::error::$u bad status $code"; exit 1 ;;
esac
done
echo "✅ All routes reachable"
-30
View File
@@ -1,30 +0,0 @@
name: Lint
on:
workflow_dispatch:
pull_request:
branches: [main]
paths:
- 'apps/*/src/**/*.ts'
- 'apps/*/src/**/*.tsx'
- 'biome.json'
push:
branches: [main]
paths:
- 'apps/*/src/**/*.ts'
- 'apps/*/src/**/*.tsx'
- 'biome.json'
jobs:
biome:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install -g @biomejs/biome
- run: biome ci . --no-errors-on-unmatched
-20
View File
@@ -1,20 +0,0 @@
name: Security
on:
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Every Monday
jobs:
codeql:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
security-events: write
steps:
- uses: actions/checkout@v6
- uses: github/codeql-action/init@v3
with:
languages: javascript-typescript, rust
- uses: github/codeql-action/analyze@v3
-36
View File
@@ -1,36 +0,0 @@
name: TypeCheck
on:
pull_request:
branches: [main]
paths:
- 'apps/react/src/**/*.ts'
- 'apps/react/src/**/*.tsx'
- 'apps/elysia/src/**/*.ts'
- 'apps/elysia/tsconfig.json'
- 'tsconfig.base.json'
jobs:
typecheck:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: oven/setup-bun@v2
with:
bun-version: latest
- name: TypeCheck apps/react
working-directory: apps/react
run: |
bun install
npx tsc --noEmit
- name: TypeCheck apps/elysia
working-directory: apps/elysia
run: |
bun install
bun run typecheck
-80
View File
@@ -1,80 +0,0 @@
name: Update Submodule Pointer
on:
repository_dispatch:
types: [submodule-updated]
permissions:
contents: write
jobs:
update:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Validate payload
env:
SERVICE: ${{ github.event.client_payload.service }}
SHA: ${{ github.event.client_payload.sha }}
run: |
set -euo pipefail
if [ -z "${SERVICE:-}" ]; then
echo "::error::Missing service in payload"
exit 1
fi
if [ -z "${SHA:-}" ]; then
echo "::error::Missing sha in payload"
exit 1
fi
if ! [[ "$SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::Invalid sha '$SHA'. Expected 40 hex characters."
exit 1
fi
case "$SERVICE" in
scraper-api|elysia-api|react-web|rust-auth) ;;
*)
echo "::error::Unsupported service '$SERVICE'"
exit 1
;;
esac
echo "Payload validated: $SERVICE → $SHA"
- name: Update submodule pointer
env:
SERVICE: ${{ github.event.client_payload.service }}
SHA: ${{ github.event.client_payload.sha }}
run: |
set -euo pipefail
echo "Updating ${SERVICE} to ${SHA}"
git submodule update --init "apps/${SERVICE}"
cd "apps/${SERVICE}"
# full fetch so we get tree objects for the target SHA
git fetch --depth=1 origin master 2>/dev/null || git fetch --depth=1 origin main
git checkout "${SHA}"
cd "${GITHUB_WORKSPACE}"
git add "apps/${SERVICE}"
git diff --cached --quiet && exit 0
git config user.name "monrepo-bot"
git config user.email "monrepo-bot@users.noreply.github.com"
git commit -m "chore: update ${SERVICE} to ${SHA:0:12}"
for attempt in {1..3}; do
if git pull --rebase origin main && git push origin main; then
echo "✅ Push succeeded on attempt $attempt"
exit 0
fi
echo "⚠️ Push attempt $attempt/3 failed; retrying..."
git rebase --abort 2>/dev/null || true
sleep 3
done
echo "::error::Failed to push submodule update after 3 attempts"
exit 1
+6 -3
View File
@@ -1,4 +1,3 @@
apps/gmw/
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# compiled output
@@ -12,6 +11,7 @@ node_modules
.turbo/
# IDEs and editors
/.idea
.serena/
.project
.classpath
.c9/
@@ -35,16 +35,18 @@ npm-debug.log
yarn-error.log
testem.log
/typings
.playwright-mcp/
# System Files
.DS_Store
Thumbs.db
.claude
.claude/*
!.claude/skills/
!.claude/settings.json
# Next.js
.next
out
**/.codegraph/**
**/.claude/**
test-output
**/**.env
**/**.env.**
@@ -62,3 +64,4 @@ docs/todo.md
**/vendor/
.codegraph/
result
-15
View File
@@ -1,15 +0,0 @@
[submodule "apps/elysia"]
path = apps/elysia
url = https://github.com/asepharyana/asepharyana-hub-elysia.git
[submodule "apps/react"]
path = apps/react
url = https://github.com/asepharyana/asepharyana-hub-react.git
[submodule "apps/scraper"]
path = apps/scraper
url = https://github.com/asepharyana/asepharyana-hub-scraper.git
[submodule "apps/rust-auth"]
path = apps/rust-auth
url = https://github.com/asepharyana/asepharyana-hub-rust-auth.git
[submodule "apps/llm-api"]
path = apps/llm-api
url = https://github.com/asepharyana/asepharyana-hub-llm-api.git
-8
View File
@@ -1,8 +0,0 @@
{
"mcpServers": {
"next-devtools": {
"command": "npx",
"args": ["-y", "next-devtools-mcp@latest"]
}
}
}
-81
View File
@@ -1,81 +0,0 @@
# Agent Protocol v10: Strategic Execution and Diagnostic Directives
1. **Mandatory Executability.**
- **Directive:** All generated outputs must be syntactically correct and directly executable by the target system's interpreter, compiler, or shell.
- **Constraint:** Placeholders and pseudo-code are forbidden. Every output must be a complete, functional artifact.
2. **Data and State Integrity.**
- **Directive:** All generated code must strictly adhere to declared data structures, schemas, and the target language's type system.
- **Constraint:** Any operation producing a type mismatch, schema violation, or logical inconsistency is an invalid operation and must be discarded.
3. **Atomic and Consistent State Modification.**
- **Directive:** Modification of a shared resource must be performed as an atomic operation or within an ACID-compliant transaction.
- **Constraint:** Operations that can lead to race conditions or inconsistent state are prohibited. Immutability is the required default.
4. **Zero-Trust Security (Inviolable Safety Constraint).**
- **Directive:** Secrets must not be stored as literal values in source code. They must be loaded at runtime from a secure external source.
- **Constraint:** Generated access policies must adhere to the Principle of Least Privilege.
5. **Supply Chain Security (Inviolable Safety Constraint).**
- **Directive:** All external dependencies must be sourced from trusted repositories and defined in a lockfile for deterministic resolution.
- **Constraint:** The dependency graph must be scanned for known CVEs. Dependencies with critical vulnerabilities are prohibited.
6. **Deterministic and Reproducible Builds.**
- **Directive:** From a given source commit, the build process must produce a byte-for-byte identical artifact in every execution.
- **Constraint:** All automated tests must be deterministic. A regression test codifying the fixed bug's failure condition must be included with the fix.
7. **Structured, Traceable Logging.**
- **Directive:** All processes must emit structured (JSON) logs for significant events. All log entries for a request must contain the same unique trace ID.
- **Constraint:** Error conditions must be explicitly logged with context and propagated. Errors must not be silently suppressed.
8. **Strict API Contract Enforcement.**
- **Directive:** All network communication must strictly conform to its published, versioned API contract.
- **Constraint:** Any network call violating the contract must be rejected. Breaking changes require a major version increment (SemVer).
9. **Distributed System Consensus.**
- **Directive:** Changes to shared state across a distributed system are committed only after a formal consensus algorithm confirms quorum.
- **Constraint:** Nodes in a minority partition must enter a read-only or unavailable state to prevent a split-brain scenario.
10. **Execution Planning and Pre-flight Validation (Think Before Acting).**
- **Directive:** For any multi-step task, a detailed execution plan (sequence of commands and file modifications) must be formulated before any state-modifying action is taken.
- **Constraint:** Before executing a command, the agent must first use a validation or dry-run flag (e.g., `--dry-run`, `--check`) if available. The operation may only proceed if the pre-flight check passes without error.
11. **Post-Failure Root Cause Analysis (Evaluate Mistakes from Logs).**
- **Directive:** Upon command execution failure (non-zero exit code), the current execution plan must be halted, and the agent must enter a diagnostic mode.
- **Constraint:** In diagnostic mode, the agent is required to: 1) Capture and parse the complete `stdout` and `stderr` logs. 2) Identify the specific error message or stack trace. 3) Correlate the error with the last command to form a root cause hypothesis. 4) Formulate a new, corrective execution plan based on the analysis.
12. **Context-Aware File System Operations.**
- **Directive:** Before modifying any file, its full content must be read to establish context. All edits must be based on an in-memory understanding of the file's current state.
- **Constraint:** Blind file operations, such as stream-based search-and-replace without structural validation, are strictly prohibited.
13. **Idempotent State Transitions.**
- **Directive:** Operations that modify state must be designed to be idempotent wherever the protocol allows.
- **Constraint:** Executing the same operation multiple times must result in the same final system state as executing it only once.
14. **Resource Lifecycle Management.**
- **Directive:** All finite system resources (e.g., file handles, network sockets) must be explicitly released after use.
- **Constraint:** The agent must generate code that prevents resource leaks, utilizing language-specific constructs like `try-with-resources` or `defer`.
15. **Configuration as Code (CaC).**
- **Directive:** All configuration must be defined and versioned in source-controlled files.
- **Constraint:** Manual, out-of-band configuration changes are prohibited. Versioned files are the single source of truth.
16. **Atomic and Semantic Version Control.**
- **Directive:** All code changes must be organized into logically atomic commits representing one complete unit of work.
- **Constraint:** Commit messages must adhere to a defined specification (e.g., Conventional Commits).
17. **User Authority and Command Primacy.**
- **Directive:** User-provided instructions and corrections are the definitive source of truth and have the highest operational priority.
- **Constraint:** The agent must immediately adapt its process to align with user directives. Rejected solutions must not be proposed again.
18. **Precedent-Based Improvement.**
- **Directive:** User-approved outputs and successful patterns must be recorded and prioritized as precedents for subsequent tasks.
- **Constraint:** Performance, security, and code quality must not degrade.
19. **Optimization by Explicit Consent.**
- **Directive:** The agent may identify and propose optimizations with a technical justification and supporting metrics.
- **Constraint:** The agent is prohibited from applying any self-initiated optimization without an explicit "approve" command from the user.
20. **System Hierarchy and Safety Overrides.**
- **Directive:** The operational control hierarchy is absolute: 1) **User Command**, 2) **Inviolable Safety Directives (#4, #5)**, 3) **Standard Operational Directives**.
- **Constraint:** If a command conflicts with an Inviolable Directive, the agent must halt, report the conflict and risk, and await a revised command.
-1
View File
@@ -1 +0,0 @@
22.11.0
-2
View File
@@ -1,2 +0,0 @@
engine-strict=true
save-exact=true
-1
View File
@@ -1 +0,0 @@
22.11.0
-8
View File
@@ -1,8 +0,0 @@
{
"recommendations": [
"nrwl.angular-console",
"biomejs.biome",
"ms-playwright.playwright",
"firsttris.vscode-jest-runner"
]
}
-23
View File
@@ -1,23 +0,0 @@
{
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit"
}
}
-332
View File
@@ -1,332 +0,0 @@
# Architecture
## Hub Repository Structure Overview
```
asepharyana-hub/
├── apps/ # Application services (Git submodules)
│ ├── elysia/ # Realtime API (Bun/Elysia/Drizzle/Redis)
│ ├── react/ # Frontend SPA (React/Vite/TanStack)
│ ├── rust-auth/ # IAM & auth service (Axum/SeaORM)
│ └── 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
### Backend Services
| Service | Language/Runtime | Framework | Database | Key Libraries |
| ------------- | ---------------- | ---------- | ------------------------ | ------------------------------------------------------------ |
| **rust-auth** | Rust | Axum 0.8 | PostgreSQL (sqlx) | sqlx, jsonwebtoken, argon2, redis, opentelemetry, prometheus |
| **elysia** | TypeScript/Bun | Elysia 1.4 | PostgreSQL (Drizzle ORM) | Drizzle ORM, Redis (ioredis), JWT (jose), OTel, Swagger |
| **scraper** | _(submodule)_ | — | — | — |
### Frontend
| Service | Framework | Build Tool | Key Libraries |
| --------- | --------- | ---------- | --------------------------------------------------------------------------------------------------- |
| **react** | React 19 | Vite 7 | TanStack Router + Query, Three.js/React Three Fiber, Tailwind CSS 4, Zustand, Recharts, tsParticles |
### 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 |
| Cache | Redis (Alpine) | Session store, rate limit counters, caching |
| 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**: `infra/traefik/traefik.yaml` — 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
docker compose -f traefik.yml -f shared.yml -f elysia.yml -f react.yml ... up -d
```
### Tailscale Networking
```mermaid
graph TB
subgraph "Tailnet (100.64.0.0/10)"
IMRNES["imrnes (100.108.1.124)"]
ORANGE["orange (100.96.248.86)"]
ARCH["archlinux (100.114.19.66)"]
LAPTOP["laptop-2f6e1iph (100.86.195.29)"]
end
subgraph "imrnes Services"
PG[(PostgreSQL)]
REDIS[Redis]
end
subgraph "orange Containers"
TRAEFIK[Traefik :443]
RUST_AUTH[rust-auth :3000]
ELYSIA[elysia-api :4092]
REACT[react-web :80]
SCRAPER[scraper-api :4091]
end
TRAEFIK --> RUST_AUTH
TRAEFIK --> ELYSIA
TRAEFIK --> REACT
TRAEFIK --> SCRAPER
RUST_AUTH -.->|Tailscale IP| PG
ELYSIA -.->|Tailscale IP| PG
RUST_AUTH -.->|Tailscale IP| REDIS
ELYSIA -.->|Tailscale IP| REDIS
style IMRNES fill:#3a7,color:#fff
style ORANGE fill:#37a,color:#fff
style ARCH fill:#773,color:#fff
style LAPTOP fill:#777,color:#fff
```
Container-to-Tailscale connectivity requires a systemd service that adds a route to the main routing table:
```
ip route add 100.64.0.0/10 dev tailscale0 table main
```
This is managed by `/etc/systemd/system/tailscale-routes.service` on the `orange` VPS.
## Data Flow
### Request Flow (Production)
```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 → orange 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->>Redis: GET/SET via Tailscale
Redis-->>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
- Every push to `main` triggers Docker builds for changed services
- Images are tagged with both `latest` and `sha-<short-sha>` (e.g., `sha-b0ef947`)
- Compose files are auto-updated to pin the new SHA tag
- This enables deterministic rollbacks by reverting the compose file change
### VPS Deployment
The `orange` VPS (Tailscale `100.96.248.86`) hosts all application containers:
1. GitHub Actions SSHes into the VPS
2. Production secrets are written as `.env`
3. The repo is synchronized via `git pull`
4. Changed compose files are detected by `git diff`
5. Docker images are pulled (with retry logic for transient failures)
6. Old containers are removed by `container_name`
7. `docker compose up -d` brings up the new containers
8. Traefik automatically detects the new containers via Docker provider
### Selective Deployment
The deploy workflow supports selective updates — if only `infra/compose/elysia.yml` changed, only `elysia-api` is pulled and recreated, avoiding disruption to other services.
```mermaid
graph TB
subgraph "Orange VPS"
DIR[/root/asepharyana-hub/]
ENV[.env]
COMPOSE[infra/compose/*.yml]
NET[app-shared-net]
DIR -->|git pull| COMPOSE
ENV -->|docker compose --env-file| COMPOSE
COMPOSE -->|docker compose pull| IMAGES[(GHCR Images)]
COMPOSE -->|docker compose up -d| CONT[Containers]
CONT --> NET
end
subgraph "GitHub Actions"
BUILD[Build & Push]
DEPLOY[Deploy Workflow]
BUILD -->|trigger| DEPLOY
DEPLOY -->|SSH| DIR
end
IMAGES -->|registry| GHCR[ghcr.io/asepharyana]
```
## Submodule Strategy
Each application lives in its own Git repository and is imported as a submodule into `apps/`. This approach:
- **Enables independent development** — each service can be developed, tested, and versioned separately
- **Pins exact commits** — the super-repository tracks exact submodule SHAs, enabling reproducible deployments
- **Supports `repository_dispatch`** — when a submodule receives a push, it can trigger the super-repository to build and deploy only that service
### Submodule Lifecycle
1. Developer pushes to a submodule (e.g., `apps/elysia`)
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
### Updating Submodules
```bash
# Update a single submodule to latest
cd apps/elysia
git checkout main
git pull
cd ../..
git add apps/elysia
git commit -m "chore(elysia): update submodule to latest"
# Update all submodules
git submodule update --remote --merge
```
## Service Mesh & Inter-Service Communication
```mermaid
graph LR
subgraph "External"
WWW[Internet]
end
subgraph "Orange VPS"
TRAEFIK[Traefik :443]
subgraph "app-shared-net"
REACT[react-web<br/>:80]
ELYSIA[elysia-api<br/>:4092]
RUST_AUTH[rust-auth<br/>:3000]
SCRAPER[scraper-api<br/>:4091]
REDIS[redis<br/>:6379]
end
end
subgraph "Imrnes VPS"
PG[(PostgreSQL<br/>:5432)]
REDIS_IMR[Redis<br/>:6379]
end
WWW -->|HTTPS| TRAEFIK
TRAEFIK --> REACT
TRAEFIK --> ELYSIA
TRAEFIK --> RUST_AUTH
TRAEFIK --> SCRAPER
ELYSIA -->|Tailscale| PG
RUST_AUTH -->|Tailscale| PG
ELYSIA -->|internal| REDIS
RUST_AUTH -->|internal| REDIS
ELYSIA -->|Tailscale| REDIS_IMR
RUST_AUTH -->|Tailscale| REDIS_IMR
```
## Observability
- **Prometheus metrics**: Available on rust-auth via `axum-prometheus`
- **Traefik access logs**: JSON format, logged at INFO level
- **Dashboard**: Traefik dashboard at `traefik.asepharyana.my.id` (secured)
+24
View File
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2026-08-02]
### Changed
- **Infra overhaul**: Docker + Traefik dihapus dari produksi → Caddy 2.11.4 (reverse proxy, auto-TLS LE, HTTP/3) + Nix/systemd services.
- **Port migration**: semua service pindah ke port 4000-an (hub 4003, tools 4007/4008, scraper 4091, llm-api 4010, dll).
- **DB via PgBouncer pool**: semua service konek ke imrnes 100.121.180.82:6432 (bukan :5432 langsung).
- **Secrets**: Bitwarden Secrets Manager (BWS) sebagai central secret store, wrapper bws-exec.
- **Flake**: dibatasi x86_64-linux (nixpkgs 26.11 drop darwin).
## [Unreleased]
### Changed
@@ -12,8 +22,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Restructured the repository into a lightweight hub repo with standalone app submodules.
- Simplified root tooling to plain `package.json` scripts and per-service commands.
- Kept infrastructure, deployment workflows, and documentation in the root hub repo.
- Cleaned up Traefik SSL config: removed legacy `asephstech`/`asephscloud` cert references, synced volume mounts, fixed `api.insecure`.
- Pruned `.env.example` from 145 legacy vars (Firebase, Discord, Coolify, Portainer, YouTube, etc.) to 30 focused vars.
- Optimized `scraper.Dockerfile`: removed Node.js and Chromium from runtime image.
- Simplified CI/CD workflows: removed orphan container reference, commented code blocks.
- Cleaned up scripts: removed stale MySQL config, fixed package references, simplified update-deps.
- Added NATS + JetStream message broker infrastructure (`infra/compose/nats.yml`).
- Added Dapr runtime infrastructure: placement service, sidecar pattern, pub/sub + state store components.
- Integrated Dapr sidecar into scraper service (`infra/compose/scraper.yml`).
- Updated deployment order: shared → NATS → Dapr → Traefik → apps.
- Added `docs/add-dapr-service.md` guide for adding Dapr to new services.
### Removed
- Removed deprecated services from apps, compose files, Dockerfiles, Traefik routes, and workflows.
- Removed stale monorepo orchestration configs and hook tooling from the root repo.
- Removed `.nvmrc` (duplicate of `.node-version`), `renovate.json` (using dependabot).
- Removed stale documentation: `dependency-map.md`, `observability.md`, `handoff-log.jsonl`, `quality-gates.json`, `workflow-state.json`.
- Removed deprecated `docs/superpowers/` design docs.
- Removed `infra/config/mysql/` (no active MySQL service) and `docs/config/squid.conf.archived`.
+130
View File
@@ -0,0 +1,130 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Repository Overview
Asepharyana Hub is a **hub monorepo** for Asep Haryana Saputra's portfolio ecosystem. Application services live in separate repos imported as Git submodules under `apps/`. Production infrastructure: Caddy reverse proxy + Nix/systemd services (Docker/Traefik removed 2026-08-02; legacy configs under `infra/` marked LEGACY).
```
asepharyana-hub/
├── apps/ # Git submodules — each app is its own repo
│ ├── hub/ # Personal portfolio SPA (asepharyana-hub-hub)
│ └── scraper/ # Rust scraper API (asepharyana-hub-scraper)
├── docs/ # ADRs, deployment guide, new-app guide
├── infra/
│ ├── compose/ # Docker Compose files (LEGACY — Docker dihapus)
│ ├── dapr/ # Dapr config + component definitions
│ ├── docker/ # Dockerfiles (LEGACY)
│ └── traefik/ # Reverse proxy config (static + dynamic)
├── scripts/ # Utility scripts (cleanup, update-deps, git hooks)
└── .github/workflows/ # CI/CD pipelines
```
### Submodule Strategy
- Each app in `apps/` is a separate Git repo imported as a submodule. Code changes happen in the submodule repo, not here.
- Submodule pointers are updated by CI/CD (via `repository_dispatch` or manual commit).
- Current submodules:
- `apps/hub` → `asepharyana/asepharyana-hub-hub`
- `apps/scraper` → `asepharyana/asepharyana-hub-scraper`
- `apps/llm-api` → `asepharyana/asepharyana-hub-llm-api`
- `apps/tools` → `asepharyana/asepharyana-hub-tools`.
### Infrastructure Stack
- **Caddy 2.11.4** — reverse proxy, TLS termination (auto-LE), HTTP/3, zstd/gzip, keep-alive tuning (`/etc/caddy/Caddyfile`, ref `infra/caddy/Caddyfile.prod`)
- **NATS + JetStream** — message broker with persistent streaming
- **Dapr** — sidecar runtime (pub/sub abstraction, state management, service invocation)
- **Redis (Alpine)** — cache, session store, Dapr state store & pub/sub backend
- **Prometheus** — metrics backend with `file_sd_configs` target files.
- **Jaeger** — distributed tracing backend (all-in-one), OTLP receiver
- **Tailscale** — secure overlay network between VPS nodes (PostgreSQL on `imrnes`, containers on `orangevps`)
### Monitoring
- **Hub dashboard** at `/dashboard` (Next.js client page, auto-refresh 15s)
- **Dashboard API** at `/api/dashboard` — returns JSON with systemd services, Jaeger traces, Prometheus metrics (RPS, latency, errors, node CPU/RAM/Disk)
- **Prometheus** scrapes node-exporter + app metrics endpoints
### Networking
- All services run as Nix/systemd units; inter-service via 127.0.0.1:<port>.
- Caddy handles all external HTTP/S traffic on port 443 (and HTTP/3 UDP).
- Cross-VPS traffic (DB, Redis) goes through Tailscale (`100.64.0.0/10`). Container-to-Tailscale connectivity requires a route in the main routing table (managed by `tailscale-routes.service`).
## Commands
```bash
make init-submodules # Initialize submodules after clone
make dev # Start shared dev infrastructure (Redis)
make update-submodules # Update all submodules to latest
bun run check # Biome lint + format + write
bun run ci # Biome CI mode (no writes, exit code on issues)
bun run format # Format only
bun run lint # Lint only
# Nix build (produksi): nix build .#default --impure --option sandbox false
```
### Validate YAML
```bash
python -c "import pathlib, yaml; [yaml.safe_load(open(p)) for p in pathlib.Path('infra').rglob('*.yml')]"
for f in infra/compose/*.yml; do docker compose -f "$f" config >/dev/null && echo "OK $f"; done
```
## CI/CD Workflows
| Workflow | Trigger | Action |
|----------|---------|--------|
| `lint.yml` | PR/push to main touching `*.json`, `*.js`, `biome.json` | `bun run ci` (Biome lint) |
| `deploy.yml` | Push to main | nix build → nix copy ssh:// → systemctl restart |
| `docker-build-push.yml` | LEGACY (Docker dihapus) | LEGACY |
| `security.yml` | PR to main + weekly Monday | CodeQL analysis (Rust) |
| `update-submodule.yml` | `repository_dispatch` | Update submodule pointer in hub repo |
### Deployment Order
1. `shared.yml` (Redis)
2. `nats.yml` (NATS + JetStream)
3. `dapr.yml` (Dapr placement)
4. `traefik.yml` (Reverse proxy)
5. Service compose files (app + Dapr sidecar)
### Secrets Required for Deploy
`SSH_PRIVATE_KEY`, `VPS_HOST`, `VPS_USER`, `VPS_TARGET_DIR`, `ENV_FILE_PRODUCTION`
## Infrastructure Patterns
### Compose File Pattern
Each service gets one compose file. Containers join `app-shared-net` with a `container_name` alias for DNS. The network is declared `external: true`.
### Dapr Sidecar Pattern
Each app gets a companion `daprd` sidecar container. Dapr components (pubsub, statestore) are mounted from `infra/dapr/components/`. The sidecar communicates with NATS for pub/sub and Dapr placement for actor coordination.
### Caddy Routing
- Site blocks in `/etc/caddy/Caddyfile` (ref `infra/caddy/Caddyfile.prod`)
- Subdomain pattern: `<service>.asepharyana.my.id` and `<service>.asepharya.web.id`
- Auto-TLS via Let's Encrypt
- Shared handler snippet `(proxy)`: `encode zstd gzip` + security headers + keep-alive tuning
### Image Tagging
- `sha-<short-sha>` — immutable, for deterministic rollbacks
- `latest` — mutable, for convenience
- Registry: `ghcr.io/asepharyana/asepharyana-hub/<service>`
- Build cache: `sha-<short>-buildcache` (registry-based caching)
## Adding a New Service
1. Create a separate repo for the app code
2. Add as submodule: `git submodule add <url> apps/<name>`
3. Create Nix flake package + systemd unit
4. Create compose file in `infra/compose/` (app + Dapr sidecar)
5. Add Caddy site block in `/etc/caddy/Caddyfile`
6. Add build job in `.github/workflows/docker-build-push.yml`
7. See `docs/add-new-app.md` for full guide
## Commit Convention
Format: `<type>(<scope>): <description>`
Types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `ci`, `perf`, `style`
Scopes: `scraper`, `infra`, `ci`, `dapr`, `nats`, `docs`, `deps`, `scripts`, `root`
Scope is required. Use imperative mood. No period at end of subject line. Co-Authored-By footer for AI-generated commits.
+17 -69
View File
@@ -14,9 +14,8 @@
## Prerequisites
- **Git** with LFS support
- **Node.js** >= 22.11.0 (via `.node-version` or `.nvmrc`)
- **Node.js** >= 22.11.0 (via `.node-version`)
- **Bun** >= 1.3.11 (package manager)
- **Rust** >= 1.89.0 (for Rust services)
- **Docker** and **Docker Compose** (for shared infrastructure)
## Local Setup
@@ -40,18 +39,14 @@ This checks out all submodules at the pinned commit (not `main`). The submodules
| Path | Remote |
| ---------------- | --------------------------------------- |
| `apps/elysia` | `asepharyana/asepharyana-hub-elysia` |
| `apps/scraper` | `asepharyana/asepharyana-hub-scraper` |
| `apps/react` | `asepharyana/asepharyana-hub-react` |
| `apps/rust-auth` | `asepharyana/asepharyana-hub-rust-auth` |
### 3. Install Dependencies per Service
Install dependencies for TypeScript/Bun services:
```bash
cd apps/elysia && bun install && cd ../..
cd apps/react && npm install && cd ../..
cd apps/scraper && bun install && cd ../..
```
### 4. Start Shared Infrastructure
@@ -75,76 +70,42 @@ Key variables to configure:
| Variable | Description |
| -------------- | ----------------------------------------------------- |
| `DATABASE_URL` | PostgreSQL connection (Tailscale IP to `imrnes` VPS) |
| `REDIS_URL` | Redis connection (`redis://localhost:6379` for local) |
| `JWT_SECRET` | JWT signing secret |
| `GITHUB_TOKEN` | GitHub personal access token |
## Development Workflow
### Running Services
**Rust API (rust-auth):**
```bash
cd apps/rust-auth
cargo run
```
**Elysia API (elysia):**
```bash
cd apps/elysia
bun run dev
```
**React Frontend (react):**
```bash
cd apps/react
npm run dev
```
Refer to each service's own documentation for setup and development instructions.
### API Documentation
- Rust OpenAPI: `http://localhost:4091/docs`
- Elysia Swagger: `http://localhost:4092/docs`
- Elysia AsyncAPI: `http://localhost:4092/docs-ws`
Refer to each service's own documentation for API docs and endpoints.
## Coding Standards
### Linting
- **ESLint** with `@antfu/eslint-config` for TypeScript/JavaScript
- **Cargo Clippy** for Rust
- **Biome** for TypeScript/JavaScript formatting and linting
Run linting:
```bash
# TypeScript/JavaScript
eslint . --no-error-on-unmatched-pattern
# Rust specific
cd apps/rust-auth && cargo clippy -- -D warnings
bun run check
```
### Formatting
- **Prettier** for TypeScript/JavaScript/Markdown (config in `.prettierrc`)
- Single quotes, 100 print width, 2-space indent, trailing commas
- **Cargo fmt** for Rust
- **Biome** for TypeScript/JavaScript
- **EditorConfig** for general formatting (`.editorconfig`)
```bash
# Prettier
prettier --write .
# Rust
cd apps/rust-auth && cargo fmt
# Format all
bun run format
```
### Rust Configuration
Rust services use edition `2024` with stable toolchain (nightly features may be used).
## Commit Message Format
@@ -177,18 +138,15 @@ This project enforces **Conventional Commits** for all commit messages.
### Examples
```
feat(rust-auth): add OAuth2 Google login flow
fix(elysia): handle null JWT payload in auth middleware
chore: update eslint config to v10
feat(scraper): add new data source integration
chore: update biome config to v10
docs: add API endpoint documentation for scraper
refactor(react): extract Header component from App
test(elysia): add unit tests for rate limiter
ci: migrate to CodeQL v3
```
### Scopes
Common scopes: `rust-auth`, `elysia`, `react`, `scraper`, `infra`, `ci`, `deps`
Common scopes: `scraper`, `infra`, `ci`, `deps`
## Pull Request Process
@@ -201,10 +159,9 @@ Common scopes: `rust-auth`, `elysia`, `react`, `scraper`, `infra`, `ci`, `deps`
3. **Run checks locally** before pushing:
```bash
cd apps/react && npx tsc --noEmit
eslint . --no-error-on-unmatched-pattern
```
```bash
bun run check
```
4. **Push and open a PR** against `main`. CI will automatically run:
- **Lint** — ESLint across changed TypeScript files
@@ -218,7 +175,7 @@ Common scopes: `rust-auth`, `elysia`, `react`, `scraper`, `infra`, `ci`, `deps`
- Updates compose manifests to use the new SHA tags
6. **Deployment Pipeline** triggers after a successful Docker build:
- SSHes into the VPS (`orange`, Tailscale IP `100.96.248.86`)
- SSHes into the VPS (`orangevps`, Tailscale IP `100.79.111.61`)
- Pulls updated Docker images
- Recreates only the changed containers
- All services share the `app-shared-net` Docker network
@@ -227,13 +184,4 @@ Common scopes: `rust-auth`, `elysia`, `react`, `scraper`, `infra`, `ci`, `deps`
## Adding a New Service
See `docs/add-new-app.md` for the complete step-by-step guide. In summary:
1. Create the app in `apps/<name>`
2. Add it as a Git submodule in `.gitmodules`
3. Register it in `infra/compose/<name>.yml`
4. Add a Dockerfile at `infra/docker/<name>.Dockerfile`
5. Add Traefik routing config in `infra/traefik/dynamic/apps.yaml`
6. Add CI entries in `.github/workflows/docker-build-push.yml`
7. Add compose file to the deploy script in `deploy-docker.yml`
8. Add any required GitHub secrets for the service
See `docs/add-new-app.md` for the complete step-by-step guide.
-32
View File
@@ -1,32 +0,0 @@
.PHONY: help dev lint format test clean update-submodules deploy init-submodules status
SHELL := /bin/bash
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
dev: ## Start development infrastructure (Redis etc.)
docker compose -f infra/compose/shared.yml up -d
lint: ## Run Biome linter
biome lint .
format: ## Format code with Biome
biome format --write .
clean: ## Clean build artifacts
rm -rf apps/*/dist apps/*/.next apps/*/target 2>/dev/null || true
rm -rf node_modules 2>/dev/null || true
update-submodules: ## Update all git submodules to latest remote
git submodule update --remote --merge --recursive
deploy: ## Deploy to VPS (triggers GitHub Actions)
@echo "Push to main to trigger deployment, or run:"
@echo " gh workflow run deploy-docker.yml"
init-submodules: ## Initialize all submodules
git submodule update --init --recursive
status: ## Show submodule status
git submodule status
+41 -186
View File
@@ -1,201 +1,56 @@
# Asepharyana Hub
# Asepharyana Infra
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.
Reverse-proxy & infrastructure config for [orangevps](https://asepharyana.my.id) (45.127.35.244).
## Services
> **Status (2026-08-28):** Repo ini dulunya monorepo `asepharyana-hub` dengan submodule aplikasi.
> Kini **murni repo infra**: Caddy reverse proxy (source of truth), firewall, drop-in systemd,
> dan docs. Build + deploy tiap aplikasi pindah ke repo masing-masing (self-contained CI).
| Service | Path | Default Local Port | Notes |
| :---------- | :--------------- | :----------------- | :---------------------------------------------------------------------------- |
| Rust API | `apps/rust-auth` | `4091` | API utama (Axum + SeaORM), scraping, image proxy/cache, metrics, OpenAPI docs |
| Elysia API | `apps/elysia` | `4092` | API realtime/auth/chat/quiz (Elysia + Bun + Drizzle + Redis) |
| React Web | `apps/react` | `3000` | Frontend React/Vite |
| Scraper | `apps/scraper` | — | Web scraper service |
## Repositori Aplikasi (self-contained build & deploy)
## Infrastructure
| Repo | Deskripsi | Deploy unit |
|------|-----------|-------------|
| [`asepharyana/hub`](https://github.com/asepharyana/hub) | Portfolio SPA (Next.js, port 4003, dashboard) | `hub` |
| [`asepharyana/scraper`](https://github.com/asepharyana/scraper) | Rust/Axum scraper API (port 4091) | `scraper` |
| [`asepharyana/llm-api`](https://github.com/asepharyana/llm-api) | Rust LLM API (llama.cpp, port 8080) | `llm-api` |
File compose berada di `infra/compose/`:
Tiap repo punya `flake.nix` + `.github/workflows/deploy.yml` sendiri:
`nix build .#<pkg>` → **push ke Attic binary cache** (`attic.asepharyana.my.id/asepharyana`) → VPS substitute via `nix-store --realise` → `nix-env --profile` → `systemctl restart`.
Push ke `main` (atau `workflow_dispatch`) langsung deploy; tidak ada lagi pointer submodule.
- `traefik.yml`: reverse proxy Traefik untuk semua layanan.
- `shared.yml`: Redis.
- `rust-auth.yml`, `elysia.yml`, `react.yml`, `scraper.yml`: manifest deploy per service (image GHCR bertag SHA).
## Infra di Repo Ini
Dockerfile per service berada di `infra/docker/`.
| Path | Isi |
|------|-----|
| `infra/caddy/Caddyfile.prod` | **Source of truth** `/etc/caddy/Caddyfile` (auto-deploy via CI) |
| `infra/firewall/firewall.sh` | deny-by-default iptables (SSH/80/443/4013/Tailscale/TCPShield) |
| `infra/firewall/99-*.conf` | sysctl hardenings |
| `infra/prometheus/targets.yml` | file_sd targets |
| `infra/systemd/scraper-otel.conf` | drop-in OTEL untuk scraper service |
| `docs/` | arsitektur + operasional (VPS) |
## Docker Image Builds
## CI/CD
Build image via Dockerfile:
| Workflow | Trigger | Aksi |
|----------|---------|------|
| `caddy-deploy.yml` | push main menyentuh `infra/**`, atau manual | sync `Caddyfile.prod` → `/etc/caddy/Caddyfile` → reload → verifikasi rute |
## Local Setup / Snapshot VPS
```bash
docker build -f infra/docker/rust.Dockerfile -t rust-auth:latest .
docker build -f infra/docker/elysia.Dockerfile -t elysia-api:latest .
docker build -f infra/docker/react.Dockerfile -t react-web:latest .
docker build -f infra/docker/scraper.Dockerfile -t scraper-api:latest .
# Clone infra repo
git clone https://github.com/asepharyana/infra.git
# Diff config live vs repo
diff /etc/caddy/Caddyfile infra/caddy/Caddyfile.prod
# Koneksi VPS (public)
ssh code@45.127.35.244
```
Tag and push:
## Menambahkan Service Baru / Subdomain
```bash
SHORT_SHA=$(git rev-parse --short HEAD)
1. Aplikasi punya repo sendiri + `deploy.yml` (lihat template di repo app yang ada).
2. Registrasi unit systemd di VPS (manual/ops) → app jalan di port lokal.
3. Tambah site block di `infra/caddy/Caddyfile.prod` (pola `import proxy <port>`) → push → CI reload Caddy.
4. (Opsional) Tambah unit ke `MONITORED_UNITS` dashboard hub di repo `asepharyana/hub`.
docker tag rust-auth:latest ghcr.io/asepharyana/asepharyana-hub/rust-auth:sha-$SHORT_SHA
docker push ghcr.io/asepharyana/asepharyana-hub/rust-auth:sha-$SHORT_SHA
# repeat for elysia-api, react-web, scraper-api
```
## Local Development
### 1) Jalankan dependency bersama
```bash
docker compose -f infra/compose/shared.yml up -d
```
### 2) Jalankan service yang dibutuhkan
```bash
# Rust API
cd apps/rust-auth
cargo run
# Elysia API
cd apps/elysia
bun install
bun run dev
# React web
cd apps/react
npm install
npm run dev
```
## API Docs and Monitoring
- Rust OpenAPI: `/docs`
- Elysia Swagger: `/docs`
- Elysia AsyncAPI viewer: `/docs-ws`
## Deployment Notes
- 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.
## Networking & Tailscale
### Arsitektur
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.108.1.124` | PostgreSQL (`hub`), Redis |
| `orange` | `100.96.248.86` | App containers (Traefik, 9Router, dll) |
| `archlinux` | `100.114.19.66` | _(development machine)_ |
| `laptop-2f6e1iph` | `100.86.195.29` | _(offline)_ |
### 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
# Manual (hilang setelah reboot)
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
Service yang connect ke Tailscale IP:
```env
# PostgreSQL di imrnes
DATABASE_URL=postgres://user:pass@100.108.1.124:5432/dbname
# Redis di imrnes
REDIS_URL=redis://100.108.1.124:6379
```
#### Persistent Systemd Service
File: `/etc/systemd/system/tailscale-routes.service`
```ini
[Unit]
Description=Add Tailscale routes to main routing table
After=tailscaled.service
Requires=tailscaled.service
[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
```
Install & enable:
```bash
sudo tee /etc/systemd/system/tailscale-routes.service > /dev/null << 'EOF'
[Unit]
Description=Add Tailscale routes to main routing table
After=tailscaled.service
Requires=tailscaled.service
[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.108.1.124', () => { 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
MIT
Lihat `docs/add-new-app.md` untuk detail.
Submodule apps/elysia deleted from b8f4b5806e
Submodule apps/llm-api deleted from c77ceee83f
Submodule apps/react deleted from 124d2f88bd
Submodule apps/rust-auth deleted from 6e695f07e8
Submodule apps/scraper deleted from 80c96eaa42
-73
View File
@@ -1,73 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
"preset": "recommended",
"a11y": {
"useButtonType": "warn",
"useIframeTitle": "warn",
"noSvgWithoutTitle": "warn",
"useAltText": "warn"
},
"complexity": {
"noForEach": "off",
"noStaticOnlyClass": "off",
"noThisInStatic": "off",
"useOptionalChain": "off"
},
"suspicious": {
"noArrayIndexKey": "warn",
"noConsole": "off",
"noExplicitAny": "warn"
},
"correctness": {
"noUnusedVariables": "error",
"useParseIntRadix": "off"
},
"style": {
"noNonNullAssertion": "off"
}
}
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
},
"javascript": {
"jsxRuntime": "transparent",
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"trailingCommas": "all",
"semicolons": "always",
"arrowParentheses": "always"
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"files": {
"ignoreUnknown": false,
"includes": [
"**",
"!**/.opencode",
"!**/dist",
"!**/out",
"!**/build",
"!**/node_modules",
"!**/target",
"!**/coverage",
"!**/*.env",
"!**/*.env.*",
"!**/apps/react/src/routeTree.gen.ts"
]
}
}
-31
View File
File diff suppressed because one or more lines are too long
+33 -115
View File
@@ -1,124 +1,42 @@
# Menambahkan Aplikasi Baru ke Deployment
# Menambahkan Service Baru / Subdomain
Dokumen ini menjelaskan langkah menambahkan service baru ke `asepharyana-hub`. Root repo berfungsi sebagai hub: source aplikasi berada di `apps/<nama-app>` sebagai submodule, sedangkan Docker Compose, Traefik, dan workflow deploy tetap berada di root repo.
Panduan untuk menambahkan service baru di ekosistem `asepharyana/infra` (2026-08-28+, pasca monorepo).
## 1. Buat repo aplikasi
## Prinsip
Buat repo baru di GitHub dengan pola nama:
- **Aplikasi hidup di repo sendiri** (`hub`, `scraper`, `llm-api`) dengan
`flake.nix` + `.github/workflows/deploy.yml` mandiri. Repo infra TIDAK berisi kode app.
- Repo infra (`asepharyana/infra`) hanya mengatur **reverse proxy & config VPS**.
```text
https://github.com/asepharyana/asepharyana-hub-<nama-app>.git
## Langkah
1. **Buat repo aplikasi** (contoh pola: `asepharyana/scraper`).
2. **Tambahkan `flake.nix`** di repo app — derivasi Nix (lihat template di repo app yang ada:
Next.js/bun atau Rust/cargo). Nama paket = nama unit systemd.
3. **Tambahkan `.github/workflows/deploy.yml`** (pola `nix build .#<pkg>` → `nix copy ssh://`
→ `nix-env --profile /nix/var/nix/profiles/<pkg> --set` → `systemctl restart <pkg>`).
Secrets yang dibutuhkan: `SSH_PRIVATE_KEY`, `VPS_HOST`, `VPS_USER`.
4. **Di VPS**: buat user systemd + unit (mis. `/etc/systemd/system/<app>.service`,
`ExecStart=/usr/local/bin/bws-exec <app> /nix/var/nix/profiles/<app>/bin/<app>`),
pastikan app jalan di port lokal.
5. **Tambah site block** di `infra/caddy/Caddyfile.prod` (pola `import proxy <port>`),
push ke `main` → CI `caddy-deploy.yml` sync + reload + verifikasi rute.
6. **(Opsional)** Tambah unit ke `MONITORED_UNITS` di hub dashboard
(repo `asepharyana/hub`, `src/app/api/dashboard/route.ts`).
## Contoh site block Caddy
```caddyfile
nama-app.asepharyana.my.id {
import proxy <PORT>
}
```
Lalu tambahkan ke root hub sebagai submodule:
## Verifikasi
```bash
git submodule add https://github.com/asepharyana/asepharyana-hub-<nama-app>.git apps/<nama-app>
git submodule update --init --recursive
```
# Dari local
curl -s -o /dev/null -w '%{http_code}\n' https://nama-app.asepharyana.my.id/
## 2. Tambahkan Dockerfile
Tambahkan Dockerfile runtime di `infra/docker/<nama-app>.Dockerfile`.
Gunakan root repo sebagai build context agar Dockerfile bisa mengakses submodule path:
```bash
docker build -f infra/docker/<nama-app>.Dockerfile -t <nama-app>:local .
```
## 3. Tambahkan Compose file
Buat `infra/compose/<nama-app>.yml`:
```yaml
services:
<nama-app>:
container_name: <nama-app>
image: ghcr.io/asepharyana/asepharyana-hub/<nama-app>:sha-<short-sha>
restart: always
networks:
app-shared-net:
aliases:
- <nama-app>
env_file:
- ../../.env
networks:
app-shared-net:
name: app-shared-net
external: true
```
Gunakan `app-shared-net` agar service dapat diakses oleh Traefik dan service lain.
## 4. Tambahkan route Traefik
Update `infra/traefik/dynamic/apps.yaml`:
```yaml
http:
routers:
<nama-app>:
rule: 'Host(`<subdomain>.asepharyana.my.id`) || Host(`<subdomain>.asepharyana.web.id`)'
entryPoints:
- websecure
tls: {}
middlewares:
- common-chain@file
service: <nama-app>-service
services:
<nama-app>-service:
loadBalancer:
servers:
- url: 'http://<nama-app>:<port>'
```
## 5. Update workflow build
Update `.github/workflows/docker-build-push.yml`:
1. Tambahkan path detection untuk `apps/<nama-app>` dan `infra/docker/<nama-app>.Dockerfile`.
2. Tambahkan service ke matrix build.
3. Tambahkan mapping Dockerfile di step `Docker metadata`.
4. Tambahkan mapping compose file dan submodule path di step `Update tags and submodules`.
## 6. Update workflow deploy
Tambahkan compose file baru ke `ALL_COMPOSE_FILES` di `.github/workflows/deploy-docker.yml`:
```bash
infra/compose/<nama-app>.yml
```
## 7. Update dokumentasi
Update file berikut bila service baru mengubah arsitektur publik:
- `README.md`
- `ARCHITECTURE.md`
- `infra/README.md`
- `.gitmodules`
## 8. Validasi
Jalankan validasi YAML dan compose rendering:
```bash
python - <<'PY'
import pathlib, yaml
for path in pathlib.Path('infra').rglob('*.yml'):
with path.open() as fh:
yaml.safe_load(fh)
print(f'OK {path}')
for path in pathlib.Path('infra').rglob('*.yaml'):
with path.open() as fh:
yaml.safe_load(fh)
print(f'OK {path}')
PY
for f in infra/compose/*.yml; do
docker compose -f "$f" config >/dev/null && echo "OK $f"
done
```
# Dari VPS
systemctl status <app>
+22 -36
View File
@@ -1,52 +1,38 @@
# ADR 0001: Use a Hub Repository with App Submodules
# ADR 0001: Infra Repo — Reverse Proxy Config Only (Submodules Removed)
## Status
Accepted
**Superseded** (2026-08-28) — lihat ADR ini sebagai arsip keputusan awal.
## Context
## Context (aslinya)
The project contains multiple independent application services that share one deployment surface: Docker Compose, Traefik routing, GitHub Actions workflows, and operational documentation.
Proyek awal memakai `asepharyana-hub` sebagai monorepo: aplikasi di `apps/<service>` sebagai
git submodule, infra (compose/traefik/dokumen/CI) terpusat di root. Keputusan itu masuk akal
saat semua service berbagi satu deployment surface.
The services should be developed and versioned independently, while deployment infrastructure should remain centralized so production routing and compose manifests stay consistent.
## Decision (aslinya)
## Decision
Gunakan `asepharyana-hub` sebagai root hub: app code submodule, infra + CI di root.
Use `asepharyana-hub` as the root hub repository.
## Superseded By
- Application code lives under `apps/<service>` as Git submodules.
- Infrastructure lives in the root repo under `infra/`.
- Documentation lives in the root repo under `docs/`.
- CI/CD workflows live in the root repo under `.github/workflows/`.
- Root tooling stays minimal: `package.json`, Prettier, ESLint, Makefile helpers, and deployment scripts.
Mulai **2026-08-28** repo dirombak:
Current app submodules:
| Service | Path | Remote |
| -------------- | ---------------- | --------------------------------------- |
| Elysia API | `apps/elysia` | `asepharyana/asepharyana-hub-elysia` |
| React frontend | `apps/react` | `asepharyana/asepharyana-hub-react` |
| Rust auth API | `apps/rust-auth` | `asepharyana/asepharyana-hub-rust-auth` |
| Scraper API | `apps/scraper` | `asepharyana/asepharyana-hub-scraper` |
- **Parent `asepharyana-hub` → `asepharyana/infra`** — murni config reverse proxy (Caddy),
firewall, systemd drop-ins, docs. CI hanya untuk deploy Caddy.
- **App repos di-rename & self-contained**: `hub`, `scraper`, `tools`, `llm-api`.
Masing-masing punya `flake.nix` + `.github/workflows/deploy.yml` sendiri
(`nix build → nix copy → nix-env --profile → systemctl restart`).
- **Submodule dihapus** — tidak ada lagi pointer submodule / repository_dispatch chain.
- `update-submodule.yml`, `notify-parent.yml`, matrix `nix-build.yml` dihapus.
## Consequences
### Positive
- Each app can evolve in its own repository.
- The hub pins exact submodule revisions for reproducible deployments.
- Deployment infrastructure remains centralized and easier to audit.
- Root tooling stays lightweight and does not impose one build system on every service.
- CI tiap app independen: push ke repo app langsung build+deploy, tak perlu 2 hop.
- Parent kecil & fokus: diff Caddyfile mudah di-audit.
- Tanpa submodule = tanpa `dubious ownership` / pointer drift / fetchGit pin.
### Negative
- Developers must understand Git submodule workflows.
- Updating a service requires updating the submodule pointer in the hub repo.
- Cross-service changes require coordinating commits across multiple repositories.
### Mitigations
- Keep `.gitmodules` accurate and minimal.
- Use `scripts/sync-submodules.sh` for local checkout consistency.
- Document service-addition steps in `docs/add-new-app.md`.
- Keep GitHub Actions responsible for Docker image builds, compose tag updates, and deployments.
- Koordinasi cross-repo manual (app + Caddy bila perlu port baru).
- Repo lama `asepharyana-hub-*` redirect ke nama baru (GitHub auto).
+128
View File
@@ -0,0 +1,128 @@
# ADR 0002: Production `.env` via GitHub Encrypted Secret
> **LEGACY (2026-08-28):** Repo `asepharyana-hub` sudah dirombak → `asepharyana/infra`.
> Workflow lama yang SCP `.env` ke VPS tidak dipakai lagi (deploy app pindah ke repo masing-masing,
> secrets via Bitwarden `bws-exec`). ADR ini dipertahankan sebagai arsip.
## Status
Accepted (archived)
## Context
The project runs on a remote VPS (`orangevps`, IP `45.127.35.244`) that hosts multiple services via Docker Compose. These services require environment variables (database credentials, API keys, tokens) that must not be committed to the repository.
The production `.env` file on the VPS is **not** a copy of the committed `.env` in the repo root — it contains additional secrets (Portainer tokens, Discord bot tokens, etc.) that only exist in production.
Previously, the `.env` file on the VPS was edited manually via SSH, which led to drift between the local `.env` and the production `.env`. When the database server IP or port changed in the local `.env`, the production `.env` was not updated, causing service outages.
## Decision
The production `.env` file is stored as a **GitHub Actions encrypted secret** named `ENV_FILE_PRODUCTION`. During deployment, the `.github/workflows/deploy-docker.yml` workflow writes this secret to a file and SCPs it to the VPS.
### Flow
```
GitHub Secret (ENV_FILE_PRODUCTION)
│
▼ (deploy-docker.yml)
echo "$ENV_FILE_PRODUCTION" > .env.prod
scp .env.prod → VPS:$VPS_TARGET_DIR/.env
│
▼ (docker compose --env-file .env up)
Container reads $DATABASE_URL, $JWT_SECRET, etc.
```
### How to update
```bash
# 1. Read current content from the VPS
ssh root@45.127.35.244 "cat <VPS app dir, e.g. /home/code/hub>/.env"
# 2. Pipe updated content to the GitHub secret
# (requires gh CLI with repo access)
cat /path/to/updated-env | gh secret set ENV_FILE_PRODUCTION --repo asepharyana/asepharyana-hub
# 3. Trigger a redeploy to push it to the VPS
gh workflow run deploy-docker.yml
# OR apply immediately on the VPS (for hotfix):
ssh root@45.127.35.244 "sed -i 's|OLD_VALUE|NEW_VALUE|' <VPS app dir, e.g. /home/code/hub>/.env"
# Then restart affected containers
```
## Server Topology
| Host | IP | Role |
|------|----|------|
| `orangevps` (VPS) | `45.127.35.244` | Docker host: Traefik, scraper-api, Redis, NATS, Dapr |
| `imrnes` (bare-metal) | `100.121.180.82` (Tailscale) | PostgreSQL (port 6432), Redis (port 6379), Browserless |
## Database
| Variable | Value |
|----------|-------|
| `DATABASE_URL` | `postgres://asephs:hunterz@100.121.180.82:6432/hub` |
| `REDIS_URL` | `redis://redis:6379` (Docker network, overridden per-service) |
| `EXTERNAL_BROWSERLESS_WS` | `ws://43.134.105.109:3001/?token=...` (external proxy) |
> **Important:** The Docker Compose `environment:` section uses variable interpolation (`${DATABASE_URL}`), which is resolved from the `--env-file .env` at compose time — NOT from the service's `env_file`. Both must be kept in sync.
## Docker Compose Project Structure
The VPS runs a single Docker Compose project named `compose` composed of multiple files:
```bash
<VPS app dir, e.g. /home/code/hub>/infra/compose/
├── traefik.yml # Reverse proxy (TLS termination, routing)
├── shared.yml # Redis
├── nats.yml # NATS message broker + JetStream
├── dapr.yml # Dapr placement service
├── scraper.yml # Scraper API + Dapr sidecar
└── observability.yml # OTel Collector + Jaeger + Dashboard
```
All files are always included together for dependency resolution:
```bash
docker compose \
--env-file .env \
-f infra/compose/traefik.yml \
-f infra/compose/shared.yml \
-f infra/compose/scraper.yml \
-f infra/compose/nats.yml \
-f infra/compose/dapr.yml \
-f infra/compose/observability.yml \
up -d
```
## GitHub Secrets Required
| Secret | Description |
|--------|-------------|
| `SSH_PRIVATE_KEY` | SSH key for VPS access |
| `VPS_HOST` | `45.127.35.244` |
| `VPS_USER` | `root` |
| `VPS_TARGET_DIR` | `<VPS app dir, e.g. /home/code/hub>` |
| `ENV_FILE_PRODUCTION` | Full `.env` content for production |
## Consequences
### Positive
- Environment is version-controlled via GitHub Secrets audit log.
- No risk of committing secrets to the repo.
- Deployment is fully automated — `.env` is pushed on every deploy.
- Easy to rotate secrets: update `ENV_FILE_PRODUCTION` and redeploy.
### Negative
- The secret is opaque — you cannot diff it or review changes via PR.
- If the secret falls out of sync with the local `.env`, services silently break on next deploy.
- Requires `gh` CLI or GitHub UI to update — not a simple file edit.
### Mitigations
- Keep the **committed `.env`** in the repo root as the source of truth for non-secret values (database URL, ports, API endpoints).
- Document any manual SSH hotfix at the same time as updating the GitHub secret.
- Run `gh secret set ENV_FILE_PRODUCTION` with the latest server `.env` content after any hotfix.
@@ -0,0 +1,47 @@
# ADR 0003: Rename Repositori & Pisahkan CI per Aplikasi
## Status
Accepted (2026-08-28)
## Context
Monorepo `asepharyana-hub` (app submodule + infra + CI terpusat) punya kelemahan:
- CI build+deploy semua app menyatu di parent (`nix-build.yml` matrix) — setiap push app
butuh 2 hop (notify-parent → update-submodule → nix-build), rawan drift pointer.
- Nama `asepharyana-hub` ambigu (parent & app prefix sama), dan submodule menambah kompleksitas.
## Decision
Rombak total:
| Lama | Baru | Peran |
|------|------|-------|
| `asepharyana-hub` | `asepharyana/infra` | Reverse proxy (Caddy) + firewall + systemd + docs. CI: caddy-deploy saja. |
| `asepharyana-hub-hub` | `asepharyana/hub` | Portfolio SPA. CI mandiri (deploy.yml). |
| `asepharyana-hub-scraper` | `asepharyana/scraper` | Rust scraper API. CI mandiri. |
| `asepharyana-hub-tools` | `asepharyana/tools` | Tools stack (gateway/workers/frontend). CI mandiri. |
| `asepharyana-hub-llm-api` | `asepharyana/llm-api` | Rust LLM API. CI mandiri. |
| `asepharyana-hub-guide` | `asepharyana/hub-guide` | (tidak di-root; plugin guide — diarsipkan) |
Setiap app repo mendapat:
- `flake.nix` (derivasi build sendiri, tanpa fetchGit submodule)
- `.github/workflows/deploy.yml` (nix build → nix copy → nix-env --profile → systemctl restart)
- Secret `SSH_PRIVATE_KEY`, `VPS_HOST`, `VPS_USER`
Parent `infra` mendapat:
- Hapus semua submodule + `update-submodule.yml` + matrix `nix-build.yml`
- `.github/workflows/caddy-deploy.yml` (sync Caddyfile → reload → verify)
- Docs diarahkan ulang.
## Consequences
- **Positif**: CI per-app independen & cepat; parent kecil; tanpa submodule = tanpa fetchGit pin
/ dubious-ownership / pointer churn. Rename GitHub auto-redirect URL lama.
- **Negatif**: koordinasi manual bila app butuh port baru di Caddy; workflow lama di
downstream (skill/cron) perlu update referensi.
## Referensi
- `docs/add-new-app.md` — proses menambah service baru
- `infra/caddy/Caddyfile.prod` — pola site block
-39
View File
@@ -1,39 +0,0 @@
# Squid Proxy Configuration (Archived)
**Status**: Archived; not referenced by active infrastructure.
**Date archived**: 2026-06-04
**Original location**: `infra/squid.conf`
**Reason**: No active Docker Compose service mounts or starts Squid. The config is preserved here for reference if Squid proxying is restored later.
## Original configuration
```squid
# Allow localhost access only
acl localhost src 127.0.0.1/32 ::1/128
http_access allow localhost
# Allow Docker bridge subnets (Adjusted for our expected subnets)
acl docker_bridge src 172.16.0.0/12
http_access allow docker_bridge
# Deny all other access
http_access deny all
# Standard port - Explicitly bind to all interfaces
http_port 0.0.0.0:3128
# Hide source info
forwarded_for off
via off
```
## Restore checklist
1. Copy this config back to `infra/squid.conf`.
2. Add a Squid service to an active compose file.
3. Mount the config into the Squid container.
4. Document which services should use the proxy.
5. Validate network exposure and authentication before production use.
@@ -1,686 +0,0 @@
# Moonrepo Migration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Migrate the monorepo to moonrepo for centralized task orchestration, caching, and consistent dependency management across all 7 apps.
**Architecture:** moonrepo sits as a task orchestration layer above the existing Nix build system. `.moon/tasks/` holds shared task definitions (TypeScript and Rust). Each app gets a `moon.yml` with tags and inter-project dependencies. Nix, Docker Compose, git submodules, and infra remain untouched.
**Tech Stack:** moonrepo CLI, Node 22, Bun, TypeScript 5+, Cargo (Rust)
---
### Task 1: Install moonrepo CLI and scaffold .moon/ directory
**Files:**
- Create: `.moon/workspace.yml`
- Create: `.moon/toolchain.yml`
- Create: `.moon/tasks/` (directory)
- [ ] **Step 1: Install moonrepo CLI via curl**
```bash
curl -fsSL https://moonrepo.dev/install/moon.sh | bash
```
- [ ] **Step 2: Verify installation**
Run: `moon --version`
Expected: prints version number (e.g., `moon 1.x.x`)
- [ ] **Step 3: Create .moon/ scaffold directories**
```bash
mkdir -p .moon/tasks
```
- [ ] **Step 4: Commit**
```bash
git add .moon/
git commit -m "chore: scaffold .moon/ directory for moonrepo"
```
---
### Task 2: Configure workspace.yml
**Files:**
- Create: `.moon/workspace.yml`
- [ ] **Step 1: Write .moon/workspace.yml**
```yaml
# https://moonrepo.dev/docs/config/workspace
$schema: "https://moonrepo.dev/schemas/workspace.json"
projects:
- "apps/*"
vcs:
manager: "git"
defaultBranch: "main"
runner:
implicitDeps:
# TypeScript apps: lint depends on build (for typecheck path)
- "typescript-build.build"
cacheTtl: 604800
```
- [ ] **Step 2: Validate config structure**
Run: `moon check`
Expected: no errors (will warn about missing project configs — expected)
- [ ] **Step 3: Commit**
```bash
git add .moon/workspace.yml
git commit -m "chore: configure moonrepo workspace with project glob"
```
---
### Task 3: Configure toolchain.yml
**Files:**
- Create: `.moon/toolchain.yml`
- [ ] **Step 1: Write .moon/toolchain.yml**
```yaml
# https://moonrepo.dev/docs/config/toolchain
$schema: "https://moonrepo.dev/schemas/toolchain.json"
node:
version: "22.11.0"
packageManager: "bun"
bun:
version: "1.3.11"
typescript:
syncProjectReferences: true
createMissingConfig: false
routeOutDirToCache: false
```
- [ ] **Step 2: Commit**
```bash
git add .moon/toolchain.yml
git commit -m "chore: configure moonrepo toolchain (Node 22, Bun 1.3)"
```
---
### Task 4: Create shared TypeScript task definitions
**Files:**
- Create: `.moon/tasks/typescript-build.yml`
- Create: `.moon/tasks/typescript-lint.yml`
- Create: `.moon/tasks/typescript-test.yml`
- [ ] **Step 1: Write .moon/tasks/typescript-build.yml**
```yaml
# https://moonrepo.dev/docs/config/tasks
$schema: "https://moonrepo.dev/schemas/tasks.json"
tasks:
build:
command: "bun run build"
inputs:
- "src/**/*"
- "tsconfig.json"
- "package.json"
outputs:
- ".next"
- "dist"
- ".output"
options:
cache: true
dev:
command: "bun run dev"
local: true
options:
persistent: true
```
- [ ] **Step 2: Write .moon/tasks/typescript-lint.yml**
```yaml
# https://moonrepo.dev/docs/config/tasks
$schema: "https://moonrepo.dev/schemas/tasks.json"
tasks:
lint:
command: "bun run lint"
inputs:
- "src/**/*"
- "eslint.config.mjs"
- "tsconfig.json"
options:
cache: false
typecheck:
command: "bun run check-types"
inputs:
- "src/**/*"
- "tsconfig.json"
options:
cache: false
```
- [ ] **Step 3: Write .moon/tasks/typescript-test.yml**
```yaml
# https://moonrepo.dev/docs/config/tasks
$schema: "https://moonrepo.dev/schemas/tasks.json"
tasks:
test:
command: "bun test"
inputs:
- "src/**/*"
- "test/**/*"
- "tests/**/*"
- "vitest.config.ts"
options:
cache: false
e2e:
command: "noop"
local: true
options:
cache: false
```
- [ ] **Step 4: Commit**
```bash
git add .moon/tasks/typescript-build.yml .moon/tasks/typescript-lint.yml .moon/tasks/typescript-test.yml
git commit -m "chore: add shared TypeScript task definitions"
```
---
### Task 5: Create shared Rust task definitions
**Files:**
- Create: `.moon/tasks/rust-build.yml`
- Create: `.moon/tasks/rust-test.yml`
- Create: `.moon/tasks/rust-lint.yml`
- [ ] **Step 1: Write .moon/tasks/rust-build.yml**
```yaml
# https://moonrepo.dev/docs/config/tasks
$schema: "https://moonrepo.dev/schemas/tasks.json"
tasks:
build:
command: "cargo build --release"
platform: system
inputs:
- "src/**/*"
- "Cargo.toml"
- "Cargo.lock"
- "build.rs"
outputs:
- "target/release/*"
options:
cache: true
envFile: false
dev:
command: "cargo run"
platform: system
local: true
options:
persistent: true
envFile: false
```
- [ ] **Step 2: Write .moon/tasks/rust-test.yml**
```yaml
# https://moonrepo.dev/docs/config/tasks
$schema: "https://moonrepo.dev/schemas/tasks.json"
tasks:
test:
command: "cargo test"
platform: system
inputs:
- "src/**/*"
- "Cargo.toml"
- "Cargo.lock"
- "tests/**/*"
options:
cache: false
envFile: false
```
- [ ] **Step 3: Write .moon/tasks/rust-lint.yml**
```yaml
# https://moonrepo.dev/docs/config/tasks
$schema: "https://moonrepo.dev/schemas/tasks.json"
tasks:
lint:
command: "cargo clippy -- -D warnings"
platform: system
inputs:
- "src/**/*"
- "Cargo.toml"
options:
cache: false
envFile: false
fmt-check:
command: "cargo fmt --check"
platform: system
inputs:
- "src/**/*"
options:
cache: false
envFile: false
```
- [ ] **Step 4: Commit**
```bash
git add .moon/tasks/rust-build.yml .moon/tasks/rust-test.yml .moon/tasks/rust-lint.yml
git commit -m "chore: add shared Rust task definitions"
```
---
### Task 6: Create per-app moon.yml for TypeScript apps
**Files:**
- Create: `apps/nextjs/moon.yml`
- Create: `apps/elysia/moon.yml`
- Create: `apps/solidjs/moon.yml`
- [ ] **Step 1: Write apps/nextjs/moon.yml**
```yaml
# https://moonrepo.dev/docs/config/project
$schema: "https://moonrepo.dev/schemas/project.json"
type: "application"
language: "typescript"
platform: "node"
tags:
- "lang:typescript"
- "type:frontend"
dependsOn:
- id: "rust-auth"
- id: "elysia"
```
- [ ] **Step 2: Write apps/elysia/moon.yml**
```yaml
# https://moonrepo.dev/docs/config/project
$schema: "https://moonrepo.dev/schemas/project.json"
type: "application"
language: "typescript"
platform: "bun"
tags:
- "lang:typescript"
- "type:backend"
```
- [ ] **Step 3: Write apps/solidjs/moon.yml**
```yaml
# https://moonrepo.dev/docs/config/project
$schema: "https://moonrepo.dev/schemas/project.json"
type: "application"
language: "typescript"
platform: "bun"
tags:
- "lang:typescript"
- "type:frontend"
dependsOn:
- id: "elysia"
- id: "rust-auth"
```
- [ ] **Step 4: Commit**
```bash
git add apps/nextjs/moon.yml apps/elysia/moon.yml apps/solidjs/moon.yml
git commit -m "chore: add moon.yml for TypeScript apps (nextjs, elysia, solidjs)"
```
---
### Task 7: Create per-app moon.yml for Rust apps
**Files:**
- Create: `apps/rust/moon.yml`
- Create: `apps/rust-auth/moon.yml`
- Create: `apps/leptos/moon.yml`
- [ ] **Step 1: Write apps/rust/moon.yml**
```yaml
# https://moonrepo.dev/docs/config/project
$schema: "https://moonrepo.dev/schemas/project.json"
type: "application"
language: "rust"
platform: "system"
tags:
- "lang:rust"
- "type:backend"
fileGroups:
sources:
- "src/**/*.rs"
- "Cargo.toml"
- "Cargo.lock"
- "build.rs"
- "rustfmt.toml"
```
- [ ] **Step 2: Write apps/rust-auth/moon.yml**
```yaml
# https://moonrepo.dev/docs/config/project
$schema: "https://moonrepo.dev/schemas/project.json"
type: "application"
language: "rust"
platform: "system"
tags:
- "lang:rust"
- "type:backend"
fileGroups:
sources:
- "src/**/*.rs"
- "Cargo.toml"
- "Cargo.lock"
```
- [ ] **Step 3: Write apps/leptos/moon.yml**
```yaml
# https://moonrepo.dev/docs/config/project
$schema: "https://moonrepo.dev/schemas/project.json"
type: "application"
language: "rust"
platform: "system"
tags:
- "lang:rust"
- "type:frontend"
dependsOn:
- id: "rust"
fileGroups:
sources:
- "src/**/*.rs"
- "Cargo.toml"
- "Cargo.lock"
- "Trunk.toml"
- "rust-toolchain.toml"
```
- [ ] **Step 4: Commit**
```bash
git add apps/rust/moon.yml apps/rust-auth/moon.yml apps/leptos/moon.yml
git commit -m "chore: add moon.yml for Rust apps (rust, rust-auth, leptos)"
```
---
### Task 8: Create moon.yml for 9router
**Files:**
- Create: `apps/9router/moon.yml`
- [ ] **Step 1: Write apps/9router/moon.yml**
```yaml
# https://moonrepo.dev/docs/config/project
$schema: "https://moonrepo.dev/schemas/project.json"
type: "application"
language: "unknown"
platform: "system"
tags:
- "type:router"
fileGroups:
sources:
- "src/**/*"
- "next.config.mjs"
- "package.json"
```
- [ ] **Step 2: Commit**
```bash
git add apps/9router/moon.yml
git commit -m "chore: add moon.yml for 9router"
```
---
### Task 9: Update flake.nix — add moon CLI to devShell
**Files:**
- Modify: `flake.nix`
- [ ] **Step 1: Add moon to nativeBuildInputs in flake.nix**
Find the `devShells.default` block in `flake.nix`. Add `moon` to `nativeBuildInputs`:
```
devShells.default = pkgs.mkShell {
name = "ultimate-asepharyana-dev";
nativeBuildInputs = with pkgs; [
rustToolchain
bun
nodejs_22
pkg-config
openssl
trunk
wasm-bindgen-cli
binaryen
process-compose
mysql84
redis
minio-client
gh
git
moon # <-- add this line
];
# ... shellHook unchanged
};
```
- [ ] **Step 2: Verify Nix can still evaluate the flake**
Run: `nix flake check --no-build 2>&1 | head -20`
Expected: no evaluation errors
- [ ] **Step 3: Commit**
```bash
git add flake.nix
git commit -m "chore: add moon CLI to Nix devShell"
```
---
### Task 10: Update .gitignore
**Files:**
- Modify: `.gitignore`
- [ ] **Step 1: Add moonrepo cache entries to .gitignore**
Append to `.gitignore`:
```gitignore
# moonrepo
.moon/cache
.~moon
```
- [ ] **Step 2: Commit**
```bash
git add .gitignore
git commit -m "chore: add moonrepo cache entries to .gitignore"
```
---
### Task 11: Validate installation with moon check
**Files:** (none — validation only)
- [ ] **Step 1: Run moon check**
```bash
moon check
```
Expected: `OK` or zero errors. If warnings about unresolved project IDs appear, verify that `apps/*` glob in `workspace.yml` matches all project directories.
- [ ] **Step 2: Run moon query projects**
```bash
moon query projects
```
Expected: lists all 7 projects with their tags: nextjs, elysia, solidjs, rust, rust-auth, leptos, 9router
- [ ] **Step 3: Verify tag queries work**
```bash
moon query projects --tag lang:typescript
```
Expected: nextjs, elysia, solidjs
```bash
moon query projects --tag lang:rust
```
Expected: rust, rust-auth, leptos
- [ ] **Step 4: Commit (if any fixes were needed)**
No commit needed if check passes clean.
---
### Task 12: Test — moon run build on TypeScript apps
**Files:** (none — test only)
- [ ] **Step 1: Build nextjs**
```bash
moon run nextjs:build
```
Expected: `next build` runs successfully, outputs to `.next/`
- [ ] **Step 2: Build elysia**
```bash
moon run elysia:build
```
Expected: `bun build` runs successfully, outputs to `dist/`
- [ ] **Step 3: Build solidjs**
```bash
moon run solidjs:build
```
Expected: `vinxi build` runs successfully, outputs to `.output/`
- [ ] **Step 4: Verify caching on second build (nextjs)**
```bash
moon run nextjs:build
```
Expected: `Cached` — no rebuild, uses moonrepo cache
---
### Task 13: Test — moon run lint and test
**Files:** (none — test only)
- [ ] **Step 1: Run lint across TypeScript apps**
```bash
moon run :lint
```
Expected: all apps with a `lint` task run it. Note failures as they exist pre-migration (not caused by moonrepo).
- [ ] **Step 2: Run tests across TypeScript apps**
```bash
moon run :test
```
Expected: all apps with a `test` task run it.
- [ ] **Step 3: Run tag-scoped commands**
```bash
moon run --tag lang:typescript :build
```
Expected: builds nextjs, elysia, solidjs only (not Rust apps).
---
### Task 14: Final commit and documentation
**Files:**
- Modify: `.gitignore` (if any final updates)
- [ ] **Step 1: Final moon check**
```bash
moon check
```
Expected: clean, no errors.
- [ ] **Step 2: Commit any remaining changes**
```bash
git status
```
If nothing outstanding, move on.
- [ ] **Step 3: Verify the full workspace is clean**
```bash
git status
```
Expected: working tree clean.
@@ -1,712 +0,0 @@
# PostgreSQL Migration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Migrate elysia (Drizzle ORM + MySQL) and rust (SeaORM + MySQL) apps to PostgreSQL via direct cutover.
**Architecture:** Export MySQL data, convert schema to PostgreSQL format, import into target PostgreSQL instance, update app drivers and connection strings, deploy and validate.
**Tech Stack:** MySQL (source), PostgreSQL (target), Drizzle ORM (elysia), SeaORM (rust), pgloader or manual conversion for schema migration.
---
## File Structure
### Elysia App Changes
- `apps/elysia/src/db/lib/database.ts` — Replace mysql2 driver with postgres driver
- `apps/elysia/src/db/lib/schema.ts` — Replace mysqlTable with pgTable, update column types
- `apps/elysia/package.json` — Replace mysql2 with pg dependency
- `.env` or config file — Update DATABASE_URL to PostgreSQL connection string
### Rust App Changes
- `apps/rust/Cargo.toml` — Replace sqlx-mysql feature with sqlx-postgres
- `apps/rust/src/infra/db_setup.rs` — Update DbBackend::MySql to DbBackend::Postgres, adjust SQL syntax
- Config/environment — Update DATABASE_URL to PostgreSQL connection string
### Migration Artifacts
- `mysql_backup.sql` — MySQL dump (created during migration, not committed)
- `converted.sql` — PostgreSQL-compatible dump (created during migration, not committed)
---
## Task Breakdown
### Task 1: Backup MySQL and Export Schema
**Files:**
- Create: `mysql_backup.sql` (temporary, not committed)
- [ ] **Step 1: Export MySQL database**
```bash
cd /mnt/code/bp3/ultimate-asepharyana.tech
mysqldump -u <mysql_user> -p <mysql_password> -h <mysql_host> <database_name> > mysql_backup.sql
```
Expected: File created with full schema + data. Verify file size > 1MB (contains data).
-[]**Step 2: Verify backup integrity**
```bash
# Check row counts in backup
grep "INSERT INTO" mysql_backup.sql | wc -l
```
Expected: Multiple INSERT statements present. Note row counts for later validation.
- [ ] **Step 3: Document backup location**
Store `mysql_backup.sql` in safe location (not in git). This is rollback insurance.
---
### Task 2: Convert MySQL Schema to PostgreSQL
**Files:**
- Create: `converted.sql` (temporary, not committed)
- [ ] **Step 1: Install pgloader (if not present)**
```bash
# macOS
brew install pgloader
# Linux (Ubuntu/Debian)
sudo apt-get install pgloader
# Or use Docker
docker run --rm -v $(pwd):/data pgloader/pgloader pgloader /data/mysql_backup.sql postgresql://asephs:hunterz@100.108.1.124:5432/hub
```
- [ ] **Step 2: Convert MySQL dump to PostgreSQL**
```bash
pgloader mysql_backup.sql postgresql://asephs:hunterz@100.108.1.124:5432/hub
```
Or manually convert if pgloader unavailable:
- Replace `AUTO_INCREMENT` with `SERIAL` or `BIGSERIAL`
- Replace `DATETIME` with `TIMESTAMP`
- Replace backticks with double quotes
- Update index syntax for PostgreSQL
Expected: Conversion completes without errors. Check for warnings about type conversions.
- [ ] **Step 3: Verify conversion output**
```bash
# If using pgloader, it creates converted.sql automatically
# If manual, save converted schema to file
cat converted.sql | head -50
```
Expected: PostgreSQL-compatible SQL syntax (no backticks, SERIAL types, TIMESTAMP).
---
### Task 3: Test PostgreSQL Import
**Files:**
- Target: PostgreSQL instance at `postgresql://asephs:hunterz@100.108.1.124:5432/hub`
- [ ] **Step 1: Connect to target PostgreSQL**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub
```
Expected: Connected to PostgreSQL. Prompt shows `hub=#`.
- [ ] **Step 2: Import converted schema**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub < converted.sql
```
Expected: Import completes. Check for errors (should be none).
- [ ] **Step 3: Validate table creation**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "\dt"
```
Expected: All tables listed (User, Account, Session, Role, Permission, UserRole, ImageCache, etc.).
- []**Step 4: Validate row counts**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT COUNT(*) FROM \"User\";"
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT COUNT(*) FROM \"Account\";"
```
Expected: Row counts match MySQL backup (from Task 1, Step 2).
- [ ] **Step 5: Validate indexes**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "\di"
```
Expected: All indexes present (email_idx, username_idx, userId_idx, sessionToken_idx, etc.).
- [ ] **Step 6: Validate foreign keys**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT constraint_name, table_name FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY';"
```
Expected: Foreign key constraints listed (User→Account, User→Session, etc.).
---
### Task 4: Update Elysia Database Driver
**Files:**
- Modify: `apps/elysia/src/db/lib/database.ts`
- Modify: `apps/elysia/src/db/lib/schema.ts`
- Modify: `apps/elysia/package.json`
- [ ] **Step 1: Update package.json dependencies**
Replace mysql2 with pg:
```json
{
"dependencies": {
"drizzle-orm": "^0.45.2",
"pg": "^8.11.0",
"elysia": "^1.4.28"
},
"devDependencies": {
"drizzle-kit": "^0.31.10"
}
}
```
Run: `cd apps/elysia && bun install`
Expected: pg installed, mysql2 removed from node_modules.
- [ ] **Step 2: Update database.ts driver**
Replace entire file:
```typescript
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'
export type Database = PostgresJsDatabase<typeof schema>
let dbInstance: Database | null = null
let sqlInstance: ReturnType<typeof postgres> | null = null
export function initializeDb(databaseUrl: string): Database {
if (dbInstance) {
return dbInstance
}
sqlInstance = postgres(databaseUrl)
dbInstance = drizzle(sqlInstance, { schema, mode: 'default' })
return dbInstance
}
export function getDb(): Database {
if (!dbInstance) {
throw new Error('Database not initialized. Call initializeDb first.')
}
return dbInstance
}
export async function closeDb() {
if (sqlInstance) {
await sqlInstance.end()
sqlInstance = null
dbInstance = null
}
}
```
Expected: File updated. No syntax errors.
- [ ] **Step 3: Update schema.ts imports**
Replace:
```typescript
import {
index,
int,
mysqlTable,
primaryKey,
text,
timestamp,
varchar,
} from 'drizzle-orm/mysql-core'
```
With:
```typescript
import {
index,
integer,
pgTable,
primaryKey,
text,
timestamp,
varchar,
} from 'drizzle-orm/postgres-core'
```
- [ ] **Step 4: Update schema.ts table definitions**
Replace all `mysqlTable` with `pgTable` and `int` with `integer`:
```typescript
// Before
export const users = mysqlTable(
'User',
{
id: varchar('id', { length: 255 }).primaryKey(),
name: varchar('name', { length: 255 }),
// ...
},
// ...
)
// After
export const users = pgTable(
'User',
{
id: varchar('id', { length: 255 }).primaryKey(),
name: varchar('name', { length: 255 }),
// ...
},
// ...
)
```
Do this for all tables: users, accounts, sessions, roles, permissions, userRoles, and any others.
Expected: All `mysqlTable` → `pgTable`, all `int` → `integer`.
- [ ] **Step 5: Update environment variable**
Set DATABASE_URL in `.env` or deployment config:
```bash
DATABASE_URL=postgresql://asephs:hunterz@100.108.1.124:5432/hub
```
Expected: Environment variable set and accessible to elysia app.
- [ ] **Step 6: Test elysia connection**
```bash
cd apps/elysia
bun run src/index.ts
```
Expected: App starts without connection errors. Check logs for "Database initialized" or similar.
- [ ] **Step 7: Commit elysia changes**
```bash
cd /mnt/code/bp3/ultimate-asepharyana.tech
git add apps/elysia/src/db/lib/database.ts apps/elysia/src/db/lib/schema.ts apps/elysia/package.json
git commit -m "feat(elysia): migrate database driver from MySQL to PostgreSQL"
```
Expected: Commit created with message.
---
### Task 5: Update Rust Database Driver
**Files:**
- Modify: `apps/rust/Cargo.toml`
- Modify: `apps/rust/src/infra/db_setup.rs`
- [ ] **Step 1: Update Cargo.toml features**
Replace:
```toml
sea-orm = { version = "1.1.19", features = ["sqlx-mysql", "runtime-tokio-rustls", "macros", "with-chrono", "with-uuid"] }
```
With:
```toml
sea-orm = { version = "1.1.19", features = ["sqlx-postgres", "runtime-tokio-rustls", "macros", "with-chrono", "with-uuid"] }
```
Expected: Cargo.toml updated. Feature changed from sqlx-mysql to sqlx-postgres.
- [ ] **Step 2: Update db_setup.rs backend check**
Replace:
```rust
match backend {
DbBackend::MySql => {
// MySQL-specific logic
}
_ => {
info!("ℹ️ Skipping schema init for non-MySQL backend");
}
}
```
With:
```rust
match backend {
DbBackend::Postgres => {
// PostgreSQL-specific logic
let tables = vec![(
"ImageCache",
schema
.create_table_from_entity(image_cache::Entity)
.if_not_exists()
.to_owned(),
)];
for (name, stmt) in tables {
match db.execute(backend.build(&stmt)).await {
Ok(_) => info!(" ✓ Table '{}' checked/created", name),
Err(e) => {
error!(" [!] Failed to create table '{}': {}", name, e);
return Err(e);
}
}
}
// PostgreSQL index creation (different syntax)
let index_sql = "CREATE INDEX IF NOT EXISTS idx_image_cache_cdn_url ON \"ImageCache\" (cdn_url)";
match db.execute(Statement::from_string(backend, index_sql)).await {
Ok(_) => info!(" ✓ Index 'idx_image_cache_cdn_url' ensured"),
Err(e) => {
let err_str = e.to_string();
// PostgreSQL duplicate index error
if err_str.contains("already exists") {
info!(" ✓ Index 'idx_image_cache_cdn_url' already exists");
} else {
error!(" [!] Failed to create index on ImageCache: {}", e);
}
}
}
info!("✅ Database schema initialization complete.");
}
_ => {
info!("ℹ️ Skipping schema init for non-PostgreSQL backend");
}
}
```
Expected: db_setup.rs updated with PostgreSQL backend handling.
- [ ] **Step 3: Update environment variable**
Set DATABASE_URL in `.env` or deployment config:
```bash
DATABASE_URL=postgresql://asephs:hunterz@100.108.1.124:5432/hub
```
Expected: Environment variable set and accessible to rust app.
- [ ] **Step 4: Rebuild rust app**
```bash
cd apps/rust
cargo build --release
```
Expected: Build completes without errors. Compilation uses sqlx-postgres feature.
- [ ] **Step 5: Test rust connection**
```bash
cd apps/rust
cargo run
```
Expected: App starts without connection errors. Check logs for "Database schema initialization complete" or similar.
- [ ] **Step 6: Commit rust changes**
```bash
cd /mnt/code/bp3/ultimate-asepharyana.tech
git add apps/rust/Cargo.toml apps/rust/src/infra/db_setup.rs
git commit -m "feat(rust): migrate database driver from MySQL to PostgreSQL"
```
Expected: Commit created with message.
---
### Task 6: Smoke Tests - Elysia App
**Files:**
- Test: Manual testing via HTTP requests or app UI
- [ ] **Step 1: Start elysia app**
```bash
cd apps/elysia
bun run src/index.ts
```
Expected: App running on configured port (check logs for port).
- [ ] **Step 2: Test user login**
```bash
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password"}'
```
Expected: Response 200 or 401 (auth error is OK, connection error is not).
-[]**Step 3: Test user creation (if endpoint exists)**
```bash
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Test User","email":"newuser@example.com"}'
```
Expected: Response 200/201 or 400 (validation error is OK).
- [ ] **Step 4: Test session retrieval**
```bash
curl -X GET http://localhost:3000/sessions \
-H "Authorization: Bearer <token>"
```
Expected: Response 200 with session data or 401 (auth error is OK).
- [ ] **Step 5: Check database logs**
```bash
# In elysia app logs, verify queries are executing against PostgreSQL
# Look for connection strings or query logs showing PostgreSQL
```
Expected: Logs show PostgreSQL queries (not MySQL).
---
### Task 7: Smoke Tests - Rust App
**Files:**
- Test: Manual testing via HTTP requests or app UI
- [ ] **Step 1: Start rust app**
```bash
cd apps/rust
cargo run --release
```
Expected: App running on configured port (check logs for port).
- [ ] **Step 2: Test image cache endpoint (if exists)**
```bash
curl -X GET http://localhost:8000/api/cache/status
```
Expected: Response 200 with cache status or 404 (endpoint may not exist).
- [ ] **Step 3: Test scraping/CDN endpoint**
```bash
curl -X GET http://localhost:8000/api/health
```
Expected: Response 200 with health status.
- [ ] **Step 4: Check database logs**
```bash
# In rust app logs, verify queries are executing against PostgreSQL
# Look for connection strings or query logs showing PostgreSQL
```
Expected: Logs show PostgreSQL queries (not MySQL).
---
### Task 8: Data Integrity Validation
**Files:**
- Test: PostgreSQL queries
- [] **Step 1: Verify user count**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT COUNT(*) as user_count FROM \"User\";"
```
Expected: Count matches MySQL backup count (from Task 1, Step 2).
- [ ] **Step 2: Verify account count**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT COUNT(*) as account_count FROM \"Account\";"
```
Expected: Count matches MySQL backup.
- [ ] **Step 3: Verify session count**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT COUNT(*) as session_count FROM \"Session\";"
```
Expected: Count matches MySQL backup.
- [] **Step 4: Verify no orphaned foreign keys**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "
SELECT a.id FROM \"Account\" a
LEFT JOIN \"User\" u ON a.user_id = u.id
WHERE u.id IS NULL;
"
```
Expected: No rows returned (no orphaned accounts).
- [ ] **Step 5: Verify role/permission relationships**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT COUNT(*) as role_count FROM \"Role\";"
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "SELECT COUNT(*) as permission_count FROM \"Permission\";"
```
Expected: Counts match MySQL backup.
---
### Task 9: Performance Baseline
**Files:**
- Test: Query performance comparison
- [ ] **Step 1: Benchmark user query on PostgreSQL**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "EXPLAIN ANALYZE SELECT * FROM \"User\" WHERE email = 'test@example.com';"
```
Expected: Query plan shows index usage (Seq Scan or Index Scan). Note execution time.
- [ ] **Step 2: Benchmark account query on PostgreSQL**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub -c "EXPLAIN ANALYZE SELECT * FROM \"Account\" WHERE user_id = 'user-123';"
```
Expected: Query plan shows index usage. Note execution time.
- [ ] **Step 3: Compare with MySQL baseline (if available)**
If MySQL is still running, run same queries and compare execution times.
Expected: PostgreSQL performance similar or better than MySQL.
---
### Task 10: Cleanup and Documentation
**Files:**
- Create: `MIGRATION_LOG.md` (optional, for documentation)
- [ ] **Step 1: Remove temporary files**
```bash
rm mysql_backup.sql converted.sql
```
Expected: Temporary migration files deleted.
- [ ] **Step 2: Document migration completion**
Create `MIGRATION_LOG.md`:
```markdown
# PostgreSQL Migration Log
**Date:** 2026-05-25
**Status:** ✅ Complete
## Summary
- Migrated elysia app from MySQL to PostgreSQL
- Migrated rust app from MySQL to PostgreSQL
- All data validated and integrity confirmed
- Apps tested and operational
## Changes
- elysia: Updated database driver (mysql2 → postgres), schema (mysqlTable → pgTable)
- rust: Updated Cargo.toml feature (sqlx-mysql → sqlx-postgres), db_setup.rs backend handling
## Validation
- Row counts match pre-migration
- Foreign keys intact
- Indexes present and performant
- Auth flow functional
- Session management working
## Rollback
MySQL backup available at: [location if kept]
To rollback: Restore MySQL from backup, revert connection strings, redeploy apps.
```
- [ ] **Step 3: Final commit**
```bash
git add MIGRATION_LOG.md
git commit -m "docs: add PostgreSQL migration completion log"
```
Expected: Commit created.
- [ ] **Step 4: Verify all apps running**
```bash
# Check elysia
curl http://localhost:3000/health
# Check rust
curl http://localhost:8000/health
```
Expected: Both apps respond with 200 status.
---
## Self-Review
**Spec Coverage:**
- ✅ Pre-migration (backup, convert, test) — Tasks 1-3
- ✅ Elysia code updates (driver, schema, env) — Task 4
- ✅ Rust code updates (Cargo.toml, db_setup.rs, env) — Task 5
- ✅ Smoke tests (auth, endpoints, logs) — Tasks 6-7
- ✅ Data validation (row counts, foreign keys) — Task 8
- ✅ Performance baseline — Task 9
- ✅ Cleanup and documentation — Task 10
**Placeholder Scan:**
- ✅ No TBD/TODO
- ✅ All code blocks complete
- ✅ All commands exact with expected output
- ✅ All file paths exact
**Type Consistency:**
- ✅ Database type: `PostgresJsDatabase` (elysia), `DbBackend::Postgres` (rust)
- ✅ Connection string format consistent: `postgresql://asephs:hunterz@100.108.1.124:5432/hub`
- ✅ Table names consistent: "User", "Account", "Session", etc.
@@ -1,533 +0,0 @@
# Production GitHub Actions Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Rework GitHub Actions build/deploy workflows into a production baseline with reliable React submodule updates, least-privilege permissions, clear deploy behavior, and current official action versions.
**Architecture:** Keep two workflows: `docker-build-push.yml` for detect/build/manifest updates, and `deploy-docker.yml` for VPS deployment. Add dispatch submodule SHA readiness checks before parent pointer updates, and remove recursive submodule checkout from deploy runner.
**Tech Stack:** GitHub Actions YAML, GitHub-hosted Ubuntu runners, Docker Buildx, GHCR, git submodules, Docker Compose over SSH.
---
## File Structure
- Modify `.github/workflows/docker-build-push.yml`: add default permissions, validate repository dispatch payloads, wait for submodule SHAs, keep selective matrix builds, harden manifest update.
- Modify `.github/workflows/deploy-docker.yml`: add default permissions, remove recursive checkout, keep auto deploy from successful build, make deploy logs clearer.
- Modify `.github/dependabot.yml`: add GitHub Actions update config so official actions stay current.
---
### Task 1: Harden build workflow permissions and dispatch validation
**Files:**
- Modify: `.github/workflows/docker-build-push.yml`
- [ ] **Step 1: Add workflow-level read permissions**
At top level, after `concurrency`, add:
```yaml
permissions:
contents: read
```
Expected shape:
```yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
env:
REGISTRY: ghcr.io
```
- [ ] **Step 2: Replace dispatch parser with payload validation**
In `.github/workflows/docker-build-push.yml`, replace `Parse repository_dispatch payload` step body with:
```yaml
- 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
rust-api|elysia-api|react-web|9router) ;;
*)
echo "::error::Unsupported service '$SERVICE'. Expected one of: rust-api, elysia-api, react-web, 9router"
exit 1
;;
esac
case "$SHA" in
*[!0-9a-fA-F]*|???????????????????????????????????????|?????????????????????????????????????????*)
echo "::error::Invalid sha '$SHA'. Expected 40 hex characters"
exit 1
;;
esac
declare -a SERVICES=("rust-api" "elysia-api" "react-web" "9router")
for svc in "${SERVICES[@]}"; do
if [ "$SERVICE" = "$svc" ]; then
echo "${svc}=true" >> "$GITHUB_OUTPUT"
else
echo "${svc}=false" >> "$GITHUB_OUTPUT"
fi
done
```
- [ ] **Step 3: Run YAML syntax check**
Run:
```bash
python - <<'PY'
from pathlib import Path
import yaml
for path in Path('.github/workflows').glob('*.yml'):
yaml.safe_load(path.read_text())
print(f'OK {path}')
PY
```
Expected:
```text
OK .github/workflows/deploy-docker.yml
OK .github/workflows/docker-build-push.yml
```
- [ ] **Step 4: Commit**
```bash
git add .github/workflows/docker-build-push.yml
git commit -m "ci: validate dispatch payloads"
```
---
### Task 2: Add submodule SHA readiness wait before build/update
**Files:**
- Modify: `.github/workflows/docker-build-push.yml`
- [ ] **Step 1: Add readiness job after changes job**
Insert this job between `changes` and `build`:
```yaml
wait-submodule-ref:
needs: [changes]
if: github.event_name == 'repository_dispatch'
runs-on: ubuntu-latest
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
"rust-api") REPO="https://github.com/MythEclipse/ultimate-asepharyana-tech-rust.git" ;;
"elysia-api") REPO="https://github.com/MythEclipse/ultimate-asepharyana-tech-elysia.git" ;;
"react-web") REPO="https://github.com/MythEclipse/ultimate-asepharyana-tech-react.git" ;;
"9router") REPO="https://github.com/MythEclipse/9router.git" ;;
*)
echo "::error::Unsupported service '$SERVICE'"
exit 1
;;
esac
echo "Waiting for $SERVICE ref $SHA in $REPO"
for attempt in {1..30}; do
if git ls-remote --exit-code "$REPO" "$SHA" >/dev/null 2>&1; then
echo "Submodule ref $SHA is fetchable for $SERVICE"
exit 0
fi
echo "Attempt $attempt/30: $SHA not visible yet; waiting 10s"
sleep 10
done
echo "::error::Submodule ref $SHA for $SERVICE was not fetchable after 300s"
exit 1
```
- [ ] **Step 2: Make build wait for readiness job without blocking push/manual events**
Change build job header from:
```yaml
build:
needs: [changes]
```
to:
```yaml
build:
needs: [changes, wait-submodule-ref]
if: |
always() &&
needs.changes.result == 'success' &&
(needs.wait-submodule-ref.result == 'success' || needs.wait-submodule-ref.result == 'skipped') &&
needs.changes.outputs.matrix != '[]'
```
Remove existing build-level line:
```yaml
if: needs.changes.outputs.matrix != '[]'
```
- [ ] **Step 3: Make update-manifest wait for readiness job**
Change update-manifest header from:
```yaml
update-manifest:
needs: [changes, build]
if: |
always() &&
(needs.build.result == 'success' || needs.build.result == 'skipped')
```
to:
```yaml
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')
```
- [ ] **Step 4: Run YAML syntax check**
Run same command from Task 1 Step 3.
Expected both workflow files print `OK`.
- [ ] **Step 5: Commit**
```bash
git add .github/workflows/docker-build-push.yml
git commit -m "ci: wait for submodule refs before builds"
```
---
### Task 3: Harden manifest update and submodule checkout
**Files:**
- Modify: `.github/workflows/docker-build-push.yml`
- [ ] **Step 1: Add job permissions to build and manifest jobs**
Ensure build job contains:
```yaml
permissions:
contents: read
packages: write
```
Ensure update-manifest job contains:
```yaml
permissions:
contents: write
```
- [ ] **Step 2: Replace dispatch submodule checkout block**
Inside `Update tags and submodules`, replace the repository_dispatch submodule update block with:
```bash
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
```
- [ ] **Step 3: Add pull/rebase retry before push**
Replace:
```bash
git commit -m "chore: update manifests and submodules [skip ci]"
git pull --rebase origin main
git push origin main
```
with:
```bash
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
```
- [ ] **Step 4: Run YAML syntax check**
Run same command from Task 1 Step 3.
Expected both workflow files print `OK`.
- [ ] **Step 5: Commit**
```bash
git add .github/workflows/docker-build-push.yml
git commit -m "ci: harden manifest updates"
```
---
### Task 4: Make deploy checkout submodule-free and least privilege
**Files:**
- Modify: `.github/workflows/deploy-docker.yml`
- [ ] **Step 1: Add workflow-level read permissions**
After `concurrency`, add:
```yaml
permissions:
contents: read
```
Expected shape:
```yaml
concurrency:
group: deploy-vps
cancel-in-progress: false
permissions:
contents: read
```
If current `cancel-in-progress` is `true`, change it to `false`.
- [ ] **Step 2: Make checkout non-recursive**
Replace checkout step:
```yaml
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
```
with:
```yaml
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: false
```
- [ ] **Step 3: Add deploy context log**
At start of `Deploy with Docker Compose on VPS` run script, after `set -euo pipefail`, add:
```bash
echo "Deploy event: ${{ github.event_name }}"
echo "Deploy ref: ${{ github.ref }}"
echo "Deploy sha: ${{ github.sha }}"
```
- [ ] **Step 4: Run YAML syntax check**
Run same command from Task 1 Step 3.
Expected both workflow files print `OK`.
- [ ] **Step 5: Commit**
```bash
git add .github/workflows/deploy-docker.yml
git commit -m "ci: avoid submodule checkout during deploy"
```
---
### Task 5: Add GitHub Actions Dependabot updates
**Files:**
- Modify: `.github/dependabot.yml`
- [ ] **Step 1: Add github-actions ecosystem**
Append this update entry under `updates:`:
```yaml
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: weekly
groups:
github-actions:
patterns:
- '*'
```
Expected file shape:
```yaml
version: 2
updates:
- package-ecosystem: 'devcontainers'
directory: '/'
schedule:
interval: weekly
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: weekly
groups:
github-actions:
patterns:
- '*'
```
- [ ] **Step 2: Run YAML syntax check**
Run:
```bash
python - <<'PY'
from pathlib import Path
import yaml
paths = [Path('.github/dependabot.yml'), *Path('.github/workflows').glob('*.yml')]
for path in paths:
yaml.safe_load(path.read_text())
print(f'OK {path}')
PY
```
Expected:
```text
OK .github/dependabot.yml
OK .github/workflows/deploy-docker.yml
OK .github/workflows/docker-build-push.yml
```
- [ ] **Step 3: Commit**
```bash
git add .github/dependabot.yml
git commit -m "ci: enable github actions dependency updates"
```
---
### Task 6: Final validation
**Files:**
- Validate: `.github/workflows/docker-build-push.yml`
- Validate: `.github/workflows/deploy-docker.yml`
- Validate: `.github/dependabot.yml`
- [ ] **Step 1: Run YAML syntax check**
Run:
```bash
python - <<'PY'
from pathlib import Path
import yaml
paths = [Path('.github/dependabot.yml'), *Path('.github/workflows').glob('*.yml')]
for path in paths:
yaml.safe_load(path.read_text())
print(f'OK {path}')
PY
```
Expected all files print `OK`.
- [ ] **Step 2: Check workflows recognized by GitHub CLI**
Run:
```bash
gh workflow list
```
Expected output includes:
```text
Build and Push Docker Images
Deploy Docker to VPS
```
- [ ] **Step 3: Inspect final diff**
Run:
```bash
git diff -- .github/workflows .github/dependabot.yml
```
Expected:
- `docker-build-push.yml` has dispatch validation, `wait-submodule-ref`, job permissions, and manifest push retry.
- `deploy-docker.yml` has non-recursive checkout and read-only permissions.
- `dependabot.yml` has `github-actions` updates.
- [ ] **Step 4: Commit any final validation fixes**
If Step 1 or Step 2 required fixes, commit them:
```bash
git add .github/workflows .github/dependabot.yml
git commit -m "ci: finalize production workflow hardening"
```
If no fixes were needed, do not create an empty commit.
@@ -1,29 +0,0 @@
# React Direct Runtime and Images Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Serve the React SPA without nginx and remove frontend image-cache proxy calls so browser uses direct image/API URLs.
**Architecture:** The React Docker image still builds with Bun/Vite, but runtime uses `vite preview` from Bun instead of nginx. `CachedImage` normalizes image URLs and renders them directly, keeping fallback/retry UI but removing `/proxy/image-cache` auditing. Traefik keeps routing `react-web:80`, so compose and dynamic routing stay stable.
**Tech Stack:** Bun, Vite, React, TypeScript, Docker, Docker Compose, Traefik.
---
## Tasks
### Task 1: Switch React Runtime From nginx to Bun/Vite Preview
Modify `infra/docker/react.Dockerfile` so runtime stage uses `oven/bun:1-alpine`, installs production deps, copies `/app/dist`, exposes 80, and runs `bunx vite preview --host 0.0.0.0 --port 80`. Verify nginx runtime/copy lines are gone and Vite preview command exists.
### Task 2: Remove Image Cache Proxy From CachedImage
Modify `apps/react/src/components/ui/cached-image.tsx` to remove `API_BASE_URL` import, `/proxy/image-cache` POST, audit state/function, and auditing overlay. Keep direct normalized image URLs, retry behavior, and fallback image.
### Task 3: Verify Build and Config
Run static checks for both tasks, `bun --cwd apps/react run build`, `docker build -f infra/docker/react.Dockerfile -t react-web:test .`, start test container on `18080:80`, curl root, clean container, and run `git diff --check`.
### Task 4: Commit and Push
Commit changed files: `infra/docker/react.Dockerfile`, `apps/react/src/components/ui/cached-image.tsx`, `docs/superpowers/plans/2026-05-27-react-direct-runtime-and-images.md`. Push branch only after verification if user asks; do not push from worktree unless explicitly instructed.
@@ -1,130 +0,0 @@
# Moonrepo Migration Design
**Date:** 2026-05-24
**Status:** approved
## Context
Ultimate Asepharyana Tech is a polyglot monorepo with 7 applications managed as git submodules: nextjs (React/Next.js), elysia (Bun/Elysia), solidjs (SolidStart), rust (Axum), rust-auth (Axum auth service), leptos (Leptos WASM), and 9router. Nix flakes handle system dependencies and Docker image builds. Docker Compose manages deployment.
## Problem
- No centralized task orchestration — running build/lint/test across all apps is manual and error-prone
- No dependency caching — builds are not incremental across apps
- Inconsistent developer experience — each app has its own conventions, onboarding is slow
## Goal
Adopt moonrepo for task orchestration, caching, and dependency graph management across all 7 apps while preserving the existing Nix build system, Docker Compose deployment, and git submodule structure.
## Architecture
### Directory Structure
```
ultimate-asepharyana.tech/
├── .moon/
│ ├── workspace.yml
│ ├── toolchain.yml
│ └── tasks/
│ ├── typescript-build.yml
│ ├── typescript-lint.yml
│ ├── typescript-test.yml
│ ├── rust-build.yml
│ ├── rust-test.yml
│ └── rust-lint.yml
├── apps/
│ ├── nextjs/moon.yml
│ ├── elysia/moon.yml
│ ├── solidjs/moon.yml
│ ├── rust/moon.yml
│ ├── rust-auth/moon.yml
│ ├── leptos/moon.yml
│ └── 9router/moon.yml
├── .moon/workspace.yml # unchanged — Nix build + deployment
├── flake.nix # unchanged
├── infra/ # unchanged — Docker Compose, Traefik, nginx
└── scripts/ # unchanged
```
### Tag Taxonomy
| Tag | Projects |
|-----|----------|
| `lang:typescript` | nextjs, elysia, solidjs |
| `lang:rust` | rust, rust-auth, leptos |
| `type:frontend` | nextjs, solidjs, leptos |
| `type:backend` | rust, elysia, rust-auth |
| `type:router` | 9router |
### Toolchain
- moonrepo manages Node 22, Bun, and TypeScript versions via `.moon/toolchain.yml` for consistent access across all TypeScript apps
- Rust toolchain remains managed by Nix/devShell — moonrepo does not touch Rust installation
- Nix devShell gains the moon CLI binary
### Project Dependencies
- **nextjs** depends on `rust-auth` and `elysia` (frontend consumes both APIs)
- **solidjs** depends on `elysia` and `rust-auth`
- **leptos** depends on `rust` (CSR frontend consumes rust backend API)
- **elysia**, **rust**, **rust-auth**, **9router** — no project dependencies
## Task Definitions
### Shared Tasks: TypeScript
All TypeScript apps inherit from `.moon/tasks/typescript-*.yml`:
| Task | Command | Inputs | Outputs |
|------|---------|--------|---------|
| `build` | `bun run build` | `src/**/*`, `tsconfig.json`, `package.json` | `.next`, `dist`, `.output` |
| `lint` | `bun run lint` | `src/**/*`, `eslint.config.mjs`, `tsconfig.json` | — |
| `typecheck` | `bun run check-types` | `src/**/*`, `tsconfig.json` | — |
| `test` | `bun test` | `src/**/*`, `test/**/*` | — |
| `e2e` | overridden per app | — | — |
### Shared Tasks: Rust
All Rust apps inherit from `.moon/tasks/rust-*.yml`, using system tasks:
| Task | Command | Inputs | Outputs |
|------|---------|--------|---------|
| `build` | `cargo build --release` | `src/**/*`, `Cargo.toml`, `Cargo.lock`, `build.rs` | `target/release/*` |
| `test` | `cargo test` | `src/**/*`, `Cargo.toml`, `Cargo.lock`, `tests/**/*` | — |
| `lint` | `cargo clippy -- -D warnings` | `src/**/*`, `Cargo.toml` | — |
| `fmt-check` | `cargo fmt --check` | `src/**/*` | — |
### Implicit Dependencies
Configured in `workspace.yml`: `lint` implicitly depends on `build` for TypeScript apps (typecheck path), and `build` propagates to dependents when source inputs change.
## Key Commands
```bash
moon run :build # build all changed apps + dependents
moon run :lint # lint all
moon run :test # test all
moon run --tag lang:rust :test # test Rust apps only
moon run --affected :build # build only what changed
moon query projects --tag lang:typescript # list TypeScript projects
```
## What Does NOT Change
- **Nix flakes** — system dependencies, Docker image builds, devShell remain as-is
- **Docker Compose** — deployment continues through existing compose files in `infra/compose/`
- **Git submodules** — all 7 app repos stay as submodules under `apps/`
- **infra/** — Traefik and Docker Compose deployment remain under `infra/`; legacy Grafana/Prometheus/Alertmanager configs have been removed
- **scripts/** — existing utility scripts unchanged
## Migration Steps (High-Level)
1. Install moonrepo CLI, create `.moon/` scaffolding
2. Create shared task definitions in `.moon/tasks/`
3. Configure `workspace.yml` with project list, tags, and dependency constraints
4. Configure `toolchain.yml` for Node 22 + Bun
5. Add `moon.yml` to each of the 7 app directories
6. Validate: `moon check`, `moon run :build`, `moon run :lint`, `moon run :test`
7. Add moon CLI to Nix devShell
8. Commit and document
@@ -1,197 +0,0 @@
# PostgreSQL Migration Design
**Date:** 2026-05-25
**Scope:** Migrate elysia (Drizzle ORM + MySQL) and rust (SeaORM + MySQL) apps to PostgreSQL
**Approach:** Direct cutover (Approach B)
**Connection String:** `postgresql://asephs:hunterz@100.108.1.124:5432/hub`
---
## Current State
### Elysia App
- **ORM:** Drizzle ORM v0.45.2
- **Driver:** mysql2 v3.20.0
- **Schema Location:** `apps/elysia/src/db/lib/schema.ts`
- **Tables:** users, accounts, sessions, roles, permissions, userRoles, and related junction tables
- **Database File:** `apps/elysia/src/db/lib/database.ts`
### Rust App
- **ORM:** SeaORM v1.1.19
- **Feature:** sqlx-mysql
- **DB Setup:** `apps/rust/src/infra/db_setup.rs`
- **Tables:** ImageCache (primary table managed by app)
- **Config:** Environment-based connection string
### Apps NOT Migrating
- 9router (uses SQLite runtime, excluded per requirements)
- nextjs (frontend, minimal DB usage)
- leptos, solidjs, rust-auth (frontends, no DB)
---
## Migration Steps
### Phase 1: Pre-Migration (Preparation)
1. **Backup MySQL**
```bash
mysqldump -u <user> -p <db> > mysql_backup.sql
```
2. **Convert MySQL dump to PostgreSQL**
- Use `pgloader` or manual conversion for schema compatibility
- Handle type conversions:
- `INT` → `INTEGER`
- `VARCHAR(n)` → `VARCHAR(n)` (PostgreSQL compatible)
- `DATETIME` → `TIMESTAMP`
- `AUTO_INCREMENT` → `SERIAL` or `BIGSERIAL`
- Verify indexes and foreign keys convert correctly
3. **Test import into target PostgreSQL**
```bash
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub < converted.sql
```
4. **Validate data integrity**
- Row counts match MySQL
- Indexes exist
- Foreign key constraints enforced
### Phase 2: Code Updates
#### Elysia App
1. **Update `apps/elysia/src/db/lib/database.ts`**
- Replace `mysql2` import with `postgres` driver
- Change Drizzle dialect from `drizzle-orm/mysql2` to `drizzle-orm/postgres`
- Update connection string format
2. **Update `apps/elysia/src/db/lib/schema.ts`**
- Replace `drizzle-orm/mysql-core` imports with `drizzle-orm/postgres-core`
- Change `mysqlTable` to `pgTable`
- Adjust column types if needed (e.g., `int` → `integer`)
3. **Update `apps/elysia/package.json`**
- Replace `mysql2` with `pg` (PostgreSQL driver)
- Keep `drizzle-orm` and `drizzle-kit` versions
4. **Update environment/config**
- Change `DATABASE_URL` to PostgreSQL connection string
#### Rust App
1. **Update `apps/rust/Cargo.toml`**
- Replace `sqlx-mysql` feature with `sqlx-postgres` in sea-orm dependency
- Add `postgres` feature if needed
2. **Update `apps/rust/src/infra/db_setup.rs`**
- Change `DbBackend::MySql` check to `DbBackend::Postgres`
- Adjust SQL syntax for PostgreSQL (e.g., index creation)
- Update error code handling (PostgreSQL uses different error codes)
3. **Update config/environment**
- Change `DATABASE_URL` to PostgreSQL connection string
### Phase 3: Cutover (Execution)
1. **Stop all apps**
```bash
# Stop elysia
# Stop rust app
```
2. **Export MySQL data**
```bash
mysqldump -u <user> -p <db> > final_backup.sql
```
3. **Convert and import to PostgreSQL**
```bash
# Convert dump
# Import to PostgreSQL
psql postgresql://asephs:hunterz@100.108.1.124:5432/hub < converted.sql
```
4. **Deploy updated apps**
- Deploy elysia with PostgreSQL driver
- Deploy rust with PostgreSQL feature
5. **Verify connectivity**
- Test database queries from both apps
- Check auth flow (users table)
- Verify session management
### Phase 4: Validation
1. **Smoke tests**
- User login/logout
- Session creation and retrieval
- Role/permission queries
- ImageCache operations (rust app)
2. **Data integrity checks**
- Row counts match pre-migration
- No orphaned foreign keys
- Indexes performing as expected
3. **Performance baseline**
- Compare query times MySQL vs PostgreSQL
- Monitor connection pool usage
### Phase 5: Rollback Plan (if needed)
1. **Stop apps**
2. **Restore MySQL from backup**
```bash
mysql -u <user> -p <db> < mysql_backup.sql
```
3. **Revert connection strings in apps**
4. **Redeploy with MySQL drivers**
5. **Restart apps**
---
## Technical Details
### Elysia Driver Change
**Before:**
```typescript
import { drizzle } from 'drizzle-orm/mysql2'
import { createPool } from 'mysql2/promise'
```
**After:**
```typescript
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
```
### Rust Feature Change
**Before:**
```toml
sea-orm = { version = "1.1.19", features = ["sqlx-mysql", ...] }
```
**After:**
```toml
sea-orm = { version = "1.1.19", features = ["sqlx-postgres", ...] }
```
---
## Risk Assessment
| Risk | Mitigation |
|------|-----------|
| Data loss during migration | Full MySQL backup before cutover; test import first |
| App downtime | Cutover during low-traffic window; rollback plan ready |
| Connection string misconfiguration | Test connection before deploying apps |
| Schema incompatibilities | Pre-test conversion; validate indexes/constraints |
| Performance regression | Baseline MySQL performance; monitor PostgreSQL after cutover |
---
## Success Criteria
- ✅ All data migrated to PostgreSQL (row counts match)
- ✅ Elysia app connects and queries work
- ✅ Rust app connects and queries work
- ✅ Auth flow functional (login/logout)
- ✅ No orphaned foreign keys
- ✅ Indexes present and performant
- ✅ Rollback plan tested and documented
---
## Timeline
- **Pre-migration:** 30 min (backup, convert, test)
- **Code updates:** 1-2 hours (driver changes, testing)
- **Cutover:** 15-30 min (stop apps, migrate data, restart)
- **Validation:** 30 min (smoke tests, data checks)
- **Total:** ~3-4 hours (including buffer)
@@ -1,21 +0,0 @@
# Hapus Nix, Docker-Only
**Goal:** Remove all Nix configuration and adapt CI/dev to Docker-only.
**Scope:**
- Delete: `flake.nix`, `flake.lock`, `nix/`, `.envrc`, `.direnv/`
- Modify: `.github/workflows/docker-build-push.yml` — remove Nix steps, move `rust-api` to Dockerfile build
- Modify: `.vscode/settings.json` — remove `nixEnvSelector`
- Modify: `README.md` — replace `nix build` with `docker build`
**CI Changes:**
- `rust-api` uses `infra/docker/rust.Dockerfile` (already exists)
- Remove `cachix/install-nix-action`, `cachix/cachix-action`, `nix build` step
- Remove `flake.nix`, `flake.lock`, `nix/**` from path triggers
- Update path filters: `nix/apps/*.nix` → `infra/docker/*.Dockerfile`
- Remove `repository_dispatch` Nix `--override-input` logic
- Add `rust-api` to Dockerfile case + push branch
**Dev Workflow:**
- Rust toolchain via `rustup`/`rust-toolchain.toml` in `apps/rust/`
- Dev via `docker compose` instead of `process-compose`
@@ -1,100 +0,0 @@
# Production GitHub Actions redesign
## Goal
Rework `.github/workflows` into a production baseline that balances reliability, security, speed, and clear failures while keeping automatic deploys from `main`.
## Current problems
- Deploy runner checks out submodules recursively even though deploy work happens on the VPS. Fresh submodule SHAs can fail with `not our ref` before deploy starts.
- Repository dispatch can update a parent submodule pointer before the submodule SHA is fetchable by GitHub Actions.
- Build and deploy permissions are broader than needed at workflow level.
- Failure logs do not clearly separate build, manifest update, submodule readiness, and deployment phases.
## Chosen approach
Use a split pipeline:
1. `docker-build-push.yml` remains the build and manifest update pipeline.
2. `deploy-docker.yml` remains deploy-only.
3. Repository dispatch waits for the requested submodule SHA to be fetchable before building and updating the parent pointer.
4. Deploy checkout no longer uses recursive submodules; the VPS updates submodules after resetting to `origin/main`.
## Build workflow design
Triggers:
- `push` to `main` for app/package/docker/workflow changes.
- `repository_dispatch` with `service` and `sha` payload.
- `workflow_dispatch` for manual full builds.
Jobs:
- `changes`: detects the service matrix and validates dispatch payloads.
- `build`: builds and pushes only selected service images using Docker Buildx registry cache.
- `update-manifest`: updates compose image tags and, for dispatch events, updates the matching submodule pointer.
Repository dispatch handling:
- For dispatch events, wait until `git ls-remote` or equivalent fetch confirms the payload SHA exists in the service submodule remote.
- Retry for a bounded timeout and fail with an explicit message if the SHA never becomes visible.
- Only after readiness is confirmed, checkout the SHA in the submodule and commit the parent pointer update.
## Deploy workflow design
Triggers:
- `workflow_run` from successful `Build and Push Docker Images` on `main`.
- `push` to `main` for `infra/compose/**` and deploy workflow changes.
- `workflow_dispatch`.
Behavior:
- Keep automatic deploy from `main`.
- Keep a single deploy concurrency group.
- Use non-recursive checkout on the runner.
- On the VPS, fetch/reset `origin/main`, update submodules, compute changed compose stacks, pull images, and run Docker Compose.
## Permissions and action trust
Defaults:
```yaml
permissions:
contents: read
```
Job-specific permissions:
- Build job: `contents: read`, `packages: write`.
- Manifest update job: `contents: write`.
- Deploy job: `contents: read`.
Action pinning:
- GitHub-owned and Docker official actions may use major versions such as `actions/checkout@v4` and `docker/build-push-action@v6`.
- Any future third-party action should be pinned to a full commit SHA.
## Reliability details
- Keep `set -euo pipefail` in shell steps.
- Add bounded retry around submodule SHA readiness.
- Add clear logs for selected services, image tags, compose files changed, and dispatch payload values.
- Avoid recursive submodule checkout in deploy to eliminate fresh-SHA checkout race.
- Manifest commits continue using `[skip ci]` to prevent build loops.
## Speed details
- Keep selective matrix builds.
- Keep Docker Buildx registry cache.
- Keep deploy checkout shallow and submodule-free.
- Avoid rebuilding from compose-only manifest commits.
## Validation
Before marking implementation complete:
- Validate workflow YAML syntax.
- Run `gh workflow list` or equivalent sanity checks.
- Verify build workflow still detects React updates.
- Verify deploy workflow no longer fails during runner checkout for fresh submodule SHAs.
@@ -1,135 +0,0 @@
# Monorepo Restructure: ultimate-asepharyana.tech → asepharyana-hub
**Date:** 2026-07-09
**Status:** Approved
**Owner:** @asepharyana
## Background
Proyek ini sebelumnya bernama `ultimate-asepharyana.tech` — sebuah monorepo yang berisi beberapa service aplikasi, infrastruktur, dokumentasi, dan utility scripts. Karena GitHub org `MythEclipse` kena banned, semua remote repos tidak bisa diakses. Perlu dilakukan restruktur dan rename project secara menyeluruh.
## Goals
1. Rename project dari `ultimate-asepharyana.tech` ke `asepharyana-hub`
2. Hapus service docker-manager dan teleuploader dari proyek
3. Hapus semua pointer `.git` submodule (clean slate)
4. Update `.gitmodules` dengan remote baru ke `github.com/asepharyana/*`
5. Update semua referensi: workflows, konfigurasi, dokumentasi, image names
6. Siapkan struktur lokal — inisialisasi git dan push dilakukan terpisah
## Service yang tetap dipertahankan
| Service | Path | Git remote baru |
|---------|------|----------------|
| Elysia API | `apps/elysia` | `asepharyana/asepharyana-hub-elysia` |
| React Frontend | `apps/react` | `asepharyana/asepharyana-hub-react` |
| Scraper | `apps/scraper` | `asepharyana/asepharyana-hub-scraper` |
| Rust Auth | `apps/rust-auth` | `asepharyana/asepharyana-hub-rust-auth` |
## Service yang dihapus
| Service | Path |
|---------|------|
| Docker Manager | `apps/docker-manager/` |
| TeleUploader | `apps/teleuploader/` |
## File yang akan dihapus
- `apps/docker-manager/` (seluruh direktori)
- `apps/teleuploader/` (seluruh direktori)
- `infra/compose/docker-manager.yml`
- `infra/compose/teleuploader.yml`
- `infra/docker/docker-manager.Dockerfile`
- `infra/docker/teleuploader.Dockerfile`
## File pointer `.git` submodule yang dihapus
- `apps/elysia/.git`
- `apps/react/.git`
- `apps/scraper/.git`
- `apps/rust-auth/.git`
## Rename mapping
| Lokasi | Dari | Ke |
|--------|------|----|
| `package.json` `name` | `ultimate-asepharyana.tech` | `asepharyana-hub` |
| `README.md` | judul & path references | `asepharyana-hub` |
| `ARCHITECTURE.md` | directory tree, paths | `asepharyana-hub` |
| `CONTRIBUTING.md` | repo names & paths | `asepharyana-hub-*` |
| `infra/README.md` | deskripsi | `asepharyana-hub` |
| `.env.example` | `TRAEFIK_CONFIG_PATH` | `asepharyana-hub` |
| Perlengkapan infra Traefik | path config references | `asepharyana-hub` |
| `scripts/cleanup-ghcr.sh` | `MythEclipse` | `asepharyana` |
| `docs/add-new-app.md` | path references | `asepharyana-hub` |
| `docs/adr/*.md` | path references | `asepharyana-hub` |
## Image name migration (GHCR)
| Lama | Baru |
|------|------|
| `ghcr.io/mytheclipse/ultimate-asepharyana.tech/elysia-api:*` | `ghcr.io/asepharyana/asepharyana-hub/elysia-api:*` |
| `ghcr.io/mytheclipse/ultimate-asepharyana.tech/react-web:*` | `ghcr.io/asepharyana/asepharyana-hub/react-web:*` |
| `ghcr.io/mytheclipse/ultimate-asepharyana.tech/scraper-api:*` | `ghcr.io/asepharyana/asepharyana-hub/scraper-api:*` |
| `ghcr.io/mytheclipse/ultimate-asepharyana.tech/rust-auth:*` | `ghcr.io/asepharyana/asepharyana-hub/rust-auth:*` |
| `ghcr.io/mytheclipse/ultimate-asepharyana.tech/docker-manager:*` | — (dihapus) |
| `ghcr.io/mytheclipse/ultimate-asepharyana.tech/teleuploader:*` | — (dihapus) |
## Workflow changes
### `docker-build-push.yml`
- `IMAGE_NAME_PREFIX`: `mytheclipse/ultimate-asepharyana.tech` → `asepharyana/asepharyana-hub`
- Hapus service entries: `docker-manager`, `teleuploader`
- Update repo URLs di `wait-submodule-ref` dari `MythEclipse/*` ke `asepharyana/*`
- Update `update-manifest` phase — hapus service docker-manager & teleuploader
### `deploy-docker.yml`
- Update `git remote add origin`
- Hapus docker-manager & teleuploader dari compose list dan checkout
### `update-submodule.yml`
- Hapus service docker-manager & teleuploader
- Update nama workflow
## `.gitmodules`
Hanya berisi 4 apps dengan remote baru:
```ini
[submodule "apps/elysia"]
path = apps/elysia
url = https://github.com/asepharyana/asepharyana-hub-elysia.git
[submodule "apps/react"]
path = apps/react
url = https://github.com/asepharyana/asepharyana-hub-react.git
[submodule "apps/scraper"]
path = apps/scraper
url = https://github.com/asepharyana/asepharyana-hub-scraper.git
[submodule "apps/rust-auth"]
path = apps/rust-auth
url = https://github.com/asepharyana/asepharyana-hub-rust-auth.git
```
## Execution plan
1. Hapus docker-manager & teleuploader direktori + file infra
2. Hapus `.git` pointer di submodule
3. Update `.gitmodules`
4. Update `package.json`
5. Update `README.md`, `ARCHITECTURE.md`, `CONTRIBUTING.md`
6. Update `infra/compose/*.yml` image tags
7. Update `.env.example`, `infra/README.md`, docs traefik
8. Update `docker-build-push.yml`
9. Update `deploy-docker.yml`
10. Update `update-submodule.yml`
11. Update `scripts/cleanup-ghcr.sh`
12. Update `docs/add-new-app.md`
13. Update `docs/adr/*.md` path references (historical)
## Post-execution state
- Root direktori `asepharyana-hub/` dengan source code apps utuh (tanpa git)
- 4 app submodule terdaftar di `.gitmodules` dengan remote baru
- Infra/docs/scripts tetap menyatu di root
- 0 references ke `MythEclipse/ultimate-asepharyana.tech` di file konfigurasi
- Siap untuk `git init && git add && git commit` kapan saja
+196
View File
@@ -0,0 +1,196 @@
# Tailscale Networking
Dokumentasi setup dan troubleshooting konektivitas Tailscale antara node `orangevps` (VPS) dan `imrnes` (bare-metal).
## Topologi
```
orangevps (VPS)
├─ Tailscale IP: 100.x.x.x (dynamic)
├─ Public IP: 45.127.35.244
├─ Docker containers (app-shared-net)
│ └─ perlu akses ke imrnes via Tailscale
└─ tailscale-routes.service
└─ menambahkan route 100.x.x.x ke tabel routing main
imrnes (Bare-metal)
├─ Tailscale IP: 100.121.180.82
├─ Layanan:
│ ├─ PostgreSQL (port 6432)
│ └─ Redis (port 6379)
└─ Layanan hanya listen di Tailscale interface
```
## Masalah: Container Tidak Bisa Mencapai Tailscale IP
Docker container secara default hanya bisa mencapai IP di Docker bridge network dan network host. Tailscale menggunakan interface virtual `tailscale0` yang tidak secara otomatis di-route ke container.
### Solusi: `tailscale-routes.service`
Service systemd yang menambahkan route Tailscale ke tabel routing `main` agar traffic dari container bisa melewati host ke Tailscale.
```ini
# /etc/systemd/system/tailscale-routes.service
[Unit]
Description=Add Tailscale routes to main routing table
After=tailscaled.service
Requires=tailscaled.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'ip rule add from all lookup main priority 10000 2>/dev/null; ip route add 100.64.0.0/10 dev tailscale0 table main 2>/dev/null || true'
ExecStop=/bin/sh -c 'ip rule del from all lookup main priority 10000 2>/dev/null; ip route del 100.64.0.0/10 dev tailscale0 table main 2>/dev/null || true'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
```
### Verifikasi
```bash
# Cek apakah route sudah ada
ip route show table main | grep tailscale
# Test dari dalam container
docker run --rm alpine ping -c 3 100.121.180.82
# Test koneksi PostgreSQL dari container
docker run --rm alpine sh -c "apk add postgresql-client && psql -h 100.121.180.82 -p 6432 -U asephs -d hub -c 'SELECT 1'"
```
## Setup Tailscale di Node Baru
### 1. Install Tailscale
```bash
curl -fsSL https://tailscale.com/install.sh | sh
```
### 2. Authenticate
```bash
sudo tailscale up --advertise-routes=<LAN_SUBNET_CIDR>
```
Untuk node yang hanya sebagai client (tidak advertise routes):
```bash
sudo tailscale up
```
### 3. Enable dan Start
```bash
sudo systemctl enable --now tailscaled
```
### 4. Setup Route Service (khusus node dengan Docker)
```bash
# Buat service file
sudo nano /etc/systemd/system/tailscale-routes.service
# Paste content di atas
sudo systemctl daemon-reload
sudo systemctl enable --now tailscale-routes.service
```
### 5. Konfigurasi ACL di Tailscale Admin
Pastikan ACL di [Tailscale Admin Console](https://login.tailscale.com/admin/acls) mengizinkan traffic antar node:
```json
{
"acls": [
{"action": "accept", "src": ["*"], "dst": ["*:*"]}
]
}
```
Atau jika ingin lebih ketat:
```json
{
"acls": [
{"action": "accept", "src": ["tag:server"], "dst": ["tag:server:*"]}
]
}
```
## Konfigurasi iptables/ufw
Pastikan port yang diperlukan terbuka di `imrnes`:
```bash
# PostgreSQL
sudo ufw allow in on tailscale0 to any port 6432 proto tcp
# Redis
sudo ufw allow in on tailscale0 to any port 6379 proto tcp
```
Atau menggunakan iptables langsung:
```bash
sudo iptables -A INPUT -i tailscale0 -p tcp --dport 6432 -j ACCEPT
sudo iptables -A INPUT -i tailscale0 -p tcp --dport 6379 -j ACCEPT
```
## Troubleshooting
### Container timeout connect ke Tailscale IP
```bash
# 1. Cek apakah route service berjalan
systemctl status tailscale-routes.service
# 2. Cek route di host
ip route show table main | grep 100.64
# 3. Cek apakah host bisa ping ke target
ping 100.121.180.82
# 4. Test dari container dengan --network host
docker run --rm --network host alpine ping -c 3 100.121.180.82
# 5. Pastikan tidak ada firewall blocking
iptables -L FORWARD -n -v
```
### Tailscale disconnect
```bash
# Cek status
tailscale status
# Restart
sudo systemctl restart tailscaled
```
### IP Tailscale berubah
Tailscale IP bisa berubah jika node dire-auth. Update:
1. `.env` production di VPS (via GitHub secret `ENV_FILE_PRODUCTION`)
2. Database connection strings
3. Redis connection strings
4. Trigger redeploy
### MagicDNS tidak resolve
```bash
# Cek DNS
tailscale dns status
# Flush DNS cache
sudo resolvectl flush-caches
```
## Catatan Keamanan
- Interface Tailscale (`tailscale0`) hanya boleh diakses oleh node yang terautentikasi dalam network yang sama
- Jangan expose port database ke public interface (`eth0`), hanya ke Tailscale
- Gunakan ACL untuk membatasi akses antar node jika diperlukan
- Rotate auth key secara berkala di Tailscale admin console
+494
View File
@@ -0,0 +1,494 @@
# Troubleshooting
Kumpulan solusi untuk masalah umum di infrastruktur `asepharyana/infra` (orangevps).
> **Catatan (2026-08-02):** Produksi sekarang Caddy + Nix/systemd. Section Traefik/Docker di bawah adalah LEGACY — Docker dan Traefik dihapus dari produksi; gunakan hanya sebagai referensi historis.
## Daftar Isi
- [Deployment](#deployment)
- [Dapr](#dapr)
- [NATS](#nats)
- [Tailscale / Networking](#tailscale--networking)
- [Caddy](#caddy)
- [Database](#database)
- [Submodule](#submodule)
---
## Deployment
### Workflow deploy gagal: "Secrets not fully configured"
**Penyebab:** Salah satu GitHub secrets tidak diset.
**Solusi:** Cek secrets di Settings > Secrets and variables > Actions:
| Secret | Status |
|--------|--------|
| `SSH_PRIVATE_KEY` | Wajib |
| `VPS_HOST` | Wajib (`45.127.35.244`) |
| `VPS_USER` | Wajib (`root`) |
| ~~`VPS_TARGET_DIR`~~ | Tidak dipakai lagi (CI baru tidak checkout VPS) |
| `ENV_FILE_PRODUCTION` | Wajib |
### Workflow build gagal: "Submodule commit not fetchable"
**Penyebab:** Commit SHA dari `repository_dispatch` belum tersedia di remote submodule repo (eventual consistency).
**Solusi:** Workflow akan retry hingga 5 menit. Jika masih gagal:
```bash
# Cek apakah commit ada di remote
git ls-remote https://github.com/asepharyana/scraper.git <SHA>
# Trigger ulang dispatch dari submodule repo, atau push langsung ke hub
```
### Push manifest gagal: conflict di main
**Penyebab:** Ada commit lain yang masuk sebelum workflow selesai.
**Solusi:** Workflow otomatis retry rebase 3 kali. Jika semua gagal:
```bash
# Manual fix di lokal
git pull --rebase origin main
# resolve conflict
git push origin main
```
---
## Dapr
### Dapr sidecar tidak connect ke placement
**Gejala:** Container `scraper-api-dapr` restart loop. Log: `failed to connect to placement`
**Diagnosis:**
```bash
# Cek log sidecar
docker logs scraper-api-dapr --tail 50
# Cek apakah placement service running
docker ps -a | grep dapr-placement
docker logs dapr-placement --tail 20
# Cek konektivitas
docker exec scraper-api-dapr curl -s http://dapr-placement:50005
```
**Solusi:**
```bash
# Restart placement dulu, lalu sidecar
docker compose -f infra/compose/dapr.yml up -d --force-recreate
sleep 5
docker compose -f infra/compose/scraper.yml up -d --force-recreate scraper-api-dapr
```
### Dapr pub/sub tidak bekerja
**Gejala:** Event di-publish tapi tidak sampai ke subscriber.
**Diagnosis:**
```bash
# Cek komponen Dapr
curl http://localhost:3500/v1.0/components
# Cek health sidecar
curl http://localhost:3500/v1.0/healthz
# Cek Redis (backend pub/sub)
docker exec redis redis-cli ping
```
**Solusi:**
```bash
# Restart sidecar
docker restart scraper-api-dapr
# Jika Redis bermasalah, restart juga
docker restart redis
```
### Dapr state store error: "key not found"
**Penyebab:** Key belum ada di state store, atau prefix berbeda.
**Diagnosis:**
```bash
# Cek state langsung di Redis
docker exec redis redis-cli KEYS 'dapr*'
# State store menggunakan prefix "dapr"
# Format key: dapr || <app-id> || <key>
```
---
## NATS
### NATS tidak bisa start
**Gejala:** Container NATS restart loop.
**Diagnosis:**
```bash
docker logs nats --tail 50
```
**Solusi:** Kemungkinan korupsi data JetStream:
```bash
# Backup dulu volume data
docker run --rm -v nats_data:/data -v /tmp:/backup alpine cp -r /data /backup/nats_data_backup
# Hapus volume dan recreate
docker compose -f infra/compose/nats.yml down
docker volume rm asepharyana-hub_nats_data
docker compose -f infra/compose/nats.yml up -d
```
### JetStream stream overflow
**Gejala:** Disk penuh, NATS lambat.
**Diagnosis:**
```bash
# Cek ukuran volume
docker system df | grep nats_data
du -sh /var/lib/docker/volumes/nats_data/_data/
# Cek stream info
nats stream list
nats stream info <stream-name>
```
**Solusi:**
```bash
# Purge stream tertentu (data hilang)
nats stream purge <stream-name>
# Atau tambah limit stream via NATS config
```
### "Slow Consumer" warning
**Gejala:** Log NATS menampilkan "slow consumer".
**Diagnosis:**
```bash
curl http://localhost:8222/varz | jq '.slow_consumers'
```
**Solusi:**
- Scale consumer (tambah worker)
- Percepat processing message
- Kurangi ukuran payload
---
## Traefik
### Traefik tidak routing ke service
**Gejala:** 404 atau 503 dari Traefik.
**Diagnosis:**
```bash
# Cek apakah service container running
docker ps -a | grep scraper-api
# Cek log Traefik
docker logs traefik --tail 50
# Cek apakah container ada di network yang benar
docker network inspect app-shared-net | grep scraper-api
# Test routing langsung
curl -H "Host: scraper.asepharyana.my.id" http://localhost/
```
**Solusi:**
```bash
# Pastikan service terdaftar di apps.yaml
# Pastikan container join app-shared-net
# Restart Traefik
docker compose -f infra/compose/traefik.yml up -d --force-recreate
```
### TLS certificate error
**Gejala:** Browser menampilkan warning certificate.
**Diagnosis:**
```bash
# Cek sertifikat di host
ls -la /root/asepharyana.my.id.pem
openssl x509 -in /root/asepharyana.my.id.pem -text -noout | head -20
# Cek apakah Traefik bisa mount
docker exec traefik ls -la /etc/traefik/certs/
```
**Solusi:**
- Update sertifikat di host
- Restart Traefik
- Jika path berbeda, set environment variable `TRAEFIK_CERT_*`
### Rate limit terlalu ketat
**Gejala:** Request legitimate di-block.
**Diagnosis:**
```bash
# Cek rate-limit config di middlewares.yaml
# Current: average 100, burst 50
```
**Solusi:** Ubah nilai `average` dan `burst` di `infra/traefik/dynamic/middlewares.yaml`, lalu reload:
```bash
docker kill --signal HUP traefik
# atau
docker exec traefik kill -HUP 1
```
---
## Tailscale / Networking
### Container tidak bisa connect ke Tailscale IP
**Gejala:** Timeout saat container connect ke `100.121.180.82:6432`.
**Diagnosis:**
```bash
# Cek route service dari host
systemctl status tailscale-routes.service
# Cek route di host
ip route show table main | grep 100.64
# Cek koneksi dari host
ping 100.121.180.82
# Test dari container (dengan --network host)
docker run --rm --network host alpine ping -c 3 100.121.180.82
```
**Solusi:**
```bash
# Restart route service
sudo systemctl restart tailscale-routes.service
# Atau tambah route manual
sudo ip rule add from all lookup main priority 10000
sudo ip route add 100.64.0.0/10 dev tailscale0 table main
```
### Database connection refused
**Gejala:** Service tidak bisa konek ke PostgreSQL.
**Diagnosis:**
```bash
# Cek apakah DB listen di Tailscale (dari imrnes)
ss -tlnp | grep 6432
# Cek dari orangevps
nc -zv 100.121.180.82 6432
# Cek firewall di imrnes
sudo ufw status
sudo iptables -L -n | grep 6432
```
**Solusi:**
```bash
# Di imrnes: pastikan PostgreSQL bind ke Tailscale interface
# Di postgresql.conf:
listen_addresses = 'localhost,100.121.180.82'
# Di pg_hba.conf:
host hub asephs 100.0.0.0/8 md5
# Restart PostgreSQL
sudo systemctl restart postgresql
```
### Redis connection refused dari container
**Gejala:** Service tidak bisa connect ke `redis://redis:6379`.
**Diagnosis:**
```bash
# Cek apakah container Redis running
docker ps -a | grep redis
# Cek apakah container target join network yang sama
docker inspect <container> | grep -A5 Networks
# Cek DNS resolve dari container
docker exec <container> getent hosts redis
```
**Solusi:**
```bash
# Pastikan Redis ada di network app-shared-net
docker network inspect app-shared-net | grep redis
# Jika tidak, attach
docker network connect app-shared-net redis
```
---
## Docker / Container
### Container restart loop
**Diagnosis:**
```bash
docker logs <container> --tail 50
docker inspect <container> | jq '.[].State'
```
**Penyebab umum:**
- Health check gagal
- Dependency service belum siap
- Environment variable tidak diset
### Image pull gagal dari GHCR
**Gejala:** `docker pull` gagal di VPS.
**Diagnosis:**
```bash
# Cek login
cat ~/.docker/config.json | grep ghcr
# Cek visibility package
# Buka https://github.com/orgs/asepharyana/packages
```
**Solusi:**
```bash
# Re-login
echo $GITHUB_TOKEN | docker login ghcr.io -u asepharyana --password-stdin
# Pastikan package visibility public atau di-share ke org
```
### Disk penuh
**Gejala:** Container crash, write error.
**Diagnosis:**
```bash
df -h
docker system df
du -sh /var/lib/docker/
```
**Solusi:**
```bash
# Bersihkan container/image/volume yang tidak dipakai
docker system prune -a -f
# Hapus image lama
docker image prune -a -f
# Lihat volume terbesar
docker system df -v | grep -E "(nats_data|redis_data)"
```
---
## Database
### Koneksi PostgreSQL lambat
**Gejala:** Query time high, connection timeout.
**Diagnosis:**
```bash
# Dari container, test latency
docker exec scraper-api ping -c 5 100.121.180.82
# Cek koneksi aktif
docker exec scraper-api psql $DATABASE_URL -c "SELECT count(*) FROM pg_stat_activity;"
```
**Solusi:**
- Cek Tailscale latency
- Adjust connection pool size
- Cek resource PostgreSQL di `imrnes`
### Migration gagal
**Gejala:** Service error setelah image update.
**Diagnosis:**
```bash
# Cek log service
docker logs scraper-api --tail 100 | grep -i migration
```
**Solusi:**
- Migration ada di submodule `apps/scraper`, bukan di hub
- Pastikan schema sesuai dengan versi code
- Rollback image jika migration tidak backward-compatible
---
## Submodule
### HEAD detached di submodule
**Gejala:** `git status` di `apps/scraper` menunjukkan "HEAD detached".
**Penyebab:** Normal. Submodule selalu dalam keadaan detached HEAD karena mengacu pada commit spesifik.
**Solusi:** Jangan commit perubahan dari dalam submodule. Selalu bekerja di repo asli.
### Submodule tidak ter-update setelah pull
```bash
git submodule update --init --recursive
```
### Konflik submodule saat rebase/merge
```bash
# Resolve dengan memilih versi yang benar
git add apps/scraper
git rebase --continue
```

Some files were not shown because too many files have changed in this diff Show More