SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-25

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

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

AI agent dùng Open Connector để làm gì

Tự host Open Connector tạo 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ữ provider token. Đây là một gateway mã nguồn mở do OOMOL Lab phát triển, được cấp phép theo Apache 2.0. Gateway chạy trong một container, lưu state trong một file SQLite duy nhất, đồng thời cung cấp các provider action qua HTTP và MCP (model context protocol).

Vấn đề bắt đầu từ integration thứ hai. Mỗi provider có OAuth (open authorization) flow riêng, thời hạn refresh token riêng và tên scope riêng. Tự nối 5 provider vào một agent có nghĩa là phải viết 5 redirect handler, 5 credential store và 5 refresh loop 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 agent config, file môi trường hoặc chính prompt. Khi đó, mọi tool mà agent chạy đều có thể đọc token này. Token cũng xuất hiện trong transcript. Đây là failure mà cách giữ secret ngoài AI agent mô tả.

Auth gateway tách credential thành 2 phần. Gateway lưu provider credential và chạy OAuth flow. 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, inject credential đó vào outbound request ở phía server rồi chỉ trả về response body. Agent không bao giờ nhận provider access token. Vì vậy, nếu transcript của agent bị lộ, bạn chỉ phải revoke một runtime token, thay vì phải xử lý toàn bộ GitHub account.

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 project đưa ra, không phải thông tin có thể kiểm chứng từ bên ngoài. Điều 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 trữ và mỗi agent có một token. Nếu phần agent vẫn còn mới với bạn, đồng thời các thuật ngữ như tool call hoặc MCP server chưa rõ ràng, lộ trình từng bước trong cách học AI agent từ đầu sẽ xây dựng dần loop, tool và các thói quen an toàn mà một gateway như thế này giả định bạn đã có.

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à giữ refresh token của mọi provider bạn kết nối với nó. Refresh token của Google hoặc GitHub là credential dài hạn để truy cập email và repository của bạn, đồng thời thường vẫn có hiệu lực sau khi bạn đổi password. Nếu dịch vụ đó bị breach, bạn cũng bị ảnh hưởng. Tự host chuyển các bản ghi này vào SQLite trên một máy bạn thuê và quản trị, đượ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 bạn vận hành. Nó giữ credential đang hoạt động của hàng chục dịch vụ trong một file, nên cần được bảo vệ như một host chạy password manager: firewall chỉ mở cổng 443, không dùng chung login, có một bản backup mà bạn đã thực sự restore ít nhất một lần, và có cảnh báo khi server không còn phản hồi. Nếu bạn không đặt password vault trên máy này, cũng đừng đặt connector trên đó.

Ghim phiên bản trước khi cài đặt bất cứ thứ gì

Open Connector còn mới. Repository này xuất hiện lần đầu vào ngày 29 June 2026. Tính đến ngày 1 August 2026, bản release mới nhất được gắn tag là v1.3.3, phát hành vào ngày 30 July 2026 và cũng có tag latest. Registry cũng publish 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 di động thường xuyê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ỗi nằm ở agent. Hãy ghim 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 phiên 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 đã thực hiện TLS termination (transport layer security) cho hostname đó
  • hai secret ngẫu nhiên được tạo ở bên dưới

Hướng dẫn Reverse proxy Traefik cho nhiều ứng dụng Docker Compose trình bày phần proxy. Quy trình cấu hình certificate từ đầu đến cuối cho một app nằm trong hướng dẫn n8n trên VPS với Docker và HTTPS.

Trước tiên, hãy tạo các secret. Encryption key bảo vệ credentials được lưu trữ. Admin token bảo vệ web console và toàn bộ bề mặt /api. Cả hai không có giá trị mặc định, nhưng runtime vẫn khởi động bình thường khi 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 lưu 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. Lý do được nêu trong danh sách lỗi bên dưới.

