SSD Nodes Learn 8GB RAM — $66/năm
Hướng dẫn Matt ConnorBởi Matt Connor

Tự host Open Connector cho AI agent trên VPS

Chạy Open Connector trên VPS để agent không giữ SaaS token: pin image, cấu hình TLS origin, OAuth callback và backup file SQLite đúng cách.

Open Connector làm gì cho AI agent

Tự host Open Connector đặt một auth gateway giữa các AI agent của bạn và mọi API software as a service (SaaS) mà chúng gọi. Vì vậy, agent không bao giờ giữ token của provider. Đây là một gateway open source do OOMOL Lab phát triển và được cấp phép theo Apache 2.0. Gateway chạy dưới dạng một container, lưu trạng thái trong một file SQLite duy nhất, đồng thời cung cấp các action của provider qua HTTP và MCP (model context protocol).

Vấn đề bắt đầu từ integration thứ hai. Mỗi provider có flow OAuth (open authorization) riêng, thời hạn refresh token riêng và tên scope riêng. Tự kết nối năm provider vào một agent nghĩa là bạn phải viết năm redirect handler, năm credential store và năm vòng lặp refresh. Các vòng lặp này phải chạy trước khi token hết hạn. Gần như không ai viết phần code đó. Họ tạo một personal access token có thời hạn dài cho mỗi service rồi dán token vào cấu hình agent, file môi trường hoặc chính prompt. Khi đó, mọi tool mà agent chạy đều có thể đọc token. Token cũng xuất hiện trong transcript. Đây là vấn đề được mô tả trong cách giữ secret ngoài AI agent.

Auth gateway tách credential thành hai phần. Gateway lưu credential của provider và chạy flow OAuth. Agent nhận một runtime token chỉ có hiệu lực khi gọi gateway. Khi agent gọi một action, gateway tải credential đã lưu, chèn credential vào request gửi đi ở phía server rồi chỉ trả về response body. Agent không bao giờ nhận access token của provider. Vì vậy, nếu transcript của agent bị lộ, bạn chỉ phải thu hồi một runtime token thay vì phải xử lý toàn bộ tài khoản GitHub.

Catalog giới thiệu hơn 1,000 provider và 10,000 action dựng sẵn. Đây là con số do chính dự án đưa ra, không phải con số bạn có thể kiểm chứng từ bên ngoài. Điều bạn có thể kiểm chứng là mô hình hoạt động: mỗi action có một HTTP endpoint, mỗi provider có một connection được lưu và mỗi agent có một token.

Vì sao nên tự host Open Connector thay vì dùng dịch vụ connector được host sẵn

Dịch vụ connector được host sẵn thực hiện cùng công việc và lưu refresh token cho mọi provider mà bạn kết nối. Refresh token của Google hoặc GitHub là key dài hạn để truy cập email và repository của bạn. Token này thường vẫn còn hiệu lực sau khi bạn đổi mật khẩu. Nếu dịch vụ đó bị breach, hệ thống của bạn cũng bị breach. Tự host chuyển các bản ghi đó vào SQLite trên một máy mà bạn thuê và quản trị. Dữ liệu được bảo vệ bằng một key không bao giờ rời khỏi máy của bạn.

Hãy tính rõ chi phí trước khi bắt đầu. VPS này sẽ trở thành server có giá trị nhất mà bạn vận hành. Nó lưu credential đang hoạt động của hàng chục dịch vụ trong một file. Vì vậy, bạn phải bảo vệ nó như một host chạy password manager: firewall chỉ expose port 443, không dùng login dùng chung, có một bản backup mà bạn đã thực sự restore ít nhất một lần, và có alert khi server ngừng phản hồi. Nếu bạn không đặt password vault trên máy này, cũng không đặt connector trên đó.

Cố định phiên bản trước khi cài đặt bất kỳ thứ gì

Open Connector còn mới. Repository này xuất hiện lần đầu vào ngày 29 tháng 6 năm 2026. Tính đến ngày 1 tháng 8 năm 2026, bản release mới nhất được gắn tag là v1.3.3, phát hành ngày 30 tháng 7 năm 2026 và cũng có tag latest. Registry cũng cung cấp tag tip, được build từ commit mới nhất trên main.

Với một project mới như vậy, các tag luôn thay đổi. Một docker compose pull nhảy qua 2 bản release có thể thay đổi endpoint mà agent của bạn phụ thuộc vào. Khi đó, bạn sẽ mất cả buổi tối để debug và tưởng rằng đó là lỗi của agent. Hãy cố định image vào tag của một bản release. Chỉ upgrade khi bạn chủ động quyết định, sau khi đọc release notes.

