TSF – Giải pháp IT toàn diện cho doanh nghiệp SMB | HCM

P6 - Cấu hình OpenClaw 9Router Multi Account

💻
filename.bash
Bước 0: Kích hoạt Codex trong cài đặt Chatgpt cho các tài khoản

Setting => Security => Enable device code author...

Bước 1: Cài đặt Node/npm và 9router

node -v
npm -v
npm install -g 9router

Nếu node/npm/git chưa được cài đặt thì:
Cài đặt Node 22 LTS:

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -

sudo apt install nodejs -y

sudo apt install curl git -y

Bước 2: Chạy 9Router

Trên máy ảo, hãy thực hiện lệnh sau:

9router

Bảng điều khiển (đối với Windows, hãy mở trực tiếp từ liên kết này): http://localhost:20128/dashboard

Vì tôi đang sử dụng Ubuntu trên một máy khác, tôi sẽ SSH vào máy ảo Ubuntu AI này từ máy của mình.

Máy client: Mở CMD và SSH vào máy ảo AI.

ssh -N -L 20128:127.0.0.1:20128 bao@192.168.16.253 (IP VM AI)
Nhập pass SSH

Bước 3: Kết nối với nhà cung cấp trong bảng điều khiển

Truy cập:

• Dashboard → Providers → select Codex / ChatGPT (Codex) (or “Codex”)

• Click Connect → perform OAuth/device login as instructed


Nó sẽ hiển thị liên kết localhost, sao chép liên kết đó và dán vào terminal/SSH trên máy AI.

Thêm tài khoản thứ hai

Lặp lại thao tác “Kết nối” (hoặc “Thêm tài khoản”) để đăng nhập bằng tài khoản thứ hai.

Tương tự

bật chế độ luân phiên (round robin)

Tạo combo: tên openai

Bước 4: Tạo khóa API 9router

Đặt tên cho khóa; hệ thống sẽ tự động tạo khóa.
Key: sk-ccf64851645b8890-g9lfc4-4241aeb4

Kiểm tra key trên máy ảo
KEY='sk-ccf64851645b8890-g9lfc4-4241aeb4'
curl -sS -H "Authorization: Bearer $KEY" http://127.0.0.1:20128/v1/models | head -c 400; echo

Bước 5: Thay đổi nhà cung cấp OpenClaw openai-codex thành điểm cuối 9Router

Chạy lệnh hàng loạt

openclaw config set --batch-json "[
  {\"path\":\"models.mode\",\"value\":\"merge\"},
  {\"path\":\"models.providers.openai-codex\",\"value\":{
    \"baseUrl\":\"http://127.0.0.1:20128/v1\",
    \"api\":\"openai-completions\",
    \"headers\":{\"Authorization\":\"Bearer $KEY\"},
    \"models\":[]
  }}
]"
 
Restart service

systemctl --user restart openclaw-gateway 
 
Xác thực + restart gateway

openclaw config validate --json
valid: true

systemctl --user restart openclaw-gateway
 
Xác minh OpenClaw để xem mô hình thông qua 9Router:

openclaw models status –plain

Chỉnh sửa lại tệp cấu hình:
nano ~/.openclaw/openclaw.json

Thêm /cx vào tệp cấu hình.

Lưu tệp: Nhấn Ctrl O để vào Enter, Ctrl X để thoát.

Nếu bạn cần kiểm tra tệp mô hình được tạo lại:
cat ~/.openclaw/agents/main/agent/models.json | head -n 60

Việc tắt chế độ luân phiên (round robin) sẽ tự động chuyển sang tài khoản opanai1.

Bước 6: Tạo dịch vụ người dùng systemd.

#0: Bật CLI Tools Openclaw

#1 Tạo unit file
mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/9router.service

Dán nội dung sau (khuyến nghị chỉ liên kết cục bộ để đảm bảo an toàn):

[Unit]
Description=9Router (OpenAI-compatible router dashboard)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
Environment=PATH=%h/.npm-global/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
WorkingDirectory=%h
ExecStart=%h/.npm-global/bin/9router --host 127.0.0.1 --port 20128 --no-browser --log --skip-update
Restart=on-failure
RestartSec=2

[Install]
WantedBy=default.target
 
Save file

#2 Reload systemd user + enable + start
systemctl --user daemon-reload
systemctl --user enable --now 9router
 
#3 Làm cho openclaw-gateway chờ 9router
mkdir -p ~/.config/systemd/user/openclaw-gateway.service.d