Bây giờ compose.yaml. File này khác ví dụ upstream ở hai chỗ, 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 pin thay cho latest. Thay đổi thứ hai là cổng. File upstream publish 3000:3000, tức là bind trên mọi interface của host. Docker ghi các published port vào bảng NAT (network address translation) trước khi packet đi qua filter chain của ufw, nên ufw deny 3000 không đóng cổng đó. Đây là bẫy được mô tả trong vì sao port Docker bypass ufw. Viết 127.0.0.1:3000:3000 sẽ publish cổng chỉ trên loopback interface, và reverse proxy của bạn sẽ 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 khi thiếu .env thay vì khởi động với credentials chưa được mã hóa. Lưu các giá trị trong .env thay vì trong 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. Dòng có nội dung 0.0.0.0:3000 nghĩa là 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, nghĩa là container chưa listening. Hãy đọc log trước khi kiểm tra proxy.

Các 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 nối service này vào network của Traefik và xóa block ports:, vì 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 static config của Traefik, nếu không router sẽ khởi động nhưng không có certificate.

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

OOMOL_CONNECT_ORIGIN là thiết lập thường bị bỏ qua, và việc bỏ qua khiến OAuth lỗi theo cách trông giống lỗi của provider. Runtime tạo redirect URI từ origin đó, theo dạng <origin>/oauth/callback. Nếu không đặt, origin mặc định là http://localhost:3000, nên 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 browser quay lại URI đó. Vì vậy, URI phải là địa chỉ mà bên ngoài có thể truy cập, và các provider từ chối http:// thuần cho mọi trường hợp ngoại trừ localhost. Đó là toàn bộ lý do deployment này cần hostname và certificate. Hãy đặt origin trước lần khởi động đầu tiên vì giá trị này được đọc khi khởi động: sau khi 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 OAuth app trên provider. Trên GitHub, vào Settings, rồi Developer settings, rồi OAuth Apps, rồi New OAuth App. Đặt authorization callback URL 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 shell session.

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 cần cho từng provider. Đây là cách nhanh nhất để kiểm tra origin đã được áp dụng chưa. Nếu vẫn hiển thị localhost, container đang chạy với giá trị cũ và OAuth flow sẽ fail ở bước cuối.

Lưu client credentials, rồi 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 browser, chấp thuận các scope, rồi provider sẽ đưa browser quay lại /oauth/callback. Tại đó, runtime exchange code và lưu credential. Web console tại origin của bạn cũng thực hiện cùng các bước này qua một form, sử dụng cùng admin token. Provider dùng plain API key thì bỏ qua toàn bộ quy trình này: PUT /api/connections/<service> với {"authType":"api_key","values":{"apiKey":"..."}} sẽ lưu trực tiếp key.

Cấp cho mỗi agent một runtime token, không dùng credential

Agent xác thực với gateway bằng runtime token do admin API cấp.

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 token bắt đầu bằng oct_. Cấp một token cho từng agent và đặt tên theo agent đó. Nếu không xác định được token, bạn sẽ phải thu hồi toàn bộ 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ỳ vị trí nào 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 tool discovery như search_actionsexecute_action thay vì một tool cho từng 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.

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

Rút gọn danh sách hành động xuống đú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 quyền hạn rất rộng cho language model. Bề mặt này còn rộng hơn ngay khi model bắt đầu đọc nội dung do chính nó không tạo ra, vì một trang do SearXNG instance của bạn trả về khi agent tìm kiếm trên web có thể chứa chỉ dẫn nhắm đến bất kỳ hành động nào mà agent được phép thực hiện. Nguyên tắc hạn chế giúp coding agent thực hiện thay đổi nhỏ nhất nhưng vẫn hoạt động cũng áp dụng cho permission của agent: chỉ cấp một số ít hành động mà công việc thực sự cần, không cấp thêm. Có hai control giúp thu hẹp phạm vi này.