Triển khai Open Connector phía sau TLS trên VPS của bạn

Trước khi container khởi động, bạn cần:

  • Docker có plugin Compose, trên Ubuntu 24.04 hoặc bản tương đương
  • một hostname có bản ghi A trỏ đến VPS này, ví dụ connect.example.com
  • một reverse proxy đã xử lý TLS (transport layer security) cho hostname đó
  • hai secret ngẫu nhiên được tạo bên dưới

Reverse proxy Traefik cho nhiều ứng dụng Docker Compose trình bày phần cấu hình proxy. Hướng dẫn n8n trên VPS với Docker và HTTPS trình bày toàn bộ quy trình cấu hình certificate cho một ứng dụng.

Trước tiên, hãy tạo các secret. Encryption key dùng để bảo vệ thông tin xác thực đã lưu. Admin token bảo vệ web console và toàn bộ bề mặt /api. Cả hai đều không có giá trị mặc định, nhưng runtime vẫn khởi động bình thường nếu thiếu chúng.

mkdir -p ~/open-connector && cd ~/open-connector
umask 077
printf 'OOMOL_CONNECT_ENCRYPTION_KEY=%s\n' "$(openssl rand -base64 32)" > .env
printf 'OOMOL_CONNECT_ADMIN_TOKEN=%s\n' "$(openssl rand -base64 32)" >> .env
chmod 600 .env

Ngay bây giờ, hãy sao chép cả hai giá trị vào password manager, trước lần khởi động đầu tiên. Encryption key không có cách khôi phục. Phần giải thích nằm trong danh sách lỗi bên dưới.

Bây giờ hãy compose.yaml. Cấu hình này khác ví dụ upstream ở hai điểm, và cả hai đều quan trọng.

services:
  connector:
    image: ghcr.io/oomol-lab/open-connector:v1.3.3
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - connector-data:/app/data
    environment:
      OOMOL_CONNECT_DATA_DIR: /app/data
      OOMOL_CONNECT_ORIGIN: "https://connect.example.com"
      OOMOL_CONNECT_ENCRYPTION_KEY: "${OOMOL_CONNECT_ENCRYPTION_KEY:?set this in .env}"
      OOMOL_CONNECT_ADMIN_TOKEN: "${OOMOL_CONNECT_ADMIN_TOKEN:?set this in .env}"

volumes:
  connector-data:

Thay đổi đầu tiên là dùng tag cố định thay cho latest. Thay đổi thứ hai là port. File upstream publish 3000:3000, tức là bind trên mọi interface của host. Docker ghi các port đã publish vào bảng NAT (network address translation) trước khi packet đi qua ufw filter chain. Vì vậy, ufw deny 3000 không đóng port đó; đây là lỗi được mô tả trong vì sao port Docker bypass ufw. Viết 127.0.0.1:3000:3000 sẽ publish chỉ trên loopback interface, còn reverse proxy của bạn kết nối từ cùng host.

:? đánh dấu mỗi biến là bắt buộc. Vì vậy, stack sẽ từ chối khởi động nếu thiếu .env, thay vì khởi động với credential chưa được mã hóa. Lưu các giá trị trong .env thay vì compose file là cách làm được trình bày trong file env và secret của Docker Compose.

docker compose up -d
docker compose logs -n 30 connector
curl -s http://127.0.0.1:3000/health
sudo ss -tlnp | grep 3000

/health trả lời { "ok": true } sau khi runtime khởi động. ss phải in ra 127.0.0.1:3000. Nếu dòng kết quả là 0.0.0.0:3000, port mapping vẫn là cấu hình upstream và gateway đang trả lời trực tiếp toàn bộ internet. Nếu health check báo connection refused, container chưa listen. Hãy đọc log trước khi chỉnh proxy.

Label Traefik cho cùng service
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.connector.rule=Host(`connect.example.com`)"
      - "traefik.http.routers.connector.entrypoints=websecure"
      - "traefik.http.routers.connector.tls.certresolver=le"
      - "traefik.http.services.connector.loadbalancer.server.port=3000"

Khi Traefik chạy trong Docker trên cùng host, hãy kết nối service này vào Traefik network và xóa block ports:. Traefik truy cập container qua internal network nên không cần publish gì lên host. certresolver=le phải khớp với tên resolver trong Traefik static config. Nếu không, router sẽ khởi động nhưng không có certificate.

Vì sao OAuth bắt buộc bạn phải có hostname thực