Sao chép lệnh chính xác như sau:

cat > ~/.config/systemd/user/openclaw-gateway.service.d/override.conf <<'EOF'
[Unit]
Wants=9router.service
After=9router.service
EOF

Restart service

systemctl --user daemon-reload
systemctl --user restart 9router
systemctl --user restart openclaw-gateway
 
 
#4 Kiểm tra trạng thái + xem nhật ký thời gian thực
systemctl --user status 9router --no-pager -l
journalctl --user -u 9router -f
 

#5 Các lệnh quản lý nhanh
systemctl --user restart 9router
systemctl --user stop 9router
systemctl --user start 9router
 

5) Kiểm tra điểm cuối trực tiếp

curl -sS http://127.0.0.1:20128/v1/models | head -c 300; echo

6) (Quan trọng) Tự động chạy sau khi khởi động lại

Các dịch vụ người dùng đôi khi chỉ chạy khi người dùng có phiên làm việc. Nếu bạn muốn dịch vụ khởi động và chạy tự động ngay cả khi không có quyền truy cập SSH, bạn cần bật linger (yêu cầu sudo):

sudo loginctl enable-linger bao

Bước 7: Thiết lập một tập lệnh để giám sát hạn mức theo tài khoản người dùng

1. Tạo thư mục bin (nếu chưa tồn tại)

mkdir -p ~/bin

2. Tạo Python file
nano ~/bin/9router-account-usage.py

(Nội dung: phân tích tệp ~/.9router/log.txt, xóa dòng PENDING, thêm theo tài khoản, in bảng cột)

#!/usr/bin/env python3
"""Summarize 9Router usage per account from ~/.9router/log.txt.

Expected log format (from 9Router):
  DD-MM-YYYY HH:MM:SS | <model> | CODEX | <account> | <prompt_tokens or -> | <completion_tokens or -> | <status>

We count only completed (non-PENDING) rows.
"""

from __future__ import annotations

import argparse
import datetime as dt
import os
import re
import sys
from collections import defaultdict

LOG_PATH_DEFAULT = os.path.expanduser("~/.9router/log.txt")

LINE_RE = re.compile(
    r"^(?P<date>\d{2}-\d{2}-\d{4})\s+(?P<time>\d{2}:\d{2}:\d{2})\s+\|\s+"
    r"(?P<model>[^|]+?)\s+\|\s+"
    r"(?P<provider>[^|]+?)\s+\|\s+"
    r"(?P<account>[^|]+?)\s+\|\s+"
    r"(?P<prompt>[^|]+?)\s+\|\s+"
    r"(?P<completion>[^|]+?)\s+\|\s+"
    r"(?P<status>.+?)\s*$"
)


def parse_int(s: str) -> int:
    s = s.strip()
    if s == "-" or s == "":
        return 0
    try:
        return int(s)
    except ValueError:
        return 0


def parse_args() -> argparse.Namespace:
    ap = argparse.ArgumentParser(description="9Router per-account usage table")
    ap.add_argument("--log", default=LOG_PATH_DEFAULT, help=f"Log file path (default: {LOG_PATH_DEFAULT})")
    ap.add_argument("--date", default="today", help="Filter date: YYYY-MM-DD or 'today' (default: today)")
    ap.add_argument("--tz", default=None, help="Timezone name (best-effort). Default: local time on server")
    ap.add_argument("--top", type=int, default=50, help="Max accounts to show (default: 50)")
    return ap.parse_args()


def resolve_date(date_arg: str) -> dt.date:
    if date_arg == "today":
        return dt.date.today()
    return dt.date.fromisoformat(date_arg)


