Tự host Open Connector cho AI agent trên VPS
Chạy Open Connector trên VPS riêng để agent không giữ SaaS token: pin image, cấu hình TLS origin, OAuth callback và backup file SQLite duy nhất.
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 mã nguồn mở do OOMOL Lab phát hành theo giấy phép Apache 2.0. Gateway chạy trong một container, lưu state trong một file SQLite duy nhất và 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ự nối 5 provider vào một agent 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 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 này. Token cũng xuất hiện trong transcript. Đây là lỗi mà bài giữ secret ngoài AI agent mô tả.
Auth gateway tách credential thành 2 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ý tài khoản GitHub.
Catalog công bố hơn 1,000 provider và 10,000 action dựng sẵn. Đây là con số do dự án tự công bố, không phải thông tin bạn có thể xác minh từ bên ngoài. Điều có thể xác minh 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 một việc và lưu refresh token của mọi provider mà bạn kết nối. Refresh token của Google hoặc GitHub là credential dài hạn để truy cập email và repository của bạn, và thường vẫn còn hiệu lực sau khi bạn đổi password. Nếu dịch vụ đó bị breach, hệ thống của bạn cũng bị ảnh hưởng. Tự host chuyển các record đó 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 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, nên cần được bảo vệ như máy host password manager: firewall chỉ mở 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 đó.
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 mới 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 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 trỏ động thay đổi thường xuyên. Một docker compose pull nhảy qua 2 release có thể thay đổi endpoint mà agent của bạn phụ thuộc vào. Bạn có thể phải mất cả buổi tối để debug, trong khi nguyên nhân lại bị xem là lỗi của agent. Hãy pin image vào tag của một 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ó Compose plugin, 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 đã thực hiện TLS termination (transport layer security) cho hostname đó
- hai secret ngẫu nhiên, được tạo ở phần dưới
Tài liệu 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 duy nhất có trong tài liệu 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 đã 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 .envHãy lưu cả hai giá trị vào password manager ngay bây giờ, trước lần khởi động đầu tiên. Encryption key không có cách khôi phục, và lý do được nêu trong danh sách lỗi bên dưới.
Bây giờ compose.yaml. Nội dung này khác ví dụ upstream ở 2 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à 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 port đã publish vào bảng NAT (network address translation) trước khi packet đi qua filter chain của ufw, vì vậy ufw deny 3000 không đóng cổng đó. Đây là vấn đề được mô tả trong vì sao port Docker vượt qua 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, nên 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 kiểm tra { "ok": true } sau khi runtime đã hoạt động. ss phải in ra 127.0.0.1:3000. Dòng có nội dung 0.0.0.0:3000 cho biết 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 trả về connection refused, container chưa listening, vì vậy 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 gắn service này vào network của Traefik và xóa block ports:, vì Traefik truy cập container qua internal network và hoàn toàn không cần publish cổng ra 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 mà không có certificate.
Vì sao OAuth bắt buộc bạn phải có hostname thật
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 dễ bị hiểu nhầm là lỗi của provider. 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 trở lại URI đó. Vì vậy, URI phải là địa chỉ mà Internet có thể truy cập, và các provider từ chối http:// thông thường trong mọi trường hợp 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 lúc startup. 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, hãy tạo OAuth app tại provider. Trên GitHub, mở Settings, sau đó chọn Developer settings, OAuth Apps rồi New OAuth App. Đặt authorization callback URL là https://connect.example.com/oauth/callback. Lưu lại 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 yêu cầu cho từng provider. Đây là cách nhanh nhất để kiểm tra origin đã có hiệu lực 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, 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ở URL này trong browser và approve các scope. Provider sẽ chuyển browser về /oauth/callback, tại đó runtime exchange code và lưu credential. Web console tại origin của bạn cũng hướng dẫn các bước tương tự bằng một form, với cùng admin token. Provider dùng plain API key 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 cấp credential
Agent xác thực với gateway bằng runtime token do admin API phát hành.
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"}'Phản hồi chứa một token bắt đầu bằng oct_. Cấp một token cho mỗi agent và đặt tên theo agent đó. Nếu không xác định được token thuộc agent nào, việc thu hồi một token sẽ buộc bạn 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":{}}'Phản hồi 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 phản hồi đó. 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_actions và execute_action thay vì tạo 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 của kết nối này.
Hãy thực hiện thêm một kiểm tra trước khi xem cấu hình là hoàn tất. Gọi lại action sau khi xóa header authorization. Quickstart của dự án tự gọi /v1 mà không có bearer, vì vậy một 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ời 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ời gọi ẩn danh hiện đã thất bại, hoặc giới hạn /api, /v1 và /mcp tại reverse proxy cho các địa chỉ mà agent của bạn sử dụng. Chỉ /oauth/callback cần mở cho Internet, vì đây là đường dẫn duy nhất mà browser redirect của provider cần dùng.
Thu gọn danh sách action chỉ còn những gì agent cần
Một gateway có hàng nghìn provider phía sau là một bề mặt tấn công rộng khi giao 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 nó không tự tạo, vì một trang do SearXNG do bạn tự host trả về khi agent tìm kiếm trên web có thể chứa instructions nhắm đến bất kỳ action nào mà agent được cấp quyề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 permissions của agent: chỉ cấp một số ít action mà công việc thực sự cần, không cấp thêm gì khác. Có 2 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 action 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ố bảo mật. Runtime token có các rule về action riêng, áp dụng bổ sung trên các rule toàn cục. Danh sách allowedProxies của chúng ban đầu trống, vì vậy POST /v1/proxy/:service bị từ chối cho đến khi bạn cấp quyền. Proxy endpoint đó chuyển tiếp request thô đến provider kèm credential của bạn, nên hãy để trống trừ khi một agent cụ thể cần dùng nó.
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 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ứ còn lại. 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 để giải niêm phong chúng. Sao lưu volume mà không có key thì không khôi phục được gì. Có 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 quy trình sao lưu định kỳ 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 đang ghi có thể khôi phục thành một 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 connectorTên volume là thư mục project của bạn cộng với _connector-data. Vì vậy mới có lệnh đầu tiên: hãy dán tên thực tế vào lệnh thứ 3. Gửi archive ra khỏi VPS bằng các bản sao lưu restic từ VPS. restic mã hóa archive trước khi archive rời khỏi VPS, vì archive đó chứa kho thông tin xác thực.
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, để console có thể cho biết agent nào đã chạy việc gì và vào lúc 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ỏ trang trạng thái Uptime Kuma đến 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 sẽ giúp bạn không phải mất cả giờ đọc output của agent.
Sự cố xảy ra 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 của 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 viết sai. Header là Authorization: Bearer <token>, và web console yêu cầu cùng token đó.
Container vẫn chạy, còn thông tin xác thực nằm ở 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 biết, 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.sqliteGiá 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 trong cùng thư mục với compose.yaml và docker compose config hiển thị giá trị đó. Khi đã đặt key, cùng lệnh tìm kiếm sẽ trả về 0, vì bản ghi được bảo vệ 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 khôi phục. 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à việc gửi support ticket cũng không giúp đượ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 lệnh dữ liệu trong runtime, vì vậy hãy đọc release notes hiện tại trước khi rotation.
Agent báo lỗi cho 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 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à đặt lại 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 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 browser đến callback URL của bạn, nên URL đó phải truy cập được từ public internet, và provider từ chối http:// thuần 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 làm 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 đượ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. Lưu key trong password manager và đưa database vào backup rotation, vì khi restore cần có cả hai.
AI agent của tôi có thể thấy access token của provider không?
Không, nếu 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 thuộc tính này: endpoint /v1/proxy/:service, vốn chuyển tiếp raw request kèm credential của bạn và có grants ban đầu rỗng là có chủ ý; 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ừ 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 đó test một action call không có header authorization. Nếu thành công, giới hạn /api, /v1 và /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 authenticated call hoạt động.
Open Connector đã sẵn sàng dùng trong production chưa?
Open Connector dùng giấy phép 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 của 1 August 2026. Chạy nó với release tag cố định, 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ử ít nhất một 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 ở architecture.