OOMOL_CONNECT_ORIGIN là thiết lập thường bị bỏ qua. Khi bỏ qua, OAuth bị lỗi theo cách khiến bạn tưởng provider bị lỗi. Runtime tạo redirect URI từ origin đó, theo dạng <origin>/oauth/callback. Nếu không được thiết lập, origin mặc định là http://localhost:3000. Khi đó, runtime gửi cho provider redirect URI là http://localhost:3000/oauth/callback, trong khi OAuth app của bạn đã đăng ký https://connect.example.com/oauth/callback. Hai chuỗi này khác nhau, nên GitHub trả về:

The redirect_uri MUST match the registered callback URL for this application.

OAuth provider chuyển hướng trình duyệt về URI đó. Vì vậy, URI phải là địa chỉ mà Internet có thể truy cập. Provider từ chối http:// thông thường, ngoại trừ localhost. Đây là toàn bộ lý do deployment này cần hostname và certificate. Hãy thiết lập origin trước lần khởi động đầu tiên, vì giá trị này được đọc khi startup. Sau khi chỉnh sửa .env hoặc compose.yaml, chạy lại docker compose up -d để áp dụng.

Kết nối provider đầu tiên qua OAuth

Trước tiên, tạo ứng dụng OAuth tại provider. Trên GitHub, vào Settings, sau đó chọn Developer settings, rồi OAuth Apps và New OAuth App. Đặt URL callback authorization thành https://connect.example.com/oauth/callback. Lưu client ID và client secret.

Mỗi lệnh gọi /api đều gửi kèm admin token, vì vậy hãy export token một lần cho phiên shell.

export ADMIN_TOKEN='paste-the-admin-token'
curl -s https://connect.example.com/api/oauth/configs \
  -H "authorization: Bearer $ADMIN_TOKEN"

Danh sách này hiển thị redirect URI mà runtime yêu cầu cho từng provider. Đây là cách nhanh nhất để kiểm tra origin đã có hiệu lực hay chưa. Nếu vẫn hiển thị localhost, container đang chạy với giá trị cũ và OAuth flow sẽ thất bại ở bước cuối.

Lưu thông tin xác thực của client, sau đó bắt đầu authorization.

curl -s -X PUT https://connect.example.com/api/oauth/configs/github \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"clientId":"...","clientSecret":"..."}'

curl -s -X POST https://connect.example.com/api/oauth/authorizations \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"service":"github"}'

Lệnh gọi thứ hai trả về một authorizationUrl. Mở nó trong trình duyệt, phê duyệt các scope, rồi provider sẽ chuyển trình duyệt về /oauth/callback. Tại đó, runtime đổi code và lưu thông tin xác thực. Web console tại origin của bạn cũng thực hiện các bước tương tự bằng form, với cùng admin token. Các provider dùng API key đơn giản sẽ bỏ qua toàn bộ quy trình này: PUT /api/connections/<service> với {"authType":"api_key","values":{"apiKey":"..."}} sẽ lưu key trực tiếp.

Cấp cho mỗi agent một runtime token, không bao giờ cấp credential

Agent xác thực với gateway bằng runtime token do admin API tạo.

curl -s -X POST https://connect.example.com/api/runtime-tokens \
  -H "authorization: Bearer $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"research-agent"}'

Response chứa một token bắt đầu bằng oct_. Cấp một token cho từng agent và đặt tên theo agent đó, vì thu hồi một token mà bạn không xác định được đồng nghĩa với việc phải thu hồi tất cả token. Sau đó agent gọi các action qua HTTP thông thường.

curl -s -X POST https://connect.example.com/v1/actions/github.get_current_user \
  -H "authorization: Bearer oct_..." \
  -H 'content-type: application/json' \
  -d '{"input":{}}'

Response hợp lệ là một envelope có trường success bằng true, còn payload của provider nằm trong data. GitHub token không xuất hiện ở bất kỳ đâu trong response đó. Với MCP client, trỏ client đến https://connect.example.com/mcp và dùng cùng bearer header. Gateway sẽ cung cấp các công cụ discovery như search_actionsexecute_action thay vì một tool cho mỗi API, nhờ đó giữ danh sách tool của agent ở mức nhỏ. Chạy MCP server trên VPS trình bày phần cấu hình phía client của kết nối này.

Thực hiện thêm một kiểm tra trước khi xem là đã hoàn tất. Gửi lại action call sau khi xóa header authorization. Quickstart của project gọi /v1 mà không có bearer, vì vậy một cài đặt không cấu hình runtime auth sẽ thực thi action cho bất kỳ ai có thể truy cập port. Nếu unauthenticated call thành công, bạn có 2 cách xử lý: cấu hình runtime token và xác nhận rằng anonymous call hiện đã thất bại, hoặc giới hạn /api, /v1/mcp trên reverse proxy chỉ cho các địa chỉ mà agent của bạn sử dụng. Chỉ /oauth/callback phải mở cho toàn bộ Internet, vì đó là đường dẫn duy nhất mà browser redirect của provider cần.

