Russ534

oci-helper

Operate oci-helper (Oracle Cloud Y探长) on fnOS — API-based tenant config and 抢机 task management without WebUI clicks, plus a Hermes-side watchdog cron for first-instance TG alerts. Use when the user asks to add an OCI tenant to oci-helper, create or update a 抢机 task, check task state, or wire first-instance TG notification.

Russ534 0 Updated 5d ago

Resources

1
GitHub

Install

npx skillscat add russ534/myopenclawbot/oci-helper

Install via the SkillsCat registry.

SKILL.md

oci-helper on fnOS

This skill covers oci-helper-specific procedures and pitfalls only. Cross-cutting fnOS setup (SSH keys, sudo, docker daemon proxy, port binding, /usr/local/bin/docker-compose symlink, btrfs swapfile with chattr +C, docker compose recreate zombie "adoring_lewin" cleanup, container restart-policy not applied until docker compose up -d recreate) lives in the fnnas-admin skill — load it when SSH is broken or compose pull fails on ghcr.io. The three-image compose template (oci-helper + watcher + websockify) is in the fnnas-admin skill's references/oci-helper-deploy.md.

Auth model

  • Login: POST /api/sys/login with {account, password} (NOT username) → data.token (JWT, ~165 chars). The WebUI uses the standard page; no API needed.
  • Bearer header: Authorization: Bearer <token> (NOT token:). Filter source: com.yohann.ocihelper.config.auth.AuthInterceptor.java. Sending token: returns 401.
  • Public-no-token endpoints: /api/sys/login, /api/sys/getEnableMfa, /api/sys/googleLogin, /api/sys/getGoogleClientId. Everything else needs Bearer.
  • Login URL is /api/sys/login not /api/v1/auth/login — generic LLM guesses of /api/v1/auth/login are wrong; check SysCfgController before guessing routes.

Adding OCI tenant via API (no WebUI)

POST /api/oci/addCfg — multipart/form-data:

  • username — config name
  • ociCfgStr — Java Properties.load() key=value lines, field names are lowercase with underscores: user, tenancy, region, fingerprint, key_file. Wrong names (e.g. userId, tenantId) → nulls → 401.
  • file — PEM file. Multipart file must exist on the same host as curl — stage it on fnos first (scp or ssh fnos 'cat > /tmp/...'), then ssh fnos-root curl -F file=@/tmp/.... Hermes-side curl with file=@/vol1/... fails because /vol1 is the fnos host, not hermes.

Hero API endpoints

Goal Endpoint Notes
List tenants POST /api/oci/userPage body {currentPage, pageSize} field names are currentPage/pageSize, NOT page/size. Wrong field name → 200 with records: [].
List 抢机 tasks POST /api/oci/createTaskPage body {currentPage, pageSize, keyword} records[i].counts is a retry counter, NOT actual successful instance count. Real count = len(instanceList) from /api/oci/details. Alerting off counts produces false positives after every failed-retry loop.
Tenant instance count POST /api/oci/details body {cfgId, cleanReLaunchDetails} response data.instanceList[] is the only true source of "instances that exist"
Create 抢机 task POST /api/oci/createInstanceBatch body {userIds, instanceInfo{...}} one-shot: schedules N attempts per AD, retries on capacity
Update task POST /api/oci/updateCreateTask body {taskId, ...same as above} restarts the task with new params
Send TG test POST /api/sys/sendMsg body {message} uses tgBotToken from kv; ok to test from Hermes

instanceInfo field quirks

  • ocpus/memory: String (service does Float.parseFloat).
  • disk: Integer (GB). ARM A1 Flex currently requires ≥ 50 (2026-08 OCI raised it from 47). Setting 47 → 400 InvalidParameter: Boot volume should be ≥ 50GB.
  • architecture: "ARM" → VM.Standard.A1.Flex (Always Free). "AMD" → VM.Standard.E2.1.Micro. The service maps via ArchitectureEnum.
  • operationSystem: just a freeform tag written to the instance. Not the image selector. Actual image comes from Compute.listImages(operatingSystem="Canonical Ubuntu", operatingSystemVersion="22.04", shape="VM.Standard.A1.Flex") — first match is Ubuntu 22.04 Minimal aarch64. So operationSystem: "Ubuntu" is the only useful value; the OCI image is hardcoded by the service.
  • rootPassword: freeform tag only. Not injected into OS. To actually set OS password after launch, use POST /api/oci/updateInstanceRootPassword.
  • createNumbers: how many to attempt. Service rotates across ADs per attempt.

updateSysCfg requires all Boolean fields non-null

{
  "tgBotToken": "...",
  "tgChatId": "-1002723293488",
  "proxy": "http://192.168.31.201:7890",
  "enableMfa": false,
  "enableDailyBroadcast": false,
  "dailyBroadcastCron": "0 9 * * *",
  "enableVersionInform": false,
  "enableGoogleLogin": false
}

