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.
Resources
1Install
npx skillscat add russ534/myopenclawbot/oci-helper Install via the SkillsCat registry.
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/loginwith{account, password}(NOTusername) →data.token(JWT, ~165 chars). The WebUI uses the standard page; no API needed. - Bearer header:
Authorization: Bearer <token>(NOTtoken:). Filter source:com.yohann.ocihelper.config.auth.AuthInterceptor.java. Sendingtoken: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/loginnot/api/v1/auth/login— generic LLM guesses of/api/v1/auth/loginare wrong; checkSysCfgControllerbefore guessing routes.
Adding OCI tenant via API (no WebUI)
POST /api/oci/addCfg — multipart/form-data:
username— config nameociCfgStr— JavaProperties.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. Multipartfilemust exist on the same host as curl — stage it on fnos first (scporssh fnos 'cat > /tmp/...'), thenssh fnos-root curl -F file=@/tmp/.... Hermes-side curl withfile=@/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 doesFloat.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 viaArchitectureEnum.operationSystem: just a freeform tag written to the instance. Not the image selector. Actual image comes fromCompute.listImages(operatingSystem="Canonical Ubuntu", operatingSystemVersion="22.04", shape="VM.Standard.A1.Flex")— first match isUbuntu 22.04 Minimal aarch64. SooperationSystem: "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, usePOST /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 tokenY102= 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.comdirectly (no proxy needed for OCI calls). - Telegram API NOT direct: container cannot reach
api.telegram.org— must use theproxyfield. LAN mihomo athttp://192.168.31.201:7890works (the container can hit the host's LAN IP). Verify withdocker exec oci-helper curl -x http://192.168.31.201:7890 -m 8 https://api.telegram.org— expect 302 from nginx-on-mihomo. proxyfield 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>&1The 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.