Thu gọn danh sách hành động xuống mức agent cần

Một gateway có hàng nghìn provider phía sau sẽ tạo ra bề mặt truy cập quá rộng cho language model. Có 2 cơ chế để thu hẹp phạm vi này.

OOMOL_CONNECT_ALLOWED_ACTIONS nhận allowlist được phân tách bằng dấu phẩy và hiểu service.* cùng *. OOMOL_CONNECT_BLOCKED_ACTIONS là denylist, và denylist được ưu tiên. Đặt allowlist thành github.get_current_user,github.list_issues nghĩa là mọi hành động khác đều bị từ chối, bất kể agent yêu cầu gì. Đây là ranh giới giữa một lỗi và một sự cố. Runtime token có các quy tắc hành động riêng, áp dụng thêm trên các quy tắc toàn cục. Danh sách allowedProxies của chúng bắt đầu trống, nên POST /v1/proxy/:service bị từ chối cho đến khi bạn cấp quyền. Endpoint proxy đó chuyển tiếp request thô đến provider kèm credential của bạn. Vì vậy, để trống mục này trừ khi một agent cụ thể cần dùng.

OOMOL_CONNECT_ALLOW_PRIVATE_NETWORK mặc định là false. Thiết lập này ngăn kết nối đến provider tự host trỏ vào địa chỉ private, chẳng hạn cloud metadata service tại 169.254.169.254 hoặc database trên cùng network. Hãy tắt tùy chọn này. Chỉ bật khi provider do chính bạn host.

Sao lưu máy chủ chứa mọi token

Có 2 thứ quan trọng, và mỗi thứ đều vô dụng nếu thiếu thứ kia. Database tại /app/data/connect.sqlite bên trong volume connector-data chứa các thông tin xác thực đã được niêm phong. Encryption key trong .env dùng để mở chúng. Sao lưu volume mà không có key thì không khôi phục được gì, còn key mà không có volume cũng không khôi phục được gì. Vì vậy, lưu key trong password manager và đưa volume vào quy trình sao lưu thông thường.

Dừng container trong khi sao chép file SQLite. Bản sao được tạo trong lúc đang ghi có thể khôi phục thành database bị hỏng.

docker volume ls | grep connector-data
docker compose stop connector
docker run --rm -v open-connector_connector-data:/data -v "$PWD":/backup alpine \
  tar czf /backup/connector-data.tgz -C /data .
docker compose start connector

Tên volume là thư mục project cộng với _connector-data. Vì vậy lệnh đầu tiên có mặt ở đây: dán tên thực vào lệnh thứ 3. Đưa archive ra khỏi VPS bằng restic sao lưu từ VPS. restic mã hóa archive trước khi archive rời máy, vì archive đó chứa credential store.

Runtime giữ các lần chạy action gần đây dưới dạng audit record, mặc định là 5,000 bản ghi. Nhờ đó, console có thể cho biết agent nào đã chạy việc gì và vào thời điểm nào. Đây là log đầu tiên cần đọc khi agent hoạt động bất thường. Đồng thời, trỏ trang trạng thái Uptime Kuma đến https://connect.example.com/health. Khi gateway ngừng phản hồi, agent có thể lỗi theo những cách khó hiểu. Biết gateway đang down giúp bạn không phải mất cả giờ đọc output của agent.

Những lỗi có thể xảy ra và thông báo bạn sẽ thấy

redirect_uri_mismatch tại provider. Origin và callback URL đã đăng ký không giống nhau. So sánh chính xác chuỗi từ /api/oauth/configs với phần cài đặt ứng dụng của provider, bao gồm cả https với http và mọi dấu gạch chéo ở cuối.

Mọi lệnh gọi /api đều trả về 401. Header admin token bị thiếu hoặc viết sai. Header là Authorization: Bearer <token>, và web console cũng yêu cầu cùng token đó.

Container vẫn chạy, còn thông tin xác thực được lưu dưới dạng plain text. Điều này xảy ra khi OOMOL_CONNECT_ENCRYPTION_KEY không được truyền vào container, vì runtime lưu các bản ghi thông tin xác thực mà không mã hóa thay vì từ chối khởi động. Hãy kiểm tra trên chính hệ thống của bạn: kết nối một provider bằng API key mà bạn có thể nhận diện, rồi tìm key đó trong database.