OOMOL_CONNECT_ALLOWED_ACTIONS nhận allowlist 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à khác biệt giữa một lỗi và một sự cố bảo mật. 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 global. Danh sách allowedProxies của chúng mặc định rỗng, vì vậy POST /v1/proxy/:service sẽ bị từ chối cho đến khi bạn cấp quyền. Proxy endpoint đó chuyển tiếp raw request đến một provider kèm credential của bạn, vì vậy hãy để trống 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 self-hosted provider trỏ vào địa chỉ private như cloud metadata service tại 169.254.169.254 hoặc database trên cùng network. Hãy để tắt. 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 credential đã được niêm phong. Encryption key trong .env dùng để mở chúng. Backup volume mà không có key thì không khôi phục được gì; key mà không có volume cũng không khôi phục được gì. Vì vậy, hãy lưu key trong password manager và đưa volume vào lịch backup thông thường.

Hãy dừng container trong khi sao chép file SQLite, vì bản sao được tạo trong lúc database đ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ủa bạn cộng với _connector-data. Đó là lý do lệnh đầu tiên có mặt ở đây: hãy dán tên thực tế vào lệnh thứ 3. Đưa archive ra khỏi VPS bằng backup restic từ VPS. restic sẽ mã hóa archive trước khi archive rời khỏi VPS, vì archive đó chính là 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 record. Nhờ đó, console có thể cho biết agent nào đã chạy action nào và chạy 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, hãy trỏ một status page của Uptime Kuma vào https://connect.example.com/health. Khi gateway ngừng phản hồi, agent sẽ fail 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.

Sự cố và thông báo bạn sẽ thấy

redirect_uri_mismatch tại nhà cung cấp. Origin và callback URL đã đăng ký không khớp. So sánh chuỗi chính xác từ /api/oauth/configs với cài đặt ứng dụng tại nhà cung cấp, bao gồm 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 của admin token bị thiếu hoặc bị viết sai. Header là Authorization: Bearer <token>, và web console cũng yêu cầu đúng token đó.

Container vẫn chạy, còn credential được lưu dưới dạng plain text. Điều này xảy ra khi OOMOL_CONNECT_ENCRYPTION_KEY không đến được container, vì runtime lưu các bản ghi credential 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 nhận biết được, 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, khóa 256-bit, chế độ Galois/counter).

Không có dữ liệu nào được giải mã sau khi restore. Encryption key đã bị 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à việc gửi ticket cho support cũng không giúp được. Hãy kết nối lại mọi provider. Việc rotation được hỗ trợ 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 gọi tên một action mà nó vẫn thấy trong catalog. Discovery và execution là hai bước riêng biệt. Một action có thể xuất hiện trong search_actions nhưng vẫn bị OOMOL_CONNECT_ALLOWED_ACTIONS, denylist hoặc chính các rule 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 tag cũ. Việc này chỉ hoạt động vì bạn đã pin tag đó.

FAQ

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

Với provider dùng API key thì không: 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ừ public internet, và provider 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.

Nếu mất encryption key của Open Connector thì chuyện gì xảy ra?

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 đượ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 là đặt key mới và kết nối lại mọi provider. Hãy 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_, còn gateway inject 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, endpoint chuyển tiếp raw request kèm credential của bạn và mặc định không có grant vì có lý do; và việc bạn tự dán API key vào agent, khiến request bỏ qua hoàn toàn gateway.

Gateway có nên truy cập được từ public internet 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, rồi đặt reverse proxy ở phía trước. Sau đó thử gọi một action mà không có header authorization. Nếu thành công, giới hạn /api, /v1/mcp tại proxy vào các địa chỉ mà agent của bạn sử dụng, cho đến khi chỉ các request đã xác thực mới hoạt động.

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

Open Connector đượ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 version number trong hướng dẫn này là snapshot tại 1 August 2026. Hãy chạy ứng dụng với release tag cố định, không bao giờ dùng 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ử ít nhất 1 lần. Thiết kế này phù hợp với một máy chủ do bạn sở hữu; rủi ro nằm ở việc version thay đổi nhanh, không nằm ở kiến trúc.