Missing any Boolean → 500 with stack Cannot invoke "java.lang.Boolean.toString()" because the return value of "...getEnableDailyBroadcast()" is null. Always send all four booleans + dailyBroadcastCron.

TG bot token storage

oci-helper stores token in oci_kv (not oci_user). Codes (from SysCfgEnum):

  • Y101 = TG bot token
  • Y102 = TG chat_id (with surrounding " quotes — strip them before use)
  • Y114 = global proxy URL

The Hermes stdout masks sensitive strings in JSON values — len(json_value) shows 13 even when the string is 165 chars. Write to a file on fnos first, read back with wc -c to get the real length. Verified 2026-10-19: TG token printed as 8108318598:*** in stdout but wc -c /tmp/oci-add/tok.txt = 47 bytes (the real value).

OCI ARM 配额(2026-08-18 已砍,开机策略核心)

Always Free ARM VM.Standard.A1.Flex 总配额 = 2 OCPU + 12 GB 内存(每租户),不是老的 4 OCPU / 24 GB。老配置(4+24)会被 OCI 自动终止。

配置 单台 单租户总 说明
1 OCPU + 6 GB 2 台 2 OCPU + 12 GB ✅ 用满配额,推荐
1 OCPU + 12 GB 1 台 1 OCPU + 12 GB 单机 RAM 大但只用一半 OCPU
2 OCPU + 12 GB 1 台 2 OCPU + 12 GB ✅ 用满配额
4 OCPU + 24 GB ❌ 任何组合 超过配额 OCI 拒绝 / 自动终止

PAYG(Pay As You Go,开通过信用卡的)仍 4 OCPU / 24 GB 不变。Always Free(planType=FREE_TIER)被砍。oci-helper 的 userPage 返回 planType 字段可查。

boot volume 联动:OCI ARM A1 Flex 最小 50 GB(不是老的 47 GB)。磁盘是单独约束,跟 OCPU/RAM 配额无关——disk: 47 永远报 400 InvalidParameter,必须 disk: 50。

AMD 实例(VM.Standard.E2.1.Micro)没被砍:仍 2 台/租户,1/8 OCPU + 1 GB。AMD E2 实例可一键开 500 Mbps 下行免费提速(oci-helper 菜单有),ARM 不能提速。

抢机常态:免费 ARM 平时容量 100% 满。日志会一直看到 容量不足,换可用区域继续执行... 三个 AD 轮询失败。这是预期行为,断点续抢保持后台跑直到 OCI 释放名额(通常北美凌晨 / 欧洲白天时段)。抢到的那一瞬 oci-helper 自动推 TG(已配 tgBotToken + tgChatId)。

Container network defaults

  • OCI API direct: container can reach iaas.<region>.oraclecloud.com directly (no proxy needed for OCI calls).
  • Telegram API NOT direct: container cannot reach api.telegram.org — must use the proxy field. LAN mihomo at http://192.168.31.201:7890 works (the container can hit the host's LAN IP). Verify with docker exec oci-helper curl -x http://192.168.31.201:7890 -m 8 https://api.telegram.org — expect 302 from nginx-on-mihomo.
  • proxy field is global: when set, both OCI SDK and TG bot use it. OCI latency goes up ~1-2s but functional. Mihomo upstream is stable enough not to matter.

Hermes-side watchdog — TG alert on first instance

oci-helper auto-pushes TG on successful launch. If the user wants a Hermes-side independent watcher (oci-helper process could die, or the user wants belt-and-suspenders confirmation), use this cron pattern:

# /etc/cron.d/oci-watch
*/5 * * * * root bash /vol1/1000/Docker/oci-helper/monitor/oci-watch.sh >/dev/null 2>&1

The script: login to oci-helper API → for each tenant, call /api/oci/details → count len(instanceList) → if increased since last run, send TG via direct Telegram Bot API (using --proxy http://192.168.31.201:7890). Fallback: oci-helper's own /api/sys/sendMsg. State in /vol1/1000/Docker/oci-helper/monitor/state. See scripts/oci-watch.sh for the working copy.

🚨 Never alert off records[i].counts from /api/oci/createTaskPage — it's a launch-attempt counter, not a success count. The service increments counts on every retry, so a task failing capacity for 18 minutes will report counts=18 with zero real instances created. Alerting off it produces "🟢 新开机成功 +18" messages that are pure false positives. Source the alert from len(data.instanceList) on /api/oci/details instead — that endpoint queries OCI directly and reflects actual launched instances. The first version of this cron watched counts and sent a "+18 抢到机" alert after 18 minutes of pure failed retries; same instance count was 0 the whole time.

Categories