docker compose cp connector:/app/data/connect.sqlite /tmp/connect.sqlite
grep -c 'github_pat_' /tmp/connect.sqlite
shred -u /tmp/connect.sqlite

Giá trị lớn hơn 0 nghĩa là key chưa có hiệu lực. Hãy kiểm tra để .env nằm cùng thư mục với compose.yamldocker compose config hiển thị giá trị đó. Khi đã đặt key, cùng truy vấn sẽ trả về 0, vì bản ghi được mã hóa bằng AES-256-GCM (advanced encryption standard, key 256-bit, chế độ Galois/counter).

Không có dữ liệu nào được giải mã sau khi restore. Encryption key đã thay đổi hoặc bị mất. Theo thiết kế, key không bao giờ được ghi cạnh dữ liệu, nên không có cách khôi phục và không có support ticket nào giải quyết được. Hãy kết nối lại mọi provider. Có thể rotation thông qua một biến key riêng và một data command trong runtime, vì vậy hãy đọc release notes hiện tại trước khi rotation.

Agent báo lỗi về một action mà nó vẫn nhìn thấy trong catalog. Discovery và execution là hai quy trình riêng. Một action có thể xuất hiện trong search_actions nhưng vẫn bị OOMOL_CONNECT_ALLOWED_ACTIONS, denylist hoặc các rule riêng của runtime token đó từ chối.

Nâng cấp. Backup volume, sửa image tag thành release mới, rồi docker compose pull && docker compose up -d. Theo dõi docker compose logs -n 50 connector để tìm dòng migration. Sau đó chạy lại health check và một action thực tế trước khi tiếp tục tin cậy hệ thống. Rollback nghĩa là khôi phục old tag. Việc này chỉ hoạt động vì bạn đã pin tag đó.

FAQ

Tôi có cần domain công khai để tự host Open Connector không?

Với các provider dùng API key thì không: một gateway trên 127.0.0.1 là đủ. Với OAuth thì trên thực tế là có. Provider chuyển hướng trình duyệt đến callback URL của bạn, nên URL đó phải phân giải được từ internet công cộng. Provider cũng từ chối http:// thuần túy bên ngoài localhost. Đặt OOMOL_CONNECT_ORIGIN thành hostname https:// của bạn trước lần khởi động đầu tiên, rồi đăng ký <origin>/oauth/callback trong OAuth app của provider.

Điều gì xảy ra nếu tôi mất encryption key của Open Connector?

Không thể giải mã các credential đã lưu và không có cách khôi phục. Key này cố ý không bao giờ được lưu cùng dữ liệu, nên không ai có database có thể đọc được dữ liệu đó, kể cả bạn. Lựa chọn duy nhất của bạn là đặt key mới và kết nối lại mọi provider. Lưu key trong password manager và đưa database vào quy trình backup định kỳ, vì việc restore cần cả hai.

AI agent của tôi có thể thấy access token của provider không?

Không, khi agent gọi thông qua gateway. Agent xác thực bằng runtime token bắt đầu bằng oct_. Gateway chèn credential của provider vào outbound request trên server và chỉ trả về response. Có 2 trường hợp làm mất tính chất này: endpoint /v1/proxy/:service, vốn chuyển tiếp raw request kèm credential của bạn và có grant bắt đầu trống vì một lý do; và việc tự dán API key vào agent, vốn bỏ qua hoàn toàn gateway.

Gateway có nên truy cập được từ internet công cộng không?

Chỉ /oauth/callback cần truy cập được. Publish container port trên 127.0.0.1 để các NAT rule của Docker không thể expose nó vượt qua firewall của bạn, rồi đặt reverse proxy ở phía trước. Sau đó kiểm tra một action call không có header authorization. Nếu thành công, giới hạn /api, /v1/mcp tại proxy chỉ cho các địa chỉ mà agent của bạn sử dụng, cho đến khi chỉ các authenticated call mới hoạt động.

Open Connector đã sẵn sàng để dùng trong production chưa?

Nó được cấp phép theo Apache 2.0 và đang phát triển nhanh: repository xuất hiện vào 29 June 2026 và v1.3.3 được release vào 30 July 2026. Vì vậy, hãy xem mọi số phiên bản trong hướng dẫn này là snapshot của 1 August 2026. Chạy nó với release tag được pin, tuyệt đối không chạy trên latest hoặc tip, đọc release notes trước mỗi lần upgrade và duy trì một volume backup mà bạn đã restore thử một lần. Thiết kế này phù hợp với một box do bạn sở hữu. Rủi ro nằm ở việc phiên bản thay đổi liên tục, không phải ở architecture.