def main() -> int:
    args = parse_args()
    path = os.path.expanduser(args.log)
    if not os.path.exists(path):
        print(f"Log not found: {path}", file=sys.stderr)
        return 2

    target_date = resolve_date(args.date)

    # account -> metrics
    m = defaultdict(lambda: {
        "requests": 0,
        "ok": 0,
        "err": 0,
        "prompt_tokens": 0,
        "completion_tokens": 0,
        "models": defaultdict(int),
        "last": None,
    })

    with open(path, "r", encoding="utf-8", errors="replace") as f:
        for line in f:
            line = line.rstrip("\n")
            if not line.strip():
                continue
            mo = LINE_RE.match(line)
            if not mo:
                continue

            status = mo.group("status").strip()
            if status.upper() == "PENDING":
                continue

            # parse date in log: DD-MM-YYYY
            d_str = mo.group("date")
            t_str = mo.group("time")
            d = dt.datetime.strptime(d_str, "%d-%m-%Y").date()
            if d != target_date:
                continue

            account = mo.group("account").strip()
            model = mo.group("model").strip()
            prompt = parse_int(mo.group("prompt"))
            completion = parse_int(mo.group("completion"))

            rec = m[account]
            rec["requests"] += 1
            if "200" in status or status.upper().startswith("OK") or "OK" in status.upper():
                rec["ok"] += 1
            else:
                rec["err"] += 1
            rec["prompt_tokens"] += prompt
            rec["completion_tokens"] += completion
            rec["models"][model] += 1
            rec["last"] = f"{d_str} {t_str}"

    rows = []
    for acct, rec in m.items():
        total_tokens = rec["prompt_tokens"] + rec["completion_tokens"]
        top_models = ",".join([f"{k}:{v}" for k, v in sorted(rec["models"].items(), key=lambda kv: (-kv[1], kv[0]))[:3]])
        rows.append((
            acct,
            rec["requests"],
            rec["ok"],
            rec["err"],
            rec["prompt_tokens"],
            rec["completion_tokens"],
            total_tokens,
            top_models,
            rec["last"] or "-",
        ))

    rows.sort(key=lambda r: (-r[1], r[0]))
    rows = rows[: args.top]

    # print table
    header = [
        "ACCOUNT",
        "REQ",
        "OK",
        "ERR",
        "PROMPT_TOK",
        "COMP_TOK",
        "TOTAL_TOK",
        "TOP_MODELS",
        "LAST",
    ]

    # compute widths
    widths = [len(h) for h in header]
    for r in rows:
        for i, val in enumerate(r):
            widths[i] = max(widths[i], len(str(val)))

    def fmt_row(vals):
        return "  ".join(str(v).ljust(widths[i]) for i, v in enumerate(vals))

    print(fmt_row(header))
    print("  ".join("-" * w for w in widths))
    for r in rows:
        print(fmt_row(r))

    # totals
    tot_req = sum(r[1] for r in rows)
    tot_prompt = sum(r[4] for r in rows)
    tot_comp = sum(r[5] for r in rows)
    print()
    print(f"TOTAL requests={tot_req} prompt_tokens={tot_prompt} completion_tokens={tot_comp} total_tokens={tot_prompt+tot_comp}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
 
Save file: Ctrl O enter, Ctrl X exit

3. Phân quyền
chmod +x ~/bin/9router-account-usage.py 

4. Tạo file ~/bin/9router-account-usage để gọi hàm ngắn gọn hơn.

cat > ~/bin/9router-account-usage <<'SH'
#!/usr/bin/env bash
set -euo pipefail
exec "$HOME/bin/9router-account-usage.py" "$@"
SH
 
Phân quyền
chmod +x ~/bin/9router-account-usage 

5. Test

~/bin/9router-account-usage --date today | head -n 60 

~/bin/9router-account-usage --date today

 

Tham khảo thêm bài viết cùng chủ đề

P10 – Gỡ OpenClaw Windows Sạch Sẽ

P10 – Gỡ OpenClaw Windows Sạch Sẽ https://youtu.be/1ljEMzohiSY 🚀 AI Tutorial – P10: Gỡ OpenClaw trên Windows (Clean Removal & Fix Issues) Nếu bạn đang gặp lỗi với OpenClaw hoặc muốn cài lại từ đầu, việc gỡ openclaw đúng cách là rất quan trọng. Nếu chỉ xóa file đơn giản,...

Read More

P9 – Cài đặt Telegram Bot Local AI với Ollama nhanh

P9 – Cài đặt Telegram Bot Local AI với Ollama nhanh https://youtu.be/YuiLJDLIVr0 🚀 AI Tutorial – P9: Cài Đặt Telegram Bot Local AI với Ollama Việc cài đặt telegram bot local giúp bạn đưa AI vào thực tế một cách nhanh chóng và hiệu quả. Khi kết hợp Ollama với...

Read More

P8 – Cài đặt OpenClaw Local AI với Ollama & Qwen

P8 – Cài đặt OpenClaw Local AI với Ollama & Qwen 🚀 AI Tutorial – P8: Cài Đặt OpenClaw Local với Ollama, Qwen & Open WebUI Việc cài đặt openclaw local giúp bạn xây dựng một hệ thống AI chạy hoàn toàn nội bộ, không phụ thuộc vào API bên...

Read More