@shadowob/cloud 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -11
- package/dist/{agent-browser-CERTMCDL.js → agent-browser-EI7FIK3X.js} +3 -3
- package/dist/{agent-browser-CIRZRIY4.js → agent-browser-YXE4ES6Q.js} +3 -3
- package/dist/{agent-pack-LF3O5TR4.js → agent-pack-35TFCZKP.js} +1 -1
- package/dist/{agent-pack-RQT27V7R.js → agent-pack-UOG6ZAUL.js} +1 -1
- package/dist/agentmemory-KP5O7GHB.js +101 -0
- package/dist/agentmemory-UV74POU5.js +100 -0
- package/dist/{airtable-BG2Q75G2.js → airtable-CXXS3YUN.js} +3 -3
- package/dist/{airtable-JCQXFM5D.js → airtable-YZ5JR5JC.js} +3 -3
- package/dist/{alipay-TZQI34RB.js → alipay-WED5P3XC.js} +3 -3
- package/dist/{alipay-MZX2XCDB.js → alipay-WJTVREMG.js} +3 -3
- package/dist/{amap-KPCLZYYL.js → amap-AIN23TQ7.js} +3 -3
- package/dist/{amap-5RQB3VGC.js → amap-F6GF7QKB.js} +3 -3
- package/dist/{atlassian-LGOEWYC7.js → atlassian-G6PM6UVM.js} +3 -3
- package/dist/{atlassian-TVS2A4IU.js → atlassian-IDR2NPJC.js} +3 -3
- package/dist/{baidu-appbuilder-QRRL3ETM.js → baidu-appbuilder-JKQIA5TG.js} +3 -3
- package/dist/{baidu-appbuilder-6UMESXHW.js → baidu-appbuilder-KVMCIFYH.js} +3 -3
- package/dist/{baidu-maps-HEPMVP5D.js → baidu-maps-GWMXV6YT.js} +3 -3
- package/dist/{baidu-maps-HXC4FBVP.js → baidu-maps-ZXGT6QZM.js} +3 -3
- package/dist/{baidu-netdisk-G5Q6B5NH.js → baidu-netdisk-J5SLQVWW.js} +3 -3
- package/dist/{baidu-netdisk-RS2K5W2M.js → baidu-netdisk-PONM3DY6.js} +3 -3
- package/dist/{baidu-smartprogram-JHD3XWF6.js → baidu-smartprogram-WJ5JMO6V.js} +3 -3
- package/dist/{baidu-smartprogram-EWTK5WKK.js → baidu-smartprogram-Y5WBR7RX.js} +3 -3
- package/dist/{browserbase-IUIYVYI7.js → browserbase-CGVQHRC4.js} +3 -3
- package/dist/{browserbase-JFO2PCIA.js → browserbase-PVWYTEZC.js} +3 -3
- package/dist/{canva-3YOFL7JS.js → canva-COCBQAT2.js} +3 -3
- package/dist/{canva-FMYN65SM.js → canva-RSTJSEX5.js} +3 -3
- package/dist/{chunk-35LJYCQF.js → chunk-3CT6RQNM.js} +745 -482
- package/dist/{chunk-KODMGZUC.js → chunk-4YO3NA26.js} +1 -1
- package/dist/{chunk-RECNVWMT.js → chunk-6V7MW4HU.js} +17 -3
- package/dist/{chunk-C6OI4ZNO.js → chunk-EVV774KS.js} +1 -1
- package/dist/{chunk-SVMXSIMG.js → chunk-F6CQ6GAG.js} +2 -1
- package/dist/{chunk-JUPAE5IA.js → chunk-OL5VH6RN.js} +72 -69
- package/dist/{chunk-POSVEKIY.js → chunk-OYY64ZSX.js} +17 -3
- package/dist/{chunk-ZHVYNIHA.js → chunk-P5Y6F2NH.js} +745 -482
- package/dist/{chunk-EEFMJYKB.js → chunk-PSK2SYZ3.js} +2 -1
- package/dist/{chunk-6YAYCWGK.js → chunk-PYJRFKPN.js} +1 -1
- package/dist/{chunk-JY2HTT7Q.js → chunk-RMDY3W4V.js} +6 -0
- package/dist/{chunk-EWB7L7IW.js → chunk-X2SREECR.js} +6 -6
- package/dist/{chunk-LXJBQBGL.js → chunk-X5VOIA72.js} +6 -6
- package/dist/{chunk-CTNUKOQE.js → chunk-Y5BJ3EW2.js} +6 -0
- package/dist/{chunk-SAP2DBHO.js → chunk-Y6BKVDG7.js} +1 -1
- package/dist/{chunk-6P2K6QZR.js → chunk-ZGMWSSCC.js} +72 -69
- package/dist/{claude-plugin-577TAQVS.js → claude-plugin-FPN32WMT.js} +1 -1
- package/dist/{claude-plugin-L3MXJJ6J.js → claude-plugin-IYHOVTVL.js} +1 -1
- package/dist/cli.js +930 -149
- package/dist/{cloudflare-RDFPKMM5.js → cloudflare-U3RHJJKK.js} +3 -3
- package/dist/{cloudflare-HBBABPK6.js → cloudflare-ZHN7UGPX.js} +3 -3
- package/dist/{cnb-FLP3QX46.js → cnb-AMXC5I7D.js} +3 -3
- package/dist/{cnb-YAVVEYFB.js → cnb-P3IZ4JTD.js} +3 -3
- package/dist/console/index.html +1 -1
- package/dist/console/static/css/index.f4563d95.css +1 -0
- package/dist/console/static/js/index.020abc71.js +1 -0
- package/dist/{coze-E6VGRNLV.js → coze-66RYMKVB.js} +3 -3
- package/dist/{coze-C6PMDPBI.js → coze-YE3BINXP.js} +3 -3
- package/dist/{dashboard.command-ZMQFKLNQ.js → dashboard.command-BRPZCZER.js} +1 -1
- package/dist/{dashboard.command-2AM45SIT.js → dashboard.command-GUHSJ2CN.js} +1 -1
- package/dist/{dingtalk-JNRNRN7E.js → dingtalk-4RFQG7N2.js} +3 -3
- package/dist/{dingtalk-WZGGIAHJ.js → dingtalk-VNFKXD2P.js} +3 -3
- package/dist/{douyin-miniprogram-AIJPPIZH.js → douyin-miniprogram-UEALAGOS.js} +3 -3
- package/dist/{douyin-miniprogram-HCYZ5NBW.js → douyin-miniprogram-UNB6UO2I.js} +3 -3
- package/dist/{figma-2YYNSCDX.js → figma-A264OWU5.js} +3 -3
- package/dist/{figma-RYOBMENP.js → figma-Y4TGSDZP.js} +3 -3
- package/dist/{firebase-OYSY6HPT.js → firebase-AI3MAGYG.js} +3 -3
- package/dist/{firebase-2IJDDBXX.js → firebase-ZGQARUIH.js} +3 -3
- package/dist/{firecrawl-2T3SBUW7.js → firecrawl-2JW7DMTH.js} +3 -3
- package/dist/{firecrawl-IYYXLAZM.js → firecrawl-UURQ5P5N.js} +3 -3
- package/dist/{flyai-QS5Q6FJR.js → flyai-EJGDMYFA.js} +3 -3
- package/dist/{flyai-7FJ4TRAG.js → flyai-ZFMZBBHJ.js} +3 -3
- package/dist/{gitagent-MWI75OIX.js → gitagent-5SDBYFNA.js} +1 -1
- package/dist/{gitagent-YBMWY7NZ.js → gitagent-ODXPCR4X.js} +1 -1
- package/dist/{gitee-3N7OFOM7.js → gitee-5UMJ4BC7.js} +3 -3
- package/dist/{gitee-KVNK6KLZ.js → gitee-D6NAZTCO.js} +3 -3
- package/dist/{github-LUEC2LID.js → github-JBLDKIA3.js} +3 -3
- package/dist/{github-XRO5Z3GC.js → github-PZQAVEZP.js} +3 -3
- package/dist/{google-ads-VPKWTX67.js → google-ads-BIFQOJ5M.js} +3 -3
- package/dist/{google-ads-A3QAJI4D.js → google-ads-QU3LJE4O.js} +3 -3
- package/dist/{google-analytics-C4UR5ZR2.js → google-analytics-7VZ6YZVA.js} +3 -3
- package/dist/{google-analytics-XDYZA2B7.js → google-analytics-HXMPCL5V.js} +3 -3
- package/dist/{google-workspace-YX35SHHX.js → google-workspace-6SEBJ4VA.js} +2 -2
- package/dist/{google-workspace-LL3EWVHH.js → google-workspace-L3AMJLCF.js} +2 -2
- package/dist/{huawei-xiaoyi-KPWLTSHB.js → huawei-xiaoyi-C6QIJMPM.js} +3 -3
- package/dist/{huawei-xiaoyi-6BSMGJHR.js → huawei-xiaoyi-JGLXWU5P.js} +3 -3
- package/dist/{hubspot-FTIEMNZO.js → hubspot-LACJGE6D.js} +3 -3
- package/dist/{hubspot-DIUHGEDI.js → hubspot-XWPRO4KZ.js} +3 -3
- package/dist/{huggingface-UUXK2RHK.js → huggingface-26QQZK4C.js} +3 -3
- package/dist/{huggingface-MJCOXA7E.js → huggingface-CQICNA2R.js} +3 -3
- package/dist/index.d.ts +1338 -1
- package/dist/index.js +1364 -226
- package/dist/{inference-ai-image-generation-PXV6IG4U.js → inference-ai-image-generation-5KYIUWT6.js} +3 -3
- package/dist/{inference-ai-image-generation-CMI6R5T3.js → inference-ai-image-generation-J2NYDCLZ.js} +3 -3
- package/dist/{inference-sh-7AZOLEFI.js → inference-sh-5SWQTK73.js} +3 -3
- package/dist/{inference-sh-ABQOD3YF.js → inference-sh-PTQF6T3R.js} +3 -3
- package/dist/{init.command-YVG4X6II.js → init.command-C7UKPK2Y.js} +3 -3
- package/dist/{init.command-JKE3SXAS.js → init.command-UNL66BMR.js} +3 -3
- package/dist/{klaviyo-LDPBWBSS.js → klaviyo-4UNPMBFT.js} +3 -3
- package/dist/{klaviyo-6K5YEFNH.js → klaviyo-SLYNEULT.js} +3 -3
- package/dist/{kuaidi100-HGFM5VK2.js → kuaidi100-HZKV5AIS.js} +3 -3
- package/dist/{kuaidi100-UHPFCVXP.js → kuaidi100-ZQUW7GHH.js} +3 -3
- package/dist/lark-HQUZNHDI.js +382 -0
- package/dist/lark-PAV7XWJS.js +381 -0
- package/dist/{linear-T4ORUP7N.js → linear-PYGQ5SLK.js} +3 -3
- package/dist/{linear-7QFSFPOD.js → linear-VJLYNTUF.js} +3 -3
- package/dist/{lovart-PDUXRUHJ.js → lovart-KC6SVNAJ.js} +3 -3
- package/dist/{lovart-QO3SK55T.js → lovart-WVKY4RR4.js} +3 -3
- package/dist/{meta-ads-SCNFI45S.js → meta-ads-E6XT33GI.js} +3 -3
- package/dist/{meta-ads-V6XPZWX3.js → meta-ads-RJ6DWRYN.js} +3 -3
- package/dist/{miclaw-TPPPS2WN.js → miclaw-4BA3A2YN.js} +3 -3
- package/dist/{miclaw-5CNTW7VV.js → miclaw-LUV6DCHX.js} +3 -3
- package/dist/{model-provider-KFB76XV5.js → model-provider-SYXJZ3JD.js} +1 -1
- package/dist/{model-provider-AVSFJSZP.js → model-provider-U7NEYA3Y.js} +1 -1
- package/dist/nature-skills-G76ABIWZ.js +143 -0
- package/dist/nature-skills-SQHMFXKT.js +142 -0
- package/dist/{notion-WFA7KGZZ.js → notion-2JZAKOFP.js} +1 -1
- package/dist/{notion-FZK76MN2.js → notion-O3NO5TJH.js} +1 -1
- package/dist/{oceanengine-3JZUS3PP.js → oceanengine-D23UZGVB.js} +3 -3
- package/dist/{oceanengine-5BRIJVJE.js → oceanengine-WDK2OXX5.js} +3 -3
- package/dist/{opencli-PFXHGCS2.js → opencli-36P63YNU.js} +3 -3
- package/dist/{opencli-VIGRJTGH.js → opencli-SUHDFR33.js} +3 -3
- package/dist/{paypal-Z5JYHIWD.js → paypal-K27SUW3B.js} +3 -3
- package/dist/{paypal-33UADIPR.js → paypal-OPZ3KOV5.js} +3 -3
- package/dist/{playwright-SQAQ3DZG.js → playwright-2ULT3NIC.js} +3 -3
- package/dist/{playwright-MG5WHK47.js → playwright-3Q7LBILG.js} +3 -3
- package/dist/{plugins-HZBWK3WQ.js → plugins-2MITZ4ZD.js} +2 -2
- package/dist/{plugins-I4GD5SZX.js → plugins-UK2QWD6G.js} +2 -2
- package/dist/{posthog-MU5MAJOQ.js → posthog-E3EHXLAN.js} +3 -3
- package/dist/{posthog-RJRRKDWB.js → posthog-KPJVLGX6.js} +3 -3
- package/dist/{salesforce-34FVIJTG.js → salesforce-FGPNG7FB.js} +3 -3
- package/dist/{salesforce-3QZ6OFVO.js → salesforce-TVHISKBC.js} +3 -3
- package/dist/{sentry-PIWW46VA.js → sentry-BZ3J3MZM.js} +3 -3
- package/dist/{sentry-MCIRMACU.js → sentry-XC57YRAJ.js} +3 -3
- package/dist/{seo-suite-WJXMA3S4.js → seo-suite-2MDEDLAB.js} +3 -3
- package/dist/{seo-suite-4SQ3I67Q.js → seo-suite-U75O3QP6.js} +3 -3
- package/dist/{serve.command-XLBJUOV6.js → serve.command-G5RVQFUD.js} +3 -3
- package/dist/{serve.command-RD6I6MFD.js → serve.command-PYGDG7K3.js} +3 -3
- package/dist/{shadowob-PRSMI5MW.js → shadowob-3QZ7DLDW.js} +158 -31
- package/dist/{shadowob-JELOWHWX.js → shadowob-CJLOEKFP.js} +158 -31
- package/dist/{sherlock-2PKY2E2Y.js → sherlock-CQFUHKDH.js} +3 -3
- package/dist/{sherlock-C5ZWPPVT.js → sherlock-DONK2I6E.js} +3 -3
- package/dist/{shopify-GL3NFVGE.js → shopify-NO5GI3WD.js} +3 -3
- package/dist/{shopify-R4G3UXM6.js → shopify-VW2KLKH5.js} +3 -3
- package/dist/{skill-discovery-YPXXV622.js → skill-discovery-6JEPPKKM.js} +3 -3
- package/dist/{skill-discovery-7INAUP4D.js → skill-discovery-PWRAVGIS.js} +3 -3
- package/dist/skills/shadowob-cli/SKILL.md +7 -0
- package/dist/{stripe-LJNPQ3CQ.js → stripe-HCNCKG4C.js} +1 -1
- package/dist/{stripe-C22RR4ZS.js → stripe-IU3KTJ4H.js} +1 -1
- package/dist/{supabase-IRNQ54FJ.js → supabase-KRL7JW2D.js} +3 -3
- package/dist/{supabase-N4ONFJNQ.js → supabase-TYEBTZNO.js} +3 -3
- package/dist/{taobao-aipaas-LRR4GMO3.js → taobao-aipaas-PEUIDOYP.js} +3 -3
- package/dist/{taobao-aipaas-RVKORSF4.js → taobao-aipaas-SA5E4MZA.js} +3 -3
- package/dist/{tapd-TMQRSMFG.js → tapd-6DDIUPVQ.js} +3 -3
- package/dist/{tapd-3JPVJ7XH.js → tapd-OTYLSZGY.js} +3 -3
- package/dist/{tencent-ads-UHC6OPBV.js → tencent-ads-OW2TAMH5.js} +3 -3
- package/dist/{tencent-ads-IGD33LO7.js → tencent-ads-XTQZ27YT.js} +3 -3
- package/dist/{tencent-docs-C3A4J3CJ.js → tencent-docs-6D6A2VCO.js} +3 -3
- package/dist/{tencent-docs-O2SC4FHL.js → tencent-docs-K3TMUIWD.js} +3 -3
- package/dist/{tencent-maps-OQOKHVW2.js → tencent-maps-IZYWITJZ.js} +3 -3
- package/dist/{tencent-maps-HMMWMNF4.js → tencent-maps-SWI7CLQY.js} +3 -3
- package/dist/text-to-cad-B2UP6PKA.js +192 -0
- package/dist/text-to-cad-I4B6VBFV.js +193 -0
- package/dist/{vercel-KOXDDTHX.js → vercel-CCKRC76D.js} +3 -3
- package/dist/{vercel-OLNVDWMG.js → vercel-SBGEMIJJ.js} +3 -3
- package/dist/{webflow-OMJKZM54.js → webflow-C3EHNNSN.js} +3 -3
- package/dist/{webflow-FULU5Q2I.js → webflow-ZFBJH4CR.js} +3 -3
- package/dist/{wechat-miniprogram-skyline-KYCDMQNW.js → wechat-miniprogram-skyline-VNCRERHX.js} +3 -3
- package/dist/{wechat-miniprogram-skyline-VR4FVIQL.js → wechat-miniprogram-skyline-Z5JQUV5Q.js} +3 -3
- package/dist/{wechat-pay-BCMAJ6UW.js → wechat-pay-RPDKPUEB.js} +3 -3
- package/dist/{wechat-pay-YQQKXVUI.js → wechat-pay-XVDGJRF2.js} +3 -3
- package/dist/{wonda-NGWIORYN.js → wonda-EL2P44S7.js} +3 -3
- package/dist/{wonda-RBABXFNM.js → wonda-XK5JK4X3.js} +3 -3
- package/dist/{wordpress-woocommerce-RNA5HB3N.js → wordpress-woocommerce-4MEE5A2M.js} +3 -3
- package/dist/{wordpress-woocommerce-RDIUTHYT.js → wordpress-woocommerce-6ECNM2QU.js} +3 -3
- package/dist/{wps-LUWHMZQQ.js → wps-E4OZEMOF.js} +3 -3
- package/dist/{wps-DAEFQHDE.js → wps-JNQUC4JS.js} +3 -3
- package/dist/{yuque-HCHTJWNI.js → yuque-GLAAOS7X.js} +3 -3
- package/dist/{yuque-KRH5O74J.js → yuque-MEF6VFLJ.js} +3 -3
- package/images/RUNNERS.md +15 -0
- package/images/cc-connect-runner/entrypoint.mjs +228 -0
- package/images/claude-runner/RUNNER.md +5 -3
- package/images/codex-runner/RUNNER.md +5 -3
- package/images/gemini-runner/RUNNER.md +5 -2
- package/images/hermes-runner/RUNNER.md +4 -2
- package/images/hermes-runner/entrypoint.mjs +269 -1
- package/images/openclaw-runner/Dockerfile +1 -0
- package/images/openclaw-runner/RUNNER.md +3 -0
- package/images/openclaw-runner/entrypoint.mjs +249 -1
- package/images/openclaw-runner/warm-runtime-deps.mjs +1 -3
- package/images/opencode-runner/RUNNER.md +5 -3
- package/package.json +3 -3
- package/templates/agent-marketplace-buddy.template.json +4 -1
- package/templates/bmad-method-buddy.template.json +4 -1
- package/templates/code-trainer.template.json +331 -0
- package/templates/gstack-buddy.template.json +4 -1
- package/templates/little-match-girl.template.json +10 -3
- package/dist/console/static/css/index.7f91f806.css +0 -1
- package/dist/console/static/js/index.4487e1ff.js +0 -1
- package/dist/lark-6LNA3LUQ.js +0 -103
- package/dist/lark-URVBZNS4.js +0 -102
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
createConsoleCommand
|
|
4
|
-
} from "./chunk-KODMGZUC.js";
|
|
5
|
-
import {
|
|
6
|
-
createInitCommand
|
|
7
|
-
} from "./chunk-SAP2DBHO.js";
|
|
8
|
-
import {
|
|
9
|
-
formatProvisionState,
|
|
10
|
-
loadProvisionState,
|
|
11
|
-
mergeProvisionState,
|
|
12
|
-
saveProvisionState
|
|
13
|
-
} from "./chunk-RTPBU5HF.js";
|
|
14
2
|
import {
|
|
15
3
|
createServeCommand,
|
|
16
4
|
detectInlineKey,
|
|
17
5
|
resolveCloudPackageAssetDir,
|
|
18
6
|
toProviderSecretEnvKey,
|
|
19
7
|
withLegacyEnvAliases
|
|
20
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X5VOIA72.js";
|
|
9
|
+
import {
|
|
10
|
+
createConsoleCommand
|
|
11
|
+
} from "./chunk-4YO3NA26.js";
|
|
12
|
+
import {
|
|
13
|
+
createInitCommand
|
|
14
|
+
} from "./chunk-Y6BKVDG7.js";
|
|
21
15
|
import {
|
|
22
16
|
RUNNER_AGENTS_VOLUME_NAME,
|
|
23
17
|
RUNNER_CONFIG_MOUNT_PATH,
|
|
@@ -29,6 +23,7 @@ import {
|
|
|
29
23
|
SHADOW_SLASH_COMMANDS_PATH,
|
|
30
24
|
buildOpenClawConfig,
|
|
31
25
|
collectPluginBuildEnvVars,
|
|
26
|
+
collectPluginRuntimeEnvOmitKeys,
|
|
32
27
|
collectPluginRuntimeExtensions,
|
|
33
28
|
collectRuntimeEnvRefPolicy,
|
|
34
29
|
collectTemplateRefs,
|
|
@@ -43,11 +38,17 @@ import {
|
|
|
43
38
|
resolveConfig,
|
|
44
39
|
runtimeContextEnv,
|
|
45
40
|
runtimeStatePvcName
|
|
46
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3CT6RQNM.js";
|
|
47
42
|
import {
|
|
48
43
|
deepMerge,
|
|
49
44
|
getPluginRegistry
|
|
50
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-ZGMWSSCC.js";
|
|
46
|
+
import {
|
|
47
|
+
formatProvisionState,
|
|
48
|
+
loadProvisionState,
|
|
49
|
+
mergeProvisionState,
|
|
50
|
+
saveProvisionState
|
|
51
|
+
} from "./chunk-RTPBU5HF.js";
|
|
51
52
|
import {
|
|
52
53
|
log
|
|
53
54
|
} from "./chunk-BF6CV2Y4.js";
|
|
@@ -89,6 +90,45 @@ import { join, resolve as resolve2 } from "path";
|
|
|
89
90
|
import { Command } from "commander";
|
|
90
91
|
|
|
91
92
|
// src/infra/plugin-k8s.ts
|
|
93
|
+
var COLON_SEPARATED_ENV_KEYS = /* @__PURE__ */ new Set(["PATH", "PYTHONPATH", "NODE_PATH"]);
|
|
94
|
+
var DEFAULT_CONTAINER_PATH_PARTS = [
|
|
95
|
+
"/usr/local/sbin",
|
|
96
|
+
"/usr/local/bin",
|
|
97
|
+
"/usr/sbin",
|
|
98
|
+
"/usr/bin",
|
|
99
|
+
"/sbin",
|
|
100
|
+
"/bin"
|
|
101
|
+
];
|
|
102
|
+
function uniquePathParts(parts) {
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const part of parts) {
|
|
105
|
+
if (!part || out.includes(part)) continue;
|
|
106
|
+
out.push(part);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
function mergeColonSeparatedEnvValue(key, current, next) {
|
|
111
|
+
const parts = [...current.split(":"), ...next.split(":")];
|
|
112
|
+
if (key !== "PATH") return uniquePathParts(parts).join(":");
|
|
113
|
+
const defaults = new Set(DEFAULT_CONTAINER_PATH_PARTS);
|
|
114
|
+
const pluginParts = parts.filter((part) => part && !defaults.has(part));
|
|
115
|
+
const defaultParts = parts.filter((part) => defaults.has(part));
|
|
116
|
+
return uniquePathParts([...pluginParts, ...defaultParts]).join(":");
|
|
117
|
+
}
|
|
118
|
+
function mergePluginEnvVars(target, incoming) {
|
|
119
|
+
for (const envVar of incoming) {
|
|
120
|
+
if (typeof envVar.name === "string" && typeof envVar.value === "string" && COLON_SEPARATED_ENV_KEYS.has(envVar.name)) {
|
|
121
|
+
const existing = target.find(
|
|
122
|
+
(candidate) => candidate.name === envVar.name && typeof candidate.value === "string"
|
|
123
|
+
);
|
|
124
|
+
if (existing && typeof existing.value === "string") {
|
|
125
|
+
existing.value = mergeColonSeparatedEnvValue(envVar.name, existing.value, envVar.value);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
target.push(envVar);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
92
132
|
function collectPluginK8sArtifacts(agent, config, namespace) {
|
|
93
133
|
const result = {
|
|
94
134
|
initContainers: [],
|
|
@@ -126,9 +166,7 @@ function collectPluginK8sArtifacts(agent, config, namespace) {
|
|
|
126
166
|
if (artifacts.volumeMounts?.length) {
|
|
127
167
|
result.volumeMounts.push(...artifacts.volumeMounts);
|
|
128
168
|
}
|
|
129
|
-
if (artifacts.envVars?.length)
|
|
130
|
-
result.envVars.push(...artifacts.envVars);
|
|
131
|
-
}
|
|
169
|
+
if (artifacts.envVars?.length) mergePluginEnvVars(result.envVars, artifacts.envVars);
|
|
132
170
|
if (artifacts.labels) {
|
|
133
171
|
Object.assign(result.labels, artifacts.labels);
|
|
134
172
|
}
|
|
@@ -259,7 +297,7 @@ function getKubeconfigPath(clusterName) {
|
|
|
259
297
|
function getMetaPath(clusterName) {
|
|
260
298
|
return join2(getClustersDir(), `${clusterName}.json`);
|
|
261
299
|
}
|
|
262
|
-
function storeKubeconfig(clusterName, rawKubeconfig, masterPublicIp, nodeCount) {
|
|
300
|
+
function storeKubeconfig(clusterName, rawKubeconfig, masterPublicIp, nodeCount, options) {
|
|
263
301
|
const dir = getClustersDir();
|
|
264
302
|
mkdirSync(dir, { recursive: true });
|
|
265
303
|
const kubeconfig = rawKubeconfig.replace(/https?:\/\/127\.0\.0\.1/g, `https://${masterPublicIp}`);
|
|
@@ -270,7 +308,9 @@ function storeKubeconfig(clusterName, rawKubeconfig, masterPublicIp, nodeCount)
|
|
|
270
308
|
masterHost: masterPublicIp,
|
|
271
309
|
nodeCount,
|
|
272
310
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
273
|
-
kubeconfigPath
|
|
311
|
+
kubeconfigPath,
|
|
312
|
+
...options?.configHash ? { configHash: options.configHash } : {},
|
|
313
|
+
...options?.features ? { features: options.features } : {}
|
|
274
314
|
};
|
|
275
315
|
writeFileSync2(getMetaPath(clusterName), JSON.stringify(meta, null, 2), { mode: 384 });
|
|
276
316
|
return meta;
|
|
@@ -285,6 +325,15 @@ Run: shadowob-cloud cluster init --config cluster.json`
|
|
|
285
325
|
}
|
|
286
326
|
return path;
|
|
287
327
|
}
|
|
328
|
+
function loadClusterMeta(clusterName) {
|
|
329
|
+
const path = getMetaPath(clusterName);
|
|
330
|
+
if (!existsSync3(path)) return null;
|
|
331
|
+
try {
|
|
332
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
288
337
|
function listRegisteredClusters() {
|
|
289
338
|
const dir = getClustersDir();
|
|
290
339
|
if (!existsSync3(dir)) return [];
|
|
@@ -330,22 +379,41 @@ import { resolve as resolve4 } from "path";
|
|
|
330
379
|
|
|
331
380
|
// src/cluster/schema.ts
|
|
332
381
|
import { z } from "zod";
|
|
333
|
-
var
|
|
334
|
-
var
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
382
|
+
var K8S_LABEL_KEY_RE = /^([A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9]\/)?[A-Za-z0-9]([-A-Za-z0-9_.]*[A-Za-z0-9])?$/;
|
|
383
|
+
var K8S_LABEL_VALUE_RE = /^(([A-Za-z0-9][-A-Za-z0-9_.]*[A-Za-z0-9])?)$/;
|
|
384
|
+
var KubernetesLabelMapSchema = z.record(
|
|
385
|
+
z.string().min(1).regex(K8S_LABEL_KEY_RE, "Invalid Kubernetes label key"),
|
|
386
|
+
z.string().max(63).regex(K8S_LABEL_VALUE_RE, "Invalid Kubernetes label value")
|
|
387
|
+
);
|
|
388
|
+
var ImageReferenceSchema = z.string().min(1).regex(/^\S+$/, "image must not contain whitespace");
|
|
389
|
+
var ClusterContainerdRegistriesSchema = z.object({
|
|
390
|
+
/**
|
|
391
|
+
* k3s containerd mirrors. Written to /etc/rancher/k3s/registries.yaml as JSON/YAML.
|
|
392
|
+
* Example: { "docker.io": { "endpoint": ["https://registry-1.docker.io"] } }
|
|
393
|
+
*/
|
|
394
|
+
mirrors: z.record(
|
|
395
|
+
z.string().min(1),
|
|
396
|
+
z.object({
|
|
397
|
+
endpoint: z.array(z.string().url()).min(1)
|
|
398
|
+
})
|
|
399
|
+
).optional(),
|
|
400
|
+
configs: z.record(
|
|
401
|
+
z.string().min(1),
|
|
402
|
+
z.object({
|
|
403
|
+
auth: z.object({
|
|
404
|
+
username: z.string().optional(),
|
|
405
|
+
password: z.string().optional(),
|
|
406
|
+
auth: z.string().optional(),
|
|
407
|
+
identityToken: z.string().optional()
|
|
408
|
+
}).optional(),
|
|
409
|
+
tls: z.object({
|
|
410
|
+
caFile: z.string().optional(),
|
|
411
|
+
certFile: z.string().optional(),
|
|
412
|
+
keyFile: z.string().optional(),
|
|
413
|
+
insecureSkipVerify: z.boolean().optional()
|
|
414
|
+
}).optional()
|
|
415
|
+
})
|
|
416
|
+
).optional()
|
|
349
417
|
});
|
|
350
418
|
var ClusterInstallConfigSchema = z.object({
|
|
351
419
|
/** k3s release version. Example: v1.35.4+k3s1 */
|
|
@@ -361,7 +429,86 @@ var ClusterInstallConfigSchema = z.object({
|
|
|
361
429
|
/** Registry prefix used by k3s for bundled system images. */
|
|
362
430
|
systemDefaultRegistry: z.string().min(1).regex(/^\S+$/, "systemDefaultRegistry must not contain whitespace").optional(),
|
|
363
431
|
/** Sandbox pause image used by k3s/containerd. Useful when Docker Hub is unreachable. */
|
|
364
|
-
pauseImage: z.string().min(1).regex(/^\S+$/, "pauseImage must not contain whitespace").optional()
|
|
432
|
+
pauseImage: z.string().min(1).regex(/^\S+$/, "pauseImage must not contain whitespace").optional(),
|
|
433
|
+
/** Optional k3s containerd registry mirrors/auth config for workload images. */
|
|
434
|
+
registries: ClusterContainerdRegistriesSchema.optional()
|
|
435
|
+
});
|
|
436
|
+
var AGENT_SANDBOX_DEFAULT_VERSION = "v0.4.5";
|
|
437
|
+
var AGENT_SANDBOX_DEFAULT_RUNTIME_CLASS = "shadow-runc";
|
|
438
|
+
var AGENT_SANDBOX_DEFAULT_RUNTIME_HANDLER = "runc";
|
|
439
|
+
var RuntimeClassNameSchema = z.string().min(1).regex(/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/, "runtimeClassName must be a Kubernetes DNS label");
|
|
440
|
+
var ClusterSandboxFeatureConfigSchema = z.object({
|
|
441
|
+
/** Enable agent-sandbox as a cluster capability. */
|
|
442
|
+
enabled: z.boolean().default(true),
|
|
443
|
+
/** Install/upgrade the upstream CRDs and controller during cluster init/apply. */
|
|
444
|
+
install: z.boolean().default(true),
|
|
445
|
+
/** Pinned upstream agent-sandbox release used to build default manifest URLs. */
|
|
446
|
+
version: z.string().min(1).regex(/^v\d+\.\d+\.\d+(?:[-+][A-Za-z0-9._-]+)?$/, "version must look like v0.4.5").default(AGENT_SANDBOX_DEFAULT_VERSION),
|
|
447
|
+
/**
|
|
448
|
+
* Optional manifest URLs. Defaults to the upstream release manifest.yaml and extensions.yaml.
|
|
449
|
+
* Use mirrored URLs for restricted networks.
|
|
450
|
+
*/
|
|
451
|
+
manifestUrls: z.array(z.string().url()).min(1).optional(),
|
|
452
|
+
/** Optional controller image override, useful for domestic/private registries. */
|
|
453
|
+
controllerImage: ImageReferenceSchema.optional(),
|
|
454
|
+
/** RuntimeClass injected into generated Cloud SaaS sandbox configs. */
|
|
455
|
+
runtimeClassName: RuntimeClassNameSchema.default(AGENT_SANDBOX_DEFAULT_RUNTIME_CLASS),
|
|
456
|
+
/** Create RuntimeClass automatically. Disable when the cluster already provides gvisor/runsc. */
|
|
457
|
+
createRuntimeClass: z.boolean().default(true),
|
|
458
|
+
/** RuntimeClass handler used when createRuntimeClass is true. */
|
|
459
|
+
runtimeClassHandler: z.string().min(1).regex(/^\S+$/, "runtimeClassHandler must not contain whitespace").default(AGENT_SANDBOX_DEFAULT_RUNTIME_HANDLER),
|
|
460
|
+
/** Wait timeout for CRDs/controller readiness. */
|
|
461
|
+
waitTimeoutSeconds: z.number().int().min(30).max(1200).default(300),
|
|
462
|
+
/** Fail cluster init/apply when sandbox cannot be verified. */
|
|
463
|
+
required: z.boolean().default(true),
|
|
464
|
+
/** Label selector injected into sandbox workloads unless templates override it. */
|
|
465
|
+
nodeSelector: KubernetesLabelMapSchema.default({ "shadowob.com/sandbox-ready": "true" }),
|
|
466
|
+
/** Run a real SandboxTemplate/SandboxClaim smoke after install/verify. */
|
|
467
|
+
smokeTest: z.boolean().default(false),
|
|
468
|
+
/** Image used by the optional smoke test. Mirror this for restricted networks. */
|
|
469
|
+
smokeImage: ImageReferenceSchema.default("busybox:1.36")
|
|
470
|
+
}).refine((sandbox) => !sandbox.createRuntimeClass || Boolean(sandbox.runtimeClassHandler), {
|
|
471
|
+
path: ["runtimeClassHandler"],
|
|
472
|
+
message: "runtimeClassHandler is required when createRuntimeClass is true"
|
|
473
|
+
});
|
|
474
|
+
var ClusterSandboxFeatureSchema = z.union([z.boolean(), ClusterSandboxFeatureConfigSchema]);
|
|
475
|
+
var ClusterFeaturesSchema = z.object({
|
|
476
|
+
/** agent-sandbox CRDs/controller/runtime-class management. */
|
|
477
|
+
sandbox: ClusterSandboxFeatureSchema.optional()
|
|
478
|
+
});
|
|
479
|
+
var NodeRoleSchema = z.enum(["master", "worker"]);
|
|
480
|
+
var NodeConfigSchema = z.object({
|
|
481
|
+
/** Node role in the cluster */
|
|
482
|
+
role: NodeRoleSchema,
|
|
483
|
+
/** Public IP or hostname */
|
|
484
|
+
host: z.string().min(1),
|
|
485
|
+
/** SSH port (default: 22) */
|
|
486
|
+
port: z.number().int().min(1).max(65535).default(22),
|
|
487
|
+
/** SSH username */
|
|
488
|
+
user: z.string().min(1),
|
|
489
|
+
/** Path to SSH private key (supports ~) — mutually inclusive with or exclusive of password */
|
|
490
|
+
sshKeyPath: z.string().optional(),
|
|
491
|
+
/** SSH private key passphrase — use ${env:VAR} to avoid storing plaintext */
|
|
492
|
+
sshKeyPassphrase: z.string().optional(),
|
|
493
|
+
/**
|
|
494
|
+
* SSH agent socket. Use true for SSH_AUTH_SOCK, or a socket path/template.
|
|
495
|
+
* Useful for encrypted private keys already loaded into an agent.
|
|
496
|
+
*/
|
|
497
|
+
sshAgent: z.union([z.boolean(), z.string().min(1)]).optional(),
|
|
498
|
+
/** SSH password — use ${env:VAR} to avoid storing plaintext */
|
|
499
|
+
password: z.string().optional(),
|
|
500
|
+
/** Optional per-node k3s installer overrides for mixed-region clusters. */
|
|
501
|
+
install: ClusterInstallConfigSchema.optional(),
|
|
502
|
+
/** Region label applied during cluster init/apply, e.g. cn or us. */
|
|
503
|
+
region: z.string().min(1).max(63).regex(K8S_LABEL_VALUE_RE).optional(),
|
|
504
|
+
/** Extra Kubernetes node labels applied during cluster init/apply. */
|
|
505
|
+
labels: KubernetesLabelMapSchema.optional(),
|
|
506
|
+
/** Per-node feature flags used for mixed-capability clusters. */
|
|
507
|
+
features: z.object({
|
|
508
|
+
sandbox: z.boolean().optional()
|
|
509
|
+
}).optional()
|
|
510
|
+
}).refine((n) => n.sshKeyPath !== void 0 || n.password !== void 0 || n.sshAgent, {
|
|
511
|
+
message: "Each node must have either sshKeyPath, password, or sshAgent"
|
|
365
512
|
});
|
|
366
513
|
var ClusterProviderSchema = z.enum(["ssh"]);
|
|
367
514
|
var ClusterConfigSchema = z.object({
|
|
@@ -373,7 +520,9 @@ var ClusterConfigSchema = z.object({
|
|
|
373
520
|
/** List of nodes */
|
|
374
521
|
nodes: z.array(NodeConfigSchema).min(1),
|
|
375
522
|
/** Optional k3s installer settings for restricted networks or pinned versions. */
|
|
376
|
-
install: ClusterInstallConfigSchema.optional()
|
|
523
|
+
install: ClusterInstallConfigSchema.optional(),
|
|
524
|
+
/** Optional cluster capabilities managed by cluster init/apply. */
|
|
525
|
+
features: ClusterFeaturesSchema.optional()
|
|
377
526
|
}).refine((c) => c.nodes.filter((n) => n.role === "master").length === 1, {
|
|
378
527
|
message: "Cluster must have exactly one master node"
|
|
379
528
|
});
|
|
@@ -409,14 +558,57 @@ ${issues.join("\n")}`);
|
|
|
409
558
|
return result.data;
|
|
410
559
|
}
|
|
411
560
|
function resolveNodeCredentials(node) {
|
|
561
|
+
let sshAgent;
|
|
562
|
+
if (node.sshAgent === true) {
|
|
563
|
+
sshAgent = process.env.SSH_AUTH_SOCK;
|
|
564
|
+
if (!sshAgent) {
|
|
565
|
+
throw new Error("SSH_AUTH_SOCK is not set (required by cluster.json sshAgent=true)");
|
|
566
|
+
}
|
|
567
|
+
} else if (typeof node.sshAgent === "string") {
|
|
568
|
+
sshAgent = resolveEnvTemplate(node.sshAgent);
|
|
569
|
+
}
|
|
412
570
|
return {
|
|
413
571
|
host: node.host,
|
|
414
572
|
port: node.port,
|
|
415
573
|
user: node.user,
|
|
416
574
|
sshKeyPath: node.sshKeyPath ? expandHome(node.sshKeyPath) : void 0,
|
|
575
|
+
sshKeyPassphrase: node.sshKeyPassphrase ? resolveEnvTemplate(node.sshKeyPassphrase) : void 0,
|
|
576
|
+
sshAgent,
|
|
417
577
|
password: node.password ? resolveEnvTemplate(node.password) : void 0
|
|
418
578
|
};
|
|
419
579
|
}
|
|
580
|
+
function resolveNodeInstallConfig(clusterInstall, node) {
|
|
581
|
+
const merged = { ...clusterInstall, ...node.install };
|
|
582
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
583
|
+
}
|
|
584
|
+
function defaultAgentSandboxManifestUrls(version = AGENT_SANDBOX_DEFAULT_VERSION) {
|
|
585
|
+
return [
|
|
586
|
+
`https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${version}/manifest.yaml`,
|
|
587
|
+
`https://github.com/kubernetes-sigs/agent-sandbox/releases/download/${version}/extensions.yaml`
|
|
588
|
+
];
|
|
589
|
+
}
|
|
590
|
+
function resolveClusterSandboxConfig(config) {
|
|
591
|
+
const sandbox = config.features?.sandbox;
|
|
592
|
+
if (sandbox === void 0 || sandbox === false) return null;
|
|
593
|
+
const normalized = sandbox === true ? {
|
|
594
|
+
enabled: true,
|
|
595
|
+
install: true,
|
|
596
|
+
version: AGENT_SANDBOX_DEFAULT_VERSION,
|
|
597
|
+
runtimeClassName: AGENT_SANDBOX_DEFAULT_RUNTIME_CLASS,
|
|
598
|
+
createRuntimeClass: true,
|
|
599
|
+
runtimeClassHandler: AGENT_SANDBOX_DEFAULT_RUNTIME_HANDLER,
|
|
600
|
+
waitTimeoutSeconds: 300,
|
|
601
|
+
required: true,
|
|
602
|
+
nodeSelector: { "shadowob.com/sandbox-ready": "true" },
|
|
603
|
+
smokeTest: false,
|
|
604
|
+
smokeImage: "busybox:1.36"
|
|
605
|
+
} : sandbox;
|
|
606
|
+
if (!normalized.enabled) return null;
|
|
607
|
+
return {
|
|
608
|
+
...normalized,
|
|
609
|
+
manifestUrls: normalized.manifestUrls && normalized.manifestUrls.length > 0 ? normalized.manifestUrls : defaultAgentSandboxManifestUrls(normalized.version)
|
|
610
|
+
};
|
|
611
|
+
}
|
|
420
612
|
function getMasterNode(config) {
|
|
421
613
|
const master = config.nodes.find((n) => n.role === "master");
|
|
422
614
|
if (!master) throw new Error("No master node found in cluster config");
|
|
@@ -429,21 +621,35 @@ function getWorkerNodes(config) {
|
|
|
429
621
|
// src/interfaces/cli/cluster.command.ts
|
|
430
622
|
function createClusterCommand(container2) {
|
|
431
623
|
const cluster = new Command2("cluster").description(
|
|
432
|
-
"Manage bare-server k3s clusters (init, status, list, destroy)"
|
|
624
|
+
"Manage bare-server k3s clusters (init, apply, status, list, destroy)"
|
|
433
625
|
);
|
|
434
|
-
|
|
626
|
+
async function applyClusterConfig(options) {
|
|
627
|
+
const config = readClusterConfig(options.config);
|
|
628
|
+
container2.logger.info(`Applying cluster "${config.name}" from ${options.config}...`);
|
|
629
|
+
const meta = await container2.cluster.init(
|
|
630
|
+
config,
|
|
631
|
+
(msg) => {
|
|
632
|
+
container2.logger.dim(msg);
|
|
633
|
+
},
|
|
634
|
+
options.force
|
|
635
|
+
);
|
|
636
|
+
container2.logger.success(`Cluster "${meta.name}" ready! Kubeconfig: ${meta.kubeconfigPath}`);
|
|
637
|
+
container2.logger.info(
|
|
638
|
+
`Edit ${options.config} and run this command again to add newly listed nodes.`
|
|
639
|
+
);
|
|
640
|
+
container2.logger.info(`Deploy agents: shadowob-cloud up --cluster ${meta.name}`);
|
|
641
|
+
}
|
|
642
|
+
cluster.command("init").description("Bootstrap or update a k3s cluster on bare servers").option("-c, --config <path>", "Path to cluster.json", "cluster.json").option("--force", "Reinstall k3s even if already installed on nodes").action(async (options) => {
|
|
435
643
|
try {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
);
|
|
445
|
-
container2.logger.success(`Cluster "${meta.name}" ready! Kubeconfig: ${meta.kubeconfigPath}`);
|
|
446
|
-
container2.logger.info(`Deploy agents: shadowob-cloud up --cluster ${meta.name}`);
|
|
644
|
+
await applyClusterConfig(options);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
container2.logger.error(err.message);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
cluster.command("apply").description("Apply cluster.json idempotently and add newly listed nodes").option("-c, --config <path>", "Path to cluster.json", "cluster.json").option("--force", "Reinstall k3s even if already installed on nodes").action(async (options) => {
|
|
651
|
+
try {
|
|
652
|
+
await applyClusterConfig(options);
|
|
447
653
|
} catch (err) {
|
|
448
654
|
container2.logger.error(err.message);
|
|
449
655
|
process.exit(1);
|
|
@@ -973,7 +1179,7 @@ function createGenerateCommand(container2) {
|
|
|
973
1179
|
container2.logger.error(`Config file not found: ${filePath}`);
|
|
974
1180
|
process.exit(1);
|
|
975
1181
|
}
|
|
976
|
-
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
1182
|
+
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-UK2QWD6G.js");
|
|
977
1183
|
await loadAllPlugins(getPluginRegistry2());
|
|
978
1184
|
const outputPath = options.output ? resolve7(options.output) : void 0;
|
|
979
1185
|
const configCwd = dirname(filePath);
|
|
@@ -1381,7 +1587,7 @@ function createOnboardCommand(container2) {
|
|
|
1381
1587
|
} else {
|
|
1382
1588
|
const answer = await ask(` No ${configPath} found. Create one from template? [Y/n] `);
|
|
1383
1589
|
if (!answer || answer.toLowerCase() !== "n") {
|
|
1384
|
-
const { createInitCommand: createInitCommand2 } = await import("./init.command-
|
|
1590
|
+
const { createInitCommand: createInitCommand2 } = await import("./init.command-C7UKPK2Y.js");
|
|
1385
1591
|
const init = createInitCommand2(container2);
|
|
1386
1592
|
await init.parseAsync(["--quick"], { from: "user" });
|
|
1387
1593
|
} else {
|
|
@@ -1398,7 +1604,7 @@ function createOnboardCommand(container2) {
|
|
|
1398
1604
|
}
|
|
1399
1605
|
container2.logger.success("Onboarding complete! Starting console...");
|
|
1400
1606
|
console.log();
|
|
1401
|
-
const { createConsoleCommand: createConsoleCommand2 } = await import("./dashboard.command-
|
|
1607
|
+
const { createConsoleCommand: createConsoleCommand2 } = await import("./dashboard.command-BRPZCZER.js");
|
|
1402
1608
|
const console_ = createConsoleCommand2(container2);
|
|
1403
1609
|
await console_.parseAsync([], { from: "user" });
|
|
1404
1610
|
});
|
|
@@ -1436,14 +1642,14 @@ function createProvisionCommand(container2) {
|
|
|
1436
1642
|
process.exit(1);
|
|
1437
1643
|
}
|
|
1438
1644
|
try {
|
|
1439
|
-
const { executePluginProvisions, loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
1645
|
+
const { executePluginProvisions, loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-UK2QWD6G.js");
|
|
1440
1646
|
try {
|
|
1441
1647
|
await loadAllPlugins(getPluginRegistry2());
|
|
1442
1648
|
} catch {
|
|
1443
1649
|
}
|
|
1444
1650
|
const agents = resolved.deployments?.agents ?? [];
|
|
1445
1651
|
const namespace = resolved.deployments?.namespace ?? "shadowob-cloud";
|
|
1446
|
-
const existing = loadProvisionState(filePath, options.stateDir);
|
|
1652
|
+
const existing = options.force ? null : loadProvisionState(filePath, options.stateDir);
|
|
1447
1653
|
const extraSecrets = {
|
|
1448
1654
|
SHADOW_SERVER_URL: shadowUrl,
|
|
1449
1655
|
SHADOW_USER_TOKEN: shadowToken
|
|
@@ -2150,7 +2356,7 @@ var SSHClient = class {
|
|
|
2150
2356
|
host: opts.host,
|
|
2151
2357
|
port: opts.port,
|
|
2152
2358
|
username: opts.user,
|
|
2153
|
-
...opts.sshKeyPath ? { privateKey: readFileSync5(opts.sshKeyPath, "utf8") } : { password: opts.password },
|
|
2359
|
+
...opts.sshAgent ? { agent: opts.sshAgent } : opts.sshKeyPath ? { privateKey: readFileSync5(opts.sshKeyPath, "utf8"), passphrase: opts.sshKeyPassphrase } : { password: opts.password },
|
|
2154
2360
|
// Reasonable timeout for WAN SSH
|
|
2155
2361
|
readyTimeout: 3e4
|
|
2156
2362
|
});
|
|
@@ -2236,9 +2442,14 @@ async function destroyCluster(options) {
|
|
|
2236
2442
|
}
|
|
2237
2443
|
|
|
2238
2444
|
// src/cluster/init.ts
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2445
|
+
import { createHash } from "crypto";
|
|
2446
|
+
|
|
2447
|
+
// src/cluster/sandbox.ts
|
|
2448
|
+
var REQUIRED_AGENT_SANDBOX_CRDS = [
|
|
2449
|
+
"sandboxes.agents.x-k8s.io",
|
|
2450
|
+
"sandboxtemplates.extensions.agents.x-k8s.io",
|
|
2451
|
+
"sandboxclaims.extensions.agents.x-k8s.io"
|
|
2452
|
+
];
|
|
2242
2453
|
function log2(onLog, msg) {
|
|
2243
2454
|
onLog?.(msg);
|
|
2244
2455
|
}
|
|
@@ -2249,6 +2460,291 @@ function asRoot2(shellCommand) {
|
|
|
2249
2460
|
const quoted = shellQuote2(shellCommand);
|
|
2250
2461
|
return `if [ "$(id -u)" -eq 0 ]; then sh -c ${quoted}; else sudo -n sh -c ${quoted}; fi`;
|
|
2251
2462
|
}
|
|
2463
|
+
async function hasAgentSandboxCrds(client) {
|
|
2464
|
+
const result = await client.exec(
|
|
2465
|
+
asRoot2(
|
|
2466
|
+
`k3s kubectl get crd ${REQUIRED_AGENT_SANDBOX_CRDS.map(shellQuote2).join(" ")} >/dev/null`
|
|
2467
|
+
)
|
|
2468
|
+
);
|
|
2469
|
+
return result.code === 0;
|
|
2470
|
+
}
|
|
2471
|
+
async function applyManifestUrl(client, url, onLog) {
|
|
2472
|
+
log2(onLog, `[sandbox] Applying ${url}`);
|
|
2473
|
+
await client.execOrThrow(asRoot2(`k3s kubectl apply -f ${shellQuote2(url)}`), {
|
|
2474
|
+
onStdout: (chunk) => log2(onLog, `[sandbox] ${chunk.trimEnd()}`),
|
|
2475
|
+
onStderr: (chunk) => log2(onLog, `[sandbox] ${chunk.trimEnd()}`),
|
|
2476
|
+
errorMessage: `Failed to apply agent-sandbox manifest ${url}. If this node cannot reach GitHub, set features.sandbox.manifestUrls to mirrored URLs.`
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
async function createOrVerifyRuntimeClass(client, options) {
|
|
2480
|
+
if (options.create) {
|
|
2481
|
+
const yaml = [
|
|
2482
|
+
"apiVersion: node.k8s.io/v1",
|
|
2483
|
+
"kind: RuntimeClass",
|
|
2484
|
+
"metadata:",
|
|
2485
|
+
` name: ${options.name}`,
|
|
2486
|
+
`handler: ${options.handler}`,
|
|
2487
|
+
""
|
|
2488
|
+
].join("\n");
|
|
2489
|
+
log2(
|
|
2490
|
+
options.onLog,
|
|
2491
|
+
`[sandbox] Ensuring RuntimeClass ${options.name} uses handler ${options.handler}`
|
|
2492
|
+
);
|
|
2493
|
+
await client.execOrThrow(asRoot2(`cat <<'EOF' | k3s kubectl apply -f -
|
|
2494
|
+
${yaml}EOF`), {
|
|
2495
|
+
errorMessage: `Failed to create RuntimeClass ${options.name}`
|
|
2496
|
+
});
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
log2(options.onLog, `[sandbox] Verifying RuntimeClass ${options.name}`);
|
|
2500
|
+
await client.execOrThrow(asRoot2(`k3s kubectl get runtimeclass ${shellQuote2(options.name)}`), {
|
|
2501
|
+
errorMessage: `RuntimeClass ${options.name} was not found. Install the runtime handler first, or set features.sandbox.createRuntimeClass=true with an explicit runtimeClassHandler.`
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
async function waitForAgentSandboxReady(client, timeoutSeconds, onLog) {
|
|
2505
|
+
const crdNames = REQUIRED_AGENT_SANDBOX_CRDS.map((name) => `crd/${name}`).join(" ");
|
|
2506
|
+
log2(onLog, "[sandbox] Waiting for agent-sandbox CRDs to become Established");
|
|
2507
|
+
await client.execOrThrow(
|
|
2508
|
+
asRoot2(`k3s kubectl wait --for=condition=Established ${crdNames} --timeout=${timeoutSeconds}s`),
|
|
2509
|
+
{ errorMessage: "agent-sandbox CRDs did not become Established" }
|
|
2510
|
+
);
|
|
2511
|
+
log2(onLog, "[sandbox] Waiting for agent-sandbox controller rollout");
|
|
2512
|
+
await client.execOrThrow(
|
|
2513
|
+
asRoot2(
|
|
2514
|
+
`k3s kubectl -n agent-sandbox-system rollout status deployment/agent-sandbox-controller --timeout=${timeoutSeconds}s`
|
|
2515
|
+
),
|
|
2516
|
+
{ errorMessage: "agent-sandbox controller did not become Ready" }
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
function nodeLabelArgs(labels) {
|
|
2520
|
+
return Object.entries(labels).map(([key, value]) => `${key}=${value}`).map(shellQuote2).join(" ");
|
|
2521
|
+
}
|
|
2522
|
+
function kubeNodeMatchesConfigNode(kubeNode, node) {
|
|
2523
|
+
const metadata = kubeNode.metadata ?? {};
|
|
2524
|
+
const status = kubeNode.status ?? {};
|
|
2525
|
+
const addresses = Array.isArray(status.addresses) ? status.addresses : [];
|
|
2526
|
+
const candidates = /* @__PURE__ */ new Set([
|
|
2527
|
+
typeof metadata.name === "string" ? metadata.name : "",
|
|
2528
|
+
...addresses.map((address) => address.address).filter((address) => typeof address === "string")
|
|
2529
|
+
]);
|
|
2530
|
+
return candidates.has(node.host);
|
|
2531
|
+
}
|
|
2532
|
+
async function labelConfiguredNodes(client, config, sandboxReady, onLog) {
|
|
2533
|
+
const output2 = await client.execOrThrow(asRoot2("k3s kubectl get nodes -o json"), {
|
|
2534
|
+
errorMessage: "Failed to list Kubernetes nodes for labeling"
|
|
2535
|
+
});
|
|
2536
|
+
const kubeNodes = JSON.parse(output2.stdout).items ?? [];
|
|
2537
|
+
for (const node of config.nodes) {
|
|
2538
|
+
const kubeNode = kubeNodes.find((candidate) => kubeNodeMatchesConfigNode(candidate, node));
|
|
2539
|
+
if (!kubeNode) {
|
|
2540
|
+
log2(onLog, `[sandbox] Could not match cluster.json node ${node.host} to a Kubernetes node`);
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
const metadata = kubeNode.metadata;
|
|
2544
|
+
const nodeName = metadata.name;
|
|
2545
|
+
if (typeof nodeName !== "string" || !nodeName) continue;
|
|
2546
|
+
const nodeSandboxReady = sandboxReady && (node.features?.sandbox ?? true);
|
|
2547
|
+
const labels = {
|
|
2548
|
+
"shadowob.com/sandbox-ready": nodeSandboxReady ? "true" : "false",
|
|
2549
|
+
...node.region ? { "shadowob.com/region": node.region } : {},
|
|
2550
|
+
...node.labels ?? {}
|
|
2551
|
+
};
|
|
2552
|
+
log2(onLog, `[sandbox] Labeling node ${nodeName}`);
|
|
2553
|
+
await client.execOrThrow(
|
|
2554
|
+
asRoot2(`k3s kubectl label node ${shellQuote2(nodeName)} ${nodeLabelArgs(labels)} --overwrite`),
|
|
2555
|
+
{ errorMessage: `Failed to label Kubernetes node ${nodeName}` }
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
async function runAgentSandboxSmoke(client, options) {
|
|
2560
|
+
const namespace = `shadow-sandbox-smoke-${Date.now().toString(36)}`;
|
|
2561
|
+
const manifest = [
|
|
2562
|
+
{
|
|
2563
|
+
apiVersion: "v1",
|
|
2564
|
+
kind: "Namespace",
|
|
2565
|
+
metadata: { name: namespace }
|
|
2566
|
+
},
|
|
2567
|
+
{
|
|
2568
|
+
apiVersion: "extensions.agents.x-k8s.io/v1alpha1",
|
|
2569
|
+
kind: "SandboxTemplate",
|
|
2570
|
+
metadata: { name: "smoke-template", namespace },
|
|
2571
|
+
spec: {
|
|
2572
|
+
networkPolicyManagement: "Unmanaged",
|
|
2573
|
+
envVarsInjectionPolicy: "Disallowed",
|
|
2574
|
+
podTemplate: {
|
|
2575
|
+
metadata: { labels: { app: "shadow-sandbox-smoke" } },
|
|
2576
|
+
spec: {
|
|
2577
|
+
runtimeClassName: options.runtimeClassName,
|
|
2578
|
+
containers: [
|
|
2579
|
+
{
|
|
2580
|
+
name: "smoke",
|
|
2581
|
+
image: options.image,
|
|
2582
|
+
command: ["sh", "-c", "echo shadow-sandbox-smoke && sleep 30"]
|
|
2583
|
+
}
|
|
2584
|
+
],
|
|
2585
|
+
restartPolicy: "Always"
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
volumeClaimTemplates: []
|
|
2589
|
+
}
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
apiVersion: "extensions.agents.x-k8s.io/v1alpha1",
|
|
2593
|
+
kind: "SandboxClaim",
|
|
2594
|
+
metadata: { name: "smoke", namespace },
|
|
2595
|
+
spec: {
|
|
2596
|
+
sandboxTemplateRef: { name: "smoke-template" },
|
|
2597
|
+
warmpool: "none",
|
|
2598
|
+
lifecycle: { shutdownPolicy: "Delete" }
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
];
|
|
2602
|
+
const yaml = JSON.stringify({ apiVersion: "v1", kind: "List", items: manifest }, null, 2);
|
|
2603
|
+
log2(options.onLog, `[sandbox] Running smoke test in namespace ${namespace}`);
|
|
2604
|
+
try {
|
|
2605
|
+
await client.execOrThrow(asRoot2(`cat <<'EOF' | k3s kubectl apply -f -
|
|
2606
|
+
${yaml}
|
|
2607
|
+
EOF`), {
|
|
2608
|
+
errorMessage: "Failed to create agent-sandbox smoke resources"
|
|
2609
|
+
});
|
|
2610
|
+
await client.execOrThrow(
|
|
2611
|
+
asRoot2(
|
|
2612
|
+
`timeout ${options.timeoutSeconds} sh -c ` + shellQuote2(
|
|
2613
|
+
'until [ "$(k3s kubectl -n ' + namespace + ' get sandboxclaim smoke -o jsonpath="{.status.conditions[?(@.type==\\"Ready\\")].status}" 2>/dev/null)" = "True" ]; do sleep 3; done'
|
|
2614
|
+
)
|
|
2615
|
+
),
|
|
2616
|
+
{ errorMessage: "agent-sandbox smoke SandboxClaim did not become Ready" }
|
|
2617
|
+
);
|
|
2618
|
+
log2(options.onLog, "[sandbox] Smoke test passed");
|
|
2619
|
+
} finally {
|
|
2620
|
+
await client.exec(
|
|
2621
|
+
asRoot2(`k3s kubectl delete namespace ${shellQuote2(namespace)} --ignore-not-found=true`)
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
async function installClusterSandbox(options) {
|
|
2626
|
+
const sandbox = resolveClusterSandboxConfig(options.config);
|
|
2627
|
+
if (!sandbox) return false;
|
|
2628
|
+
const master = getMasterNode(options.config);
|
|
2629
|
+
const creds = resolveNodeCredentials(master);
|
|
2630
|
+
const client = new SSHClient();
|
|
2631
|
+
log2(options.onLog, `[sandbox ${creds.host}] Connecting via SSH...`);
|
|
2632
|
+
await client.connect(creds);
|
|
2633
|
+
try {
|
|
2634
|
+
const alreadyInstalled = await hasAgentSandboxCrds(client);
|
|
2635
|
+
if (!sandbox.install && !alreadyInstalled) {
|
|
2636
|
+
const message = "features.sandbox.enabled=true but the required agent-sandbox CRDs are not installed. Set features.sandbox.install=true or apply the CRDs/controller before using sandbox.";
|
|
2637
|
+
if (sandbox.required) throw new Error(message);
|
|
2638
|
+
log2(options.onLog, `[sandbox] ${message}`);
|
|
2639
|
+
return false;
|
|
2640
|
+
}
|
|
2641
|
+
if (sandbox.install) {
|
|
2642
|
+
log2(options.onLog, `[sandbox] Installing agent-sandbox ${sandbox.version}`);
|
|
2643
|
+
for (const url of sandbox.manifestUrls) {
|
|
2644
|
+
await applyManifestUrl(client, url, options.onLog);
|
|
2645
|
+
}
|
|
2646
|
+
if (sandbox.controllerImage) {
|
|
2647
|
+
log2(options.onLog, `[sandbox] Setting controller image ${sandbox.controllerImage}`);
|
|
2648
|
+
await client.execOrThrow(
|
|
2649
|
+
asRoot2(
|
|
2650
|
+
`k3s kubectl -n agent-sandbox-system set image deployment/agent-sandbox-controller agent-sandbox-controller=${shellQuote2(sandbox.controllerImage)}`
|
|
2651
|
+
),
|
|
2652
|
+
{ errorMessage: "Failed to set agent-sandbox controller image" }
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
await createOrVerifyRuntimeClass(client, {
|
|
2657
|
+
name: sandbox.runtimeClassName,
|
|
2658
|
+
handler: sandbox.runtimeClassHandler,
|
|
2659
|
+
create: sandbox.createRuntimeClass,
|
|
2660
|
+
onLog: options.onLog
|
|
2661
|
+
});
|
|
2662
|
+
await waitForAgentSandboxReady(client, sandbox.waitTimeoutSeconds, options.onLog);
|
|
2663
|
+
await labelConfiguredNodes(client, options.config, true, options.onLog);
|
|
2664
|
+
if (sandbox.smokeTest) {
|
|
2665
|
+
await runAgentSandboxSmoke(client, {
|
|
2666
|
+
runtimeClassName: sandbox.runtimeClassName,
|
|
2667
|
+
image: sandbox.smokeImage,
|
|
2668
|
+
timeoutSeconds: sandbox.waitTimeoutSeconds,
|
|
2669
|
+
onLog: options.onLog
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
log2(
|
|
2673
|
+
options.onLog,
|
|
2674
|
+
`[sandbox] Ready with RuntimeClass ${sandbox.runtimeClassName}; new deployments can use agent-sandbox`
|
|
2675
|
+
);
|
|
2676
|
+
return true;
|
|
2677
|
+
} catch (err) {
|
|
2678
|
+
if (sandbox.required) throw err;
|
|
2679
|
+
log2(
|
|
2680
|
+
options.onLog,
|
|
2681
|
+
`[sandbox] Not ready; deployment fallback remains available: ${err.message}`
|
|
2682
|
+
);
|
|
2683
|
+
return false;
|
|
2684
|
+
} finally {
|
|
2685
|
+
await client.dispose();
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// src/cluster/init.ts
|
|
2690
|
+
var K3S_INSTALL_URL = "https://get.k3s.io";
|
|
2691
|
+
var CN_PAUSE_IMAGE = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6";
|
|
2692
|
+
var CN_SYSTEM_DEFAULT_REGISTRY = "registry.cn-hangzhou.aliyuncs.com";
|
|
2693
|
+
function log3(onLog, msg) {
|
|
2694
|
+
onLog?.(msg);
|
|
2695
|
+
}
|
|
2696
|
+
function shellQuote3(value) {
|
|
2697
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
2698
|
+
}
|
|
2699
|
+
function asRoot3(shellCommand) {
|
|
2700
|
+
const quoted = shellQuote3(shellCommand);
|
|
2701
|
+
return `if [ "$(id -u)" -eq 0 ]; then sh -c ${quoted}; else sudo -n sh -c ${quoted}; fi`;
|
|
2702
|
+
}
|
|
2703
|
+
function resolveEnvTemplates(value) {
|
|
2704
|
+
if (typeof value === "string") {
|
|
2705
|
+
return value.replace(/\$\{env:([^}]+)\}/g, (_match, envKey) => {
|
|
2706
|
+
const envVal = process.env[envKey];
|
|
2707
|
+
if (envVal === void 0) {
|
|
2708
|
+
throw new Error(`Environment variable "${envKey}" is not set (required by cluster.json)`);
|
|
2709
|
+
}
|
|
2710
|
+
return envVal;
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
if (Array.isArray(value)) return value.map(resolveEnvTemplates);
|
|
2714
|
+
if (value && typeof value === "object") {
|
|
2715
|
+
return Object.fromEntries(
|
|
2716
|
+
Object.entries(value).map(([key, child]) => [key, resolveEnvTemplates(child)])
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
return value;
|
|
2720
|
+
}
|
|
2721
|
+
function clusterConfigHash(config) {
|
|
2722
|
+
return createHash("sha256").update(stableStringify(config)).digest("hex");
|
|
2723
|
+
}
|
|
2724
|
+
function stableStringify(value) {
|
|
2725
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
2726
|
+
if (value && typeof value === "object") {
|
|
2727
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => `${JSON.stringify(key)}:${stableStringify(child)}`).join(",")}}`;
|
|
2728
|
+
}
|
|
2729
|
+
return JSON.stringify(value);
|
|
2730
|
+
}
|
|
2731
|
+
async function ensureK3sRegistriesConfig(client, install2, serviceName, onLog) {
|
|
2732
|
+
if (!install2?.registries) return;
|
|
2733
|
+
const registries = JSON.stringify(resolveEnvTemplates(install2.registries), null, 2);
|
|
2734
|
+
log3(onLog, `[${serviceName}] Writing k3s containerd registries.yaml`);
|
|
2735
|
+
await client.execOrThrow(
|
|
2736
|
+
asRoot3(
|
|
2737
|
+
[
|
|
2738
|
+
"mkdir -p /etc/rancher/k3s",
|
|
2739
|
+
`cat > /etc/rancher/k3s/registries.yaml <<'EOF'
|
|
2740
|
+
${registries}
|
|
2741
|
+
EOF`,
|
|
2742
|
+
`if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet ${serviceName}; then systemctl restart ${serviceName}; fi`
|
|
2743
|
+
].join("\n")
|
|
2744
|
+
),
|
|
2745
|
+
{ errorMessage: `Failed to write k3s registries.yaml for ${serviceName}` }
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2252
2748
|
function resolveK3sMirror(install2) {
|
|
2253
2749
|
return process.env.INSTALL_K3S_MIRROR ?? process.env.K3S_INSTALL_MIRROR ?? install2?.k3sMirror;
|
|
2254
2750
|
}
|
|
@@ -2317,7 +2813,7 @@ function k3sInstallEnv(extra, install2) {
|
|
|
2317
2813
|
INSTALL_K3S_VERSION: version,
|
|
2318
2814
|
...extra
|
|
2319
2815
|
};
|
|
2320
|
-
return Object.entries(env).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${
|
|
2816
|
+
return Object.entries(env).filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote3(value)}`).join(" ");
|
|
2321
2817
|
}
|
|
2322
2818
|
async function isK3sInstalled(client) {
|
|
2323
2819
|
const result = await client.exec("which k3s");
|
|
@@ -2326,25 +2822,26 @@ async function isK3sInstalled(client) {
|
|
|
2326
2822
|
async function installMaster(master, install2, force, onLog) {
|
|
2327
2823
|
const creds = resolveNodeCredentials(master);
|
|
2328
2824
|
const client = new SSHClient();
|
|
2329
|
-
|
|
2825
|
+
log3(onLog, `[master ${creds.host}] Connecting via SSH...`);
|
|
2330
2826
|
await client.connect(creds);
|
|
2331
2827
|
try {
|
|
2828
|
+
await ensureK3sRegistriesConfig(client, install2, "k3s", onLog);
|
|
2332
2829
|
const alreadyInstalled = await isK3sInstalled(client);
|
|
2333
2830
|
if (alreadyInstalled && !force) {
|
|
2334
|
-
|
|
2831
|
+
log3(
|
|
2335
2832
|
onLog,
|
|
2336
2833
|
`[master ${creds.host}] k3s already installed \u2014 skipping install (use --force to reinstall)`
|
|
2337
2834
|
);
|
|
2338
|
-
|
|
2835
|
+
log3(onLog, `[master ${creds.host}] Reading existing token and kubeconfig...`);
|
|
2339
2836
|
} else {
|
|
2340
2837
|
if (alreadyInstalled && force) {
|
|
2341
|
-
|
|
2342
|
-
await client.exec(
|
|
2838
|
+
log3(onLog, `[master ${creds.host}] k3s already installed \u2014 reinstalling (--force)`);
|
|
2839
|
+
await client.exec(asRoot3("/usr/local/bin/k3s-uninstall.sh 2>/dev/null || true"));
|
|
2343
2840
|
}
|
|
2344
|
-
|
|
2841
|
+
log3(onLog, `[master ${creds.host}] Installing k3s server...`);
|
|
2345
2842
|
await client.execOrThrow(
|
|
2346
|
-
|
|
2347
|
-
`curl -sfL ${
|
|
2843
|
+
asRoot3(
|
|
2844
|
+
`curl -sfL ${shellQuote3(K3S_INSTALL_URL)} | ${k3sInstallEnv(
|
|
2348
2845
|
{
|
|
2349
2846
|
INSTALL_K3S_EXEC: k3sExec(["server", "--tls-san", creds.host], install2)
|
|
2350
2847
|
},
|
|
@@ -2352,28 +2849,28 @@ async function installMaster(master, install2, force, onLog) {
|
|
|
2352
2849
|
)} sh -`
|
|
2353
2850
|
),
|
|
2354
2851
|
{
|
|
2355
|
-
onStdout: (c) =>
|
|
2356
|
-
onStderr: (c) =>
|
|
2852
|
+
onStdout: (c) => log3(onLog, `[master] ${c.trimEnd()}`),
|
|
2853
|
+
onStderr: (c) => log3(onLog, `[master] ${c.trimEnd()}`),
|
|
2357
2854
|
errorMessage: `Failed to install k3s on master ${creds.host}`
|
|
2358
2855
|
}
|
|
2359
2856
|
);
|
|
2360
2857
|
}
|
|
2361
|
-
|
|
2858
|
+
log3(onLog, `[master ${creds.host}] Waiting for k3s to be ready...`);
|
|
2362
2859
|
await client.execOrThrow(
|
|
2363
|
-
|
|
2860
|
+
asRoot3(`timeout 120 sh -c 'until k3s kubectl get nodes > /dev/null 2>&1; do sleep 3; done'`),
|
|
2364
2861
|
{ errorMessage: "k3s master did not become ready within 120s" }
|
|
2365
2862
|
);
|
|
2366
|
-
|
|
2863
|
+
log3(onLog, `[master ${creds.host}] Reading node token...`);
|
|
2367
2864
|
const tokenResult = await client.execOrThrow(
|
|
2368
|
-
|
|
2865
|
+
asRoot3("cat /var/lib/rancher/k3s/server/node-token"),
|
|
2369
2866
|
{
|
|
2370
2867
|
errorMessage: "Failed to read k3s node token"
|
|
2371
2868
|
}
|
|
2372
2869
|
);
|
|
2373
2870
|
const token = tokenResult.stdout.trim();
|
|
2374
2871
|
if (!token) throw new Error("k3s node token is empty");
|
|
2375
|
-
|
|
2376
|
-
const kubeconfigResult = await client.execOrThrow(
|
|
2872
|
+
log3(onLog, `[master ${creds.host}] Reading kubeconfig...`);
|
|
2873
|
+
const kubeconfigResult = await client.execOrThrow(asRoot3("cat /etc/rancher/k3s/k3s.yaml"), {
|
|
2377
2874
|
errorMessage: "Failed to read k3s kubeconfig"
|
|
2378
2875
|
});
|
|
2379
2876
|
return { token, kubeconfig: kubeconfigResult.stdout };
|
|
@@ -2384,25 +2881,26 @@ async function installMaster(master, install2, force, onLog) {
|
|
|
2384
2881
|
async function installWorker(worker, masterHost, token, install2, force, onLog) {
|
|
2385
2882
|
const creds = resolveNodeCredentials(worker);
|
|
2386
2883
|
const client = new SSHClient();
|
|
2387
|
-
|
|
2884
|
+
log3(onLog, `[worker ${creds.host}] Connecting via SSH...`);
|
|
2388
2885
|
await client.connect(creds);
|
|
2389
2886
|
try {
|
|
2887
|
+
await ensureK3sRegistriesConfig(client, install2, "k3s-agent", onLog);
|
|
2390
2888
|
const alreadyInstalled = await isK3sInstalled(client);
|
|
2391
2889
|
if (alreadyInstalled && !force) {
|
|
2392
|
-
|
|
2890
|
+
log3(
|
|
2393
2891
|
onLog,
|
|
2394
2892
|
`[worker ${creds.host}] k3s already installed \u2014 skipping install (use --force to reinstall)`
|
|
2395
2893
|
);
|
|
2396
2894
|
return;
|
|
2397
2895
|
}
|
|
2398
2896
|
if (alreadyInstalled && force) {
|
|
2399
|
-
|
|
2400
|
-
await client.exec(
|
|
2897
|
+
log3(onLog, `[worker ${creds.host}] k3s already installed \u2014 reinstalling (--force)`);
|
|
2898
|
+
await client.exec(asRoot3("/usr/local/bin/k3s-agent-uninstall.sh 2>/dev/null || true"));
|
|
2401
2899
|
}
|
|
2402
|
-
|
|
2900
|
+
log3(onLog, `[worker ${creds.host}] Installing k3s agent and joining cluster...`);
|
|
2403
2901
|
await client.execOrThrow(
|
|
2404
|
-
|
|
2405
|
-
`curl -sfL ${
|
|
2902
|
+
asRoot3(
|
|
2903
|
+
`curl -sfL ${shellQuote3(K3S_INSTALL_URL)} | ${k3sInstallEnv(
|
|
2406
2904
|
{
|
|
2407
2905
|
K3S_URL: `https://${masterHost}:6443`,
|
|
2408
2906
|
K3S_TOKEN: token,
|
|
@@ -2412,12 +2910,12 @@ async function installWorker(worker, masterHost, token, install2, force, onLog)
|
|
|
2412
2910
|
)} sh -`
|
|
2413
2911
|
),
|
|
2414
2912
|
{
|
|
2415
|
-
onStdout: (c) =>
|
|
2416
|
-
onStderr: (c) =>
|
|
2913
|
+
onStdout: (c) => log3(onLog, `[worker ${creds.host}] ${c.trimEnd()}`),
|
|
2914
|
+
onStderr: (c) => log3(onLog, `[worker ${creds.host}] ${c.trimEnd()}`),
|
|
2417
2915
|
errorMessage: `Failed to install k3s agent on worker ${creds.host}`
|
|
2418
2916
|
}
|
|
2419
2917
|
);
|
|
2420
|
-
|
|
2918
|
+
log3(onLog, `[worker ${creds.host}] Agent joined cluster \u2713`);
|
|
2421
2919
|
} finally {
|
|
2422
2920
|
await client.dispose();
|
|
2423
2921
|
}
|
|
@@ -2426,17 +2924,39 @@ async function initCluster(options) {
|
|
|
2426
2924
|
const { config, force = false, onLog } = options;
|
|
2427
2925
|
const master = getMasterNode(config);
|
|
2428
2926
|
const workers = getWorkerNodes(config);
|
|
2429
|
-
|
|
2430
|
-
const
|
|
2927
|
+
log3(onLog, `Initializing cluster "${config.name}" with ${config.nodes.length} nodes...`);
|
|
2928
|
+
const masterInstall = resolveNodeInstallConfig(config.install, master);
|
|
2929
|
+
const { token, kubeconfig } = await installMaster(master, masterInstall, force, onLog);
|
|
2431
2930
|
if (workers.length > 0) {
|
|
2432
|
-
|
|
2931
|
+
log3(onLog, `Installing ${workers.length} worker(s) in parallel...`);
|
|
2433
2932
|
await Promise.all(
|
|
2434
|
-
workers.map(
|
|
2933
|
+
workers.map(
|
|
2934
|
+
(worker) => installWorker(
|
|
2935
|
+
worker,
|
|
2936
|
+
master.host,
|
|
2937
|
+
token,
|
|
2938
|
+
resolveNodeInstallConfig(config.install, worker),
|
|
2939
|
+
force,
|
|
2940
|
+
onLog
|
|
2941
|
+
)
|
|
2942
|
+
)
|
|
2435
2943
|
);
|
|
2436
2944
|
}
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2439
|
-
|
|
2945
|
+
const sandboxConfig = resolveClusterSandboxConfig(config);
|
|
2946
|
+
const sandboxEnabled = await installClusterSandbox({ config, onLog });
|
|
2947
|
+
const meta = storeKubeconfig(config.name, kubeconfig, master.host, config.nodes.length, {
|
|
2948
|
+
configHash: clusterConfigHash(config),
|
|
2949
|
+
features: {
|
|
2950
|
+
sandbox: sandboxConfig ? {
|
|
2951
|
+
enabled: sandboxEnabled,
|
|
2952
|
+
version: sandboxConfig.version,
|
|
2953
|
+
runtimeClassName: sandboxConfig.runtimeClassName,
|
|
2954
|
+
nodeSelector: sandboxConfig.nodeSelector
|
|
2955
|
+
} : { enabled: false }
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
log3(onLog, `Kubeconfig stored at ${meta.kubeconfigPath}`);
|
|
2959
|
+
log3(onLog, `Cluster "${config.name}" is ready. Use: shadowob-cloud up --cluster ${config.name}`);
|
|
2440
2960
|
return meta;
|
|
2441
2961
|
}
|
|
2442
2962
|
|
|
@@ -2587,8 +3107,68 @@ var ConfigService = class {
|
|
|
2587
3107
|
};
|
|
2588
3108
|
|
|
2589
3109
|
// src/services/deploy.service.ts
|
|
2590
|
-
import { existsSync as
|
|
3110
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2591
3111
|
import { dirname as dirname3, resolve as resolve16 } from "path";
|
|
3112
|
+
|
|
3113
|
+
// src/utils/kubeconfig-file.ts
|
|
3114
|
+
import { existsSync as existsSync16, readFileSync as readFileSync6, statSync } from "fs";
|
|
3115
|
+
import { homedir as homedir3 } from "os";
|
|
3116
|
+
import { join as join4 } from "path";
|
|
3117
|
+
function defaultKubeconfigPath() {
|
|
3118
|
+
return join4(homedir3(), ".kube", "config");
|
|
3119
|
+
}
|
|
3120
|
+
function kubeconfigSetupHint() {
|
|
3121
|
+
return "Configure KUBECONFIG_HOST_PATH or CLOUD_SAAS_CLUSTER_KUBECONFIG_HOST_PATH to an existing host kubeconfig file, or initialize/import a cluster before deploying.";
|
|
3122
|
+
}
|
|
3123
|
+
function assertReadableKubeconfigFile(kubeconfigPath, label = "Kubernetes kubeconfig") {
|
|
3124
|
+
if (!existsSync16(kubeconfigPath)) {
|
|
3125
|
+
throw new Error(`${label} not found at ${kubeconfigPath}. ${kubeconfigSetupHint()}`);
|
|
3126
|
+
}
|
|
3127
|
+
let stat3;
|
|
3128
|
+
try {
|
|
3129
|
+
stat3 = statSync(kubeconfigPath);
|
|
3130
|
+
} catch (err) {
|
|
3131
|
+
throw new Error(
|
|
3132
|
+
`Failed to inspect ${label} at ${kubeconfigPath}: ${err.message}. ` + kubeconfigSetupHint()
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
if (stat3.isDirectory()) {
|
|
3136
|
+
throw new Error(
|
|
3137
|
+
`${label} path ${kubeconfigPath} is a directory, not a file. This usually means Docker created the bind-mount target because the host kubeconfig file is missing. ` + kubeconfigSetupHint()
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
if (!stat3.isFile()) {
|
|
3141
|
+
throw new Error(
|
|
3142
|
+
`${label} path ${kubeconfigPath} is not a regular file. ${kubeconfigSetupHint()}`
|
|
3143
|
+
);
|
|
3144
|
+
}
|
|
3145
|
+
if (stat3.size === 0) {
|
|
3146
|
+
throw new Error(`${label} at ${kubeconfigPath} is empty. ${kubeconfigSetupHint()}`);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
function readKubeconfigFile(kubeconfigPath, label = "Kubernetes kubeconfig") {
|
|
3150
|
+
assertReadableKubeconfigFile(kubeconfigPath, label);
|
|
3151
|
+
try {
|
|
3152
|
+
return readFileSync6(kubeconfigPath, "utf8");
|
|
3153
|
+
} catch (err) {
|
|
3154
|
+
throw new Error(
|
|
3155
|
+
`Failed to read ${label} at ${kubeconfigPath}: ${err.message}. ` + kubeconfigSetupHint()
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
function findReadableKubeconfigPath(candidates, label = "Kubernetes kubeconfig") {
|
|
3160
|
+
const uniqueCandidates = [
|
|
3161
|
+
...new Set(candidates.filter((candidate) => Boolean(candidate)))
|
|
3162
|
+
];
|
|
3163
|
+
for (const candidate of uniqueCandidates) {
|
|
3164
|
+
if (!existsSync16(candidate)) continue;
|
|
3165
|
+
assertReadableKubeconfigFile(candidate, label);
|
|
3166
|
+
return candidate;
|
|
3167
|
+
}
|
|
3168
|
+
return void 0;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
// src/services/deploy.service.ts
|
|
2592
3172
|
function deploymentReadyTimeoutMs() {
|
|
2593
3173
|
const raw = Number(process.env.CLOUD_DEPLOYMENT_READY_TIMEOUT_MS);
|
|
2594
3174
|
return Number.isFinite(raw) && raw > 0 ? raw : 20 * 6e4;
|
|
@@ -2596,13 +3176,22 @@ function deploymentReadyTimeoutMs() {
|
|
|
2596
3176
|
function isAgentSandboxBackend(config) {
|
|
2597
3177
|
return (config.deployments?.backend ?? "agent-sandbox") === "agent-sandbox";
|
|
2598
3178
|
}
|
|
3179
|
+
function workloadBackendPolicy(config) {
|
|
3180
|
+
if (config.deployments?.backendPolicy) return config.deployments.backendPolicy;
|
|
3181
|
+
return isAgentSandboxBackend(config) ? "sandbox-required" : "deployment-only";
|
|
3182
|
+
}
|
|
3183
|
+
function sandboxRuntimeClassNames(config) {
|
|
3184
|
+
const deployments = config.deployments;
|
|
3185
|
+
if (!deployments) return [];
|
|
3186
|
+
const defaultRuntimeClassName = deployments.sandbox?.runtimeClassName ?? "gvisor";
|
|
3187
|
+
const names = deployments.agents.map(
|
|
3188
|
+
(agent) => agent.sandbox?.runtimeClassName ?? defaultRuntimeClassName
|
|
3189
|
+
);
|
|
3190
|
+
return [...new Set(names)];
|
|
3191
|
+
}
|
|
2599
3192
|
function readKubeconfigForRuntimeWait(kubeConfigPath) {
|
|
2600
3193
|
if (!kubeConfigPath) return void 0;
|
|
2601
|
-
|
|
2602
|
-
return readFileSync6(kubeConfigPath, "utf8");
|
|
2603
|
-
} catch {
|
|
2604
|
-
return void 0;
|
|
2605
|
-
}
|
|
3194
|
+
return readKubeconfigFile(kubeConfigPath);
|
|
2606
3195
|
}
|
|
2607
3196
|
function resolveStackName(namespace, stack) {
|
|
2608
3197
|
return stack ?? `dev-${namespace}`;
|
|
@@ -2644,7 +3233,7 @@ function configUsesPlugins(value, depth = 0) {
|
|
|
2644
3233
|
}
|
|
2645
3234
|
async function ensureBuiltInPluginsLoaded() {
|
|
2646
3235
|
try {
|
|
2647
|
-
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
3236
|
+
const { loadAllPlugins, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-UK2QWD6G.js");
|
|
2648
3237
|
const registry = getPluginRegistry2();
|
|
2649
3238
|
if (registry.size === 0) await loadAllPlugins(registry);
|
|
2650
3239
|
} catch {
|
|
@@ -2653,7 +3242,7 @@ async function ensureBuiltInPluginsLoaded() {
|
|
|
2653
3242
|
function readKubeconfigCurrentContext(kubeConfigPath) {
|
|
2654
3243
|
if (!kubeConfigPath) return void 0;
|
|
2655
3244
|
try {
|
|
2656
|
-
return
|
|
3245
|
+
return readKubeconfigFile(kubeConfigPath).match(/current-context:\s*(\S+)/)?.[1];
|
|
2657
3246
|
} catch {
|
|
2658
3247
|
return void 0;
|
|
2659
3248
|
}
|
|
@@ -2666,6 +3255,39 @@ function summarizeK8sTarget(options, kubeConfigPath) {
|
|
|
2666
3255
|
const kubeconfig = kubeConfigPath ?? process.env.KUBECONFIG ?? "~/.kube/config";
|
|
2667
3256
|
return `Kubernetes target: cluster=${cluster} context=${context} kubeconfig=${kubeconfig}`;
|
|
2668
3257
|
}
|
|
3258
|
+
function applyManagedClusterDefaults(config, clusterName) {
|
|
3259
|
+
if (!clusterName || !config.deployments) return;
|
|
3260
|
+
const sandbox = loadClusterMeta(clusterName)?.features?.sandbox;
|
|
3261
|
+
if (!config.deployments.backendPolicy) {
|
|
3262
|
+
config.deployments.backendPolicy = sandbox?.enabled ? "sandbox-preferred" : config.deployments.backend === "agent-sandbox" ? "sandbox-required" : "deployment-only";
|
|
3263
|
+
}
|
|
3264
|
+
if (!config.deployments.backend) {
|
|
3265
|
+
config.deployments.backend = config.deployments.backendPolicy === "deployment-only" ? "deployment" : sandbox?.enabled ? "agent-sandbox" : "deployment";
|
|
3266
|
+
}
|
|
3267
|
+
if (config.deployments.backend === "agent-sandbox" && sandbox?.enabled && sandbox.runtimeClassName) {
|
|
3268
|
+
config.deployments.sandbox = {
|
|
3269
|
+
...config.deployments.sandbox ?? {},
|
|
3270
|
+
runtimeClassName: config.deployments.sandbox?.runtimeClassName ?? sandbox.runtimeClassName
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
if (config.deployments.backend === "agent-sandbox" && sandbox?.enabled && sandbox.nodeSelector) {
|
|
3274
|
+
config.deployments.scheduling = {
|
|
3275
|
+
...config.deployments.scheduling ?? {},
|
|
3276
|
+
nodeSelector: {
|
|
3277
|
+
...sandbox.nodeSelector,
|
|
3278
|
+
...config.deployments.scheduling?.nodeSelector ?? {}
|
|
3279
|
+
}
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
function describeSandboxPreflightFailure(missing, warnings) {
|
|
3284
|
+
return [
|
|
3285
|
+
"agent-sandbox preflight failed.",
|
|
3286
|
+
missing.length > 0 ? `Missing: ${missing.join(", ")}.` : "",
|
|
3287
|
+
warnings.length > 0 ? `Warnings: ${warnings.join(", ")}.` : "",
|
|
3288
|
+
'Run "shadowob-cloud cluster apply --config cluster.json" to install/verify sandbox, or set deployments.backendPolicy="deployment-only" for fallback.'
|
|
3289
|
+
].filter(Boolean).join(" ");
|
|
3290
|
+
}
|
|
2669
3291
|
var DeployService = class {
|
|
2670
3292
|
constructor(configService, manifestService, k8s6, logger) {
|
|
2671
3293
|
this.configService = configService;
|
|
@@ -2685,10 +3307,13 @@ var DeployService = class {
|
|
|
2685
3307
|
});
|
|
2686
3308
|
const runtimeContext = normalizeDeploymentRuntimeContext(options.runtimeContext);
|
|
2687
3309
|
const effectiveEnv = buildEffectiveEnv(options.runtimeEnvVars, runtimeContext);
|
|
2688
|
-
if (!
|
|
3310
|
+
if (!existsSync17(filePath)) {
|
|
2689
3311
|
throw new Error(`Config file not found: ${filePath}`);
|
|
2690
3312
|
}
|
|
2691
3313
|
const kubeConfigPath = options.kubeConfigPath ?? (options.cluster ? loadKubeconfigPath(options.cluster) : void 0);
|
|
3314
|
+
if (kubeConfigPath) {
|
|
3315
|
+
assertReadableKubeconfigFile(kubeConfigPath);
|
|
3316
|
+
}
|
|
2692
3317
|
const k8sTargetSummary = summarizeK8sTarget(options, kubeConfigPath);
|
|
2693
3318
|
this.logger.info(k8sTargetSummary);
|
|
2694
3319
|
emit(`${k8sTargetSummary}
|
|
@@ -2701,6 +3326,7 @@ var DeployService = class {
|
|
|
2701
3326
|
await this.configService.parseFile(filePath),
|
|
2702
3327
|
runtimeContext
|
|
2703
3328
|
);
|
|
3329
|
+
applyManagedClusterDefaults(config, options.cluster);
|
|
2704
3330
|
const namespace = options.namespace ?? config.deployments?.namespace ?? "shadowob-cloud";
|
|
2705
3331
|
const stackName = resolveStackName(namespace, options.stack);
|
|
2706
3332
|
const agents = config.deployments?.agents ?? [];
|
|
@@ -2745,10 +3371,31 @@ var DeployService = class {
|
|
|
2745
3371
|
const usesPlugins = configUsesPlugins(config);
|
|
2746
3372
|
if (usesPlugins) await ensureBuiltInPluginsLoaded();
|
|
2747
3373
|
const resolved = await this.configService.resolve(config, configCwd, { env: effectiveEnv });
|
|
3374
|
+
if (resolved.deployments && isAgentSandboxBackend(resolved)) {
|
|
3375
|
+
const policy = workloadBackendPolicy(resolved);
|
|
3376
|
+
const preflight = this.k8s.checkAgentSandboxPreflight({
|
|
3377
|
+
kubeconfig: readKubeconfigForRuntimeWait(kubeConfigPath),
|
|
3378
|
+
runtimeClassNames: sandboxRuntimeClassNames(resolved)
|
|
3379
|
+
});
|
|
3380
|
+
if (!preflight.ok) {
|
|
3381
|
+
const message = describeSandboxPreflightFailure(preflight.missing, preflight.warnings);
|
|
3382
|
+
if (policy === "sandbox-preferred") {
|
|
3383
|
+
this.logger.warn(`${message} Falling back to Kubernetes Deployment.`);
|
|
3384
|
+
emit(`${message} Falling back to Kubernetes Deployment.
|
|
3385
|
+
`);
|
|
3386
|
+
resolved.deployments.backend = "deployment";
|
|
3387
|
+
resolved.deployments.backendPolicy = "deployment-only";
|
|
3388
|
+
} else {
|
|
3389
|
+
throw new Error(message);
|
|
3390
|
+
}
|
|
3391
|
+
} else if (preflight.warnings.length > 0) {
|
|
3392
|
+
this.logger.warn(`agent-sandbox preflight warnings: ${preflight.warnings.join(", ")}`);
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
2748
3395
|
if (usesPlugins) await ensureBuiltInPluginsLoaded();
|
|
2749
3396
|
if (!options.skipProvision) {
|
|
2750
3397
|
try {
|
|
2751
|
-
const { executePluginProvisions, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-
|
|
3398
|
+
const { executePluginProvisions, getPluginRegistry: getPluginRegistry2 } = await import("./plugins-UK2QWD6G.js");
|
|
2752
3399
|
for (const agent of agents) {
|
|
2753
3400
|
const provisionResults = await executePluginProvisions(
|
|
2754
3401
|
agent,
|
|
@@ -2763,6 +3410,9 @@ var DeployService = class {
|
|
|
2763
3410
|
for (const e of provisionResults.errors) {
|
|
2764
3411
|
this.logger.warn(`Plugin provision error (${e.pluginId}): ${e.error}`);
|
|
2765
3412
|
}
|
|
3413
|
+
throw new Error(
|
|
3414
|
+
`Plugin provisioning failed: ${provisionResults.errors.map((e) => `${e.pluginId}: ${e.error}`).join("; ")}`
|
|
3415
|
+
);
|
|
2766
3416
|
}
|
|
2767
3417
|
if (Object.keys(provisionResults.secrets).length > 0) {
|
|
2768
3418
|
agent.env = { ...agent.env ?? {}, ...provisionResults.secrets };
|
|
@@ -2786,7 +3436,10 @@ var DeployService = class {
|
|
|
2786
3436
|
await options.onProvisionState?.(merged);
|
|
2787
3437
|
}
|
|
2788
3438
|
}
|
|
2789
|
-
} catch {
|
|
3439
|
+
} catch (err) {
|
|
3440
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3441
|
+
this.logger.warn(`Plugin provisioning failed; aborting deploy: ${message}`);
|
|
3442
|
+
throw err;
|
|
2790
3443
|
}
|
|
2791
3444
|
}
|
|
2792
3445
|
const k8sShadowUrl = options.k8sShadowUrl ?? effectiveEnv.SHADOW_AGENT_SERVER_URL ?? options.shadowUrl ?? effectiveEnv.K8S_SHADOW_URL ?? effectiveEnv.SHADOW_SERVER_URL;
|
|
@@ -2859,11 +3512,11 @@ var DeployService = class {
|
|
|
2859
3512
|
emit("Lock canceled, retrying...\n");
|
|
2860
3513
|
}
|
|
2861
3514
|
} catch {
|
|
2862
|
-
const { join:
|
|
3515
|
+
const { join: join8 } = await import("path");
|
|
2863
3516
|
const { homedir: homedir6 } = await import("os");
|
|
2864
|
-
const { rmSync: rmSync4, existsSync:
|
|
2865
|
-
const lockDir =
|
|
2866
|
-
if (
|
|
3517
|
+
const { rmSync: rmSync4, existsSync: existsSync20 } = await import("fs");
|
|
3518
|
+
const lockDir = join8(homedir6(), ".shadowob", "pulumi", ".pulumi", "locks");
|
|
3519
|
+
if (existsSync20(lockDir)) {
|
|
2867
3520
|
try {
|
|
2868
3521
|
rmSync4(lockDir, { recursive: true });
|
|
2869
3522
|
this.logger.info("Lock files removed, retrying...");
|
|
@@ -2963,6 +3616,9 @@ var DeployService = class {
|
|
|
2963
3616
|
`Cannot destroy namespace "${namespace}" without a Pulumi config snapshot. Destroy must run through the deployment stack state.`
|
|
2964
3617
|
);
|
|
2965
3618
|
}
|
|
3619
|
+
if (options.kubeConfigPath) {
|
|
3620
|
+
assertReadableKubeconfigFile(options.kubeConfigPath);
|
|
3621
|
+
}
|
|
2966
3622
|
const stack = await this.k8s.getOrCreateStack({
|
|
2967
3623
|
stackName,
|
|
2968
3624
|
config: options.config,
|
|
@@ -2990,10 +3646,10 @@ var DeployService = class {
|
|
|
2990
3646
|
};
|
|
2991
3647
|
|
|
2992
3648
|
// src/services/deployment-runtime.service.ts
|
|
2993
|
-
import { createHash } from "crypto";
|
|
2994
|
-
import { existsSync as
|
|
2995
|
-
import { homedir as
|
|
2996
|
-
import { delimiter, join as
|
|
3649
|
+
import { createHash as createHash2 } from "crypto";
|
|
3650
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3651
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
|
|
3652
|
+
import { delimiter, join as join5 } from "path";
|
|
2997
3653
|
function extractKubeContext(kubeconfigYaml) {
|
|
2998
3654
|
const match = kubeconfigYaml.match(/current-context:\s*(\S+)/);
|
|
2999
3655
|
return match?.[1];
|
|
@@ -3030,24 +3686,24 @@ function normalizeRuntimeEnvVars2(envVars) {
|
|
|
3030
3686
|
return normalized;
|
|
3031
3687
|
}
|
|
3032
3688
|
function getStableRuntimeKubeconfigPath(kubeconfigYaml) {
|
|
3033
|
-
const runtimeDir =
|
|
3689
|
+
const runtimeDir = join5(homedir4(), ".shadowob", "kubeconfigs");
|
|
3034
3690
|
mkdirSync4(runtimeDir, { recursive: true });
|
|
3035
|
-
const hash =
|
|
3036
|
-
const kubeconfigPath =
|
|
3037
|
-
if (!
|
|
3691
|
+
const hash = createHash2("sha256").update(kubeconfigYaml).digest("hex");
|
|
3692
|
+
const kubeconfigPath = join5(runtimeDir, `${hash}.yaml`);
|
|
3693
|
+
if (!existsSync18(kubeconfigPath)) {
|
|
3038
3694
|
writeFileSync5(kubeconfigPath, kubeconfigYaml, { mode: 384 });
|
|
3039
3695
|
}
|
|
3040
3696
|
return kubeconfigPath;
|
|
3041
3697
|
}
|
|
3042
3698
|
function isContainerizedRuntime() {
|
|
3043
|
-
return process.env.SHADOW_CONTAINERIZED === "1" ||
|
|
3699
|
+
return process.env.SHADOW_CONTAINERIZED === "1" || existsSync18("/.dockerenv");
|
|
3044
3700
|
}
|
|
3045
3701
|
function getHostLocalRuntimeKubeconfigPaths() {
|
|
3046
3702
|
const candidates = [process.env.KUBECONFIG_HOST_PATH?.trim()];
|
|
3047
3703
|
if (!isContainerizedRuntime()) {
|
|
3048
3704
|
candidates.push(
|
|
3049
3705
|
...process.env.KUBECONFIG?.split(delimiter).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [],
|
|
3050
|
-
|
|
3706
|
+
defaultKubeconfigPath()
|
|
3051
3707
|
);
|
|
3052
3708
|
}
|
|
3053
3709
|
return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
|
|
@@ -3060,17 +3716,17 @@ function resolveAmbientRuntimeKubeconfigPath() {
|
|
|
3060
3716
|
const candidates = [
|
|
3061
3717
|
...process.env.KUBECONFIG?.split(delimiter).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [],
|
|
3062
3718
|
process.env.KUBECONFIG_HOST_PATH?.trim(),
|
|
3063
|
-
|
|
3719
|
+
defaultKubeconfigPath()
|
|
3064
3720
|
].filter((candidate) => Boolean(candidate));
|
|
3065
|
-
return candidates
|
|
3721
|
+
return findReadableKubeconfigPath(candidates, "Cloud SaaS Kubernetes kubeconfig");
|
|
3066
3722
|
}
|
|
3067
3723
|
var DeploymentRuntimeService = class {
|
|
3068
3724
|
constructor(deployService) {
|
|
3069
3725
|
this.deployService = deployService;
|
|
3070
3726
|
}
|
|
3071
3727
|
async deployFromSnapshot(options) {
|
|
3072
|
-
const configDir = mkdtempSync2(
|
|
3073
|
-
const configPath =
|
|
3728
|
+
const configDir = mkdtempSync2(join5(tmpdir2(), "sc-cfg-"));
|
|
3729
|
+
const configPath = join5(configDir, "shadowob-cloud.json");
|
|
3074
3730
|
writeFileSync5(configPath, JSON.stringify(options.configSnapshot, null, 2), "utf-8");
|
|
3075
3731
|
const {
|
|
3076
3732
|
configSnapshot: _configSnapshot,
|
|
@@ -3123,7 +3779,7 @@ var DeploymentRuntimeService = class {
|
|
|
3123
3779
|
let k8sContext;
|
|
3124
3780
|
let kubeConfigPath;
|
|
3125
3781
|
const activeKubeconfigPath = resolveAmbientRuntimeKubeconfigPath();
|
|
3126
|
-
const activeKubeconfig = cluster?.kubeconfig ? cluster.kubeconfig : activeKubeconfigPath ?
|
|
3782
|
+
const activeKubeconfig = cluster?.kubeconfig ? cluster.kubeconfig : activeKubeconfigPath ? readKubeconfigFile(activeKubeconfigPath, "Cloud SaaS Kubernetes kubeconfig") : void 0;
|
|
3127
3783
|
if (activeKubeconfig) {
|
|
3128
3784
|
const shouldRewriteLoopback = Boolean(
|
|
3129
3785
|
cluster?.kubeconfig || activeKubeconfigPath && !isHostLocalRuntimeKubeconfigPath(activeKubeconfigPath)
|
|
@@ -3454,23 +4110,23 @@ function rolloutUndoAll(namespace) {
|
|
|
3454
4110
|
|
|
3455
4111
|
// src/clients/kubectl-runtime.ts
|
|
3456
4112
|
import { execFileSync as execFileSync2, spawn as spawn4, spawnSync as spawnSync2 } from "child_process";
|
|
3457
|
-
import { existsSync as
|
|
3458
|
-
import {
|
|
3459
|
-
import { delimiter as delimiter2, join as
|
|
4113
|
+
import { existsSync as existsSync19, mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
4114
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
4115
|
+
import { delimiter as delimiter2, join as join6 } from "path";
|
|
3460
4116
|
function volumeSnapshotApiAvailableFromOutput2(output2) {
|
|
3461
4117
|
return output2.split(/\s+/).map((item) => item.trim()).some(
|
|
3462
4118
|
(resource) => resource === "volumesnapshots" || resource === "volumesnapshots.snapshot.storage.k8s.io"
|
|
3463
4119
|
);
|
|
3464
4120
|
}
|
|
3465
4121
|
function isContainerizedRuntime2() {
|
|
3466
|
-
return process.env.SHADOW_CONTAINERIZED === "1" ||
|
|
4122
|
+
return process.env.SHADOW_CONTAINERIZED === "1" || existsSync19("/.dockerenv");
|
|
3467
4123
|
}
|
|
3468
4124
|
function getHostLocalKubeconfigPaths() {
|
|
3469
4125
|
const candidates = [process.env.KUBECONFIG_HOST_PATH?.trim()];
|
|
3470
4126
|
if (!isContainerizedRuntime2()) {
|
|
3471
4127
|
candidates.push(
|
|
3472
4128
|
...process.env.KUBECONFIG?.split(delimiter2).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [],
|
|
3473
|
-
|
|
4129
|
+
defaultKubeconfigPath()
|
|
3474
4130
|
);
|
|
3475
4131
|
}
|
|
3476
4132
|
return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
|
|
@@ -3484,22 +4140,23 @@ function extractCurrentContext(kubeconfigYaml) {
|
|
|
3484
4140
|
}
|
|
3485
4141
|
function resolveAmbientKubeconfig() {
|
|
3486
4142
|
const envCandidates = process.env.KUBECONFIG?.split(delimiter2).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0) ?? [];
|
|
3487
|
-
const
|
|
4143
|
+
const candidates = [
|
|
3488
4144
|
...envCandidates,
|
|
3489
4145
|
process.env.KUBECONFIG_HOST_PATH?.trim(),
|
|
3490
|
-
|
|
3491
|
-
].filter((candidate) => Boolean(candidate))
|
|
4146
|
+
defaultKubeconfigPath()
|
|
4147
|
+
].filter((candidate) => Boolean(candidate));
|
|
4148
|
+
const kubeconfigPath = findReadableKubeconfigPath(candidates, "Kubernetes kubectl kubeconfig");
|
|
3492
4149
|
if (!kubeconfigPath) {
|
|
3493
4150
|
return void 0;
|
|
3494
4151
|
}
|
|
3495
4152
|
return {
|
|
3496
|
-
kubeconfig:
|
|
4153
|
+
kubeconfig: readKubeconfigFile(kubeconfigPath, "Kubernetes kubectl kubeconfig"),
|
|
3497
4154
|
shouldRewriteLoopback: !isHostLocalKubeconfigPath(kubeconfigPath)
|
|
3498
4155
|
};
|
|
3499
4156
|
}
|
|
3500
4157
|
function createTempKubeconfig(kubeconfig, includeAmbientContext = false, rewriteLoopback = true) {
|
|
3501
|
-
const dir = mkdtempSync3(
|
|
3502
|
-
const path =
|
|
4158
|
+
const dir = mkdtempSync3(join6(tmpdir3(), "sc-saas-kube-"));
|
|
4159
|
+
const path = join6(dir, "kubeconfig");
|
|
3503
4160
|
const rewritten = rewriteLoopback ? rewriteLoopbackKubeconfig(kubeconfig, process.env.KUBECONFIG_LOOPBACK_HOST) : kubeconfig;
|
|
3504
4161
|
writeFileSync6(path, rewritten, { mode: 384 });
|
|
3505
4162
|
const args = ["--kubeconfig", path];
|
|
@@ -3552,6 +4209,93 @@ async function withKubeconfigAsync(kubeconfig, fn) {
|
|
|
3552
4209
|
cleanup();
|
|
3553
4210
|
}
|
|
3554
4211
|
}
|
|
4212
|
+
function execKubectl(args, kubeconfig, timeout = 3e3) {
|
|
4213
|
+
return withKubeconfig(
|
|
4214
|
+
kubeconfig,
|
|
4215
|
+
(kubeArgs) => execFileSync2("kubectl", [...kubeArgs, ...args], {
|
|
4216
|
+
encoding: "utf-8",
|
|
4217
|
+
timeout,
|
|
4218
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4219
|
+
})
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
function tryExecKubectl(args, kubeconfig, timeout = 5e3) {
|
|
4223
|
+
try {
|
|
4224
|
+
return execKubectl(args, kubeconfig, timeout);
|
|
4225
|
+
} catch {
|
|
4226
|
+
return null;
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
function resourceOutputHas(output2, resourceName) {
|
|
4230
|
+
return Boolean(
|
|
4231
|
+
output2?.split(/\s+/).map((item) => item.trim()).some((item) => item === resourceName)
|
|
4232
|
+
);
|
|
4233
|
+
}
|
|
4234
|
+
function checkAgentSandboxPreflight(options) {
|
|
4235
|
+
const missing = [];
|
|
4236
|
+
const warnings = [];
|
|
4237
|
+
const kubeconfig = options?.kubeconfig;
|
|
4238
|
+
const extensionResources = tryExecKubectl(
|
|
4239
|
+
["api-resources", "--api-group", "extensions.agents.x-k8s.io", "-o", "name"],
|
|
4240
|
+
kubeconfig
|
|
4241
|
+
);
|
|
4242
|
+
if (!resourceOutputHas(extensionResources, "sandboxtemplates")) {
|
|
4243
|
+
missing.push("CRD sandboxtemplates.extensions.agents.x-k8s.io");
|
|
4244
|
+
}
|
|
4245
|
+
if (!resourceOutputHas(extensionResources, "sandboxclaims")) {
|
|
4246
|
+
missing.push("CRD sandboxclaims.extensions.agents.x-k8s.io");
|
|
4247
|
+
}
|
|
4248
|
+
const coreResources = tryExecKubectl(
|
|
4249
|
+
["api-resources", "--api-group", "agents.x-k8s.io", "-o", "name"],
|
|
4250
|
+
kubeconfig
|
|
4251
|
+
);
|
|
4252
|
+
if (!resourceOutputHas(coreResources, "sandboxes")) {
|
|
4253
|
+
missing.push("CRD sandboxes.agents.x-k8s.io");
|
|
4254
|
+
}
|
|
4255
|
+
const controllerOutput = tryExecKubectl(
|
|
4256
|
+
["-n", "agent-sandbox-system", "get", "deployment", "agent-sandbox-controller", "-o", "json"],
|
|
4257
|
+
kubeconfig,
|
|
4258
|
+
1e4
|
|
4259
|
+
);
|
|
4260
|
+
if (!controllerOutput) {
|
|
4261
|
+
missing.push("deployment/agent-sandbox-controller in namespace agent-sandbox-system");
|
|
4262
|
+
} else {
|
|
4263
|
+
try {
|
|
4264
|
+
const controller = JSON.parse(controllerOutput);
|
|
4265
|
+
const status = controller.status ?? {};
|
|
4266
|
+
if ((status.availableReplicas ?? 0) < 1) {
|
|
4267
|
+
missing.push("Ready agent-sandbox controller");
|
|
4268
|
+
}
|
|
4269
|
+
} catch {
|
|
4270
|
+
missing.push("Readable agent-sandbox controller status");
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
const runtimeClassNames = [
|
|
4274
|
+
...new Set(
|
|
4275
|
+
[options?.runtimeClassName, ...options?.runtimeClassNames ?? []].map((name) => name?.trim()).filter((name) => Boolean(name))
|
|
4276
|
+
)
|
|
4277
|
+
];
|
|
4278
|
+
for (const runtimeClassName of runtimeClassNames) {
|
|
4279
|
+
const runtimeClass = tryExecKubectl(["get", "runtimeclass", runtimeClassName], kubeconfig);
|
|
4280
|
+
if (!runtimeClass) {
|
|
4281
|
+
missing.push(`RuntimeClass ${runtimeClassName}`);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
const sandboxNodes = tryExecKubectl(
|
|
4285
|
+
["get", "nodes", "-l", "shadowob.com/sandbox-ready=true", "-o", "name"],
|
|
4286
|
+
kubeconfig
|
|
4287
|
+
);
|
|
4288
|
+
if (!sandboxNodes?.trim()) {
|
|
4289
|
+
warnings.push("No nodes are labeled shadowob.com/sandbox-ready=true");
|
|
4290
|
+
}
|
|
4291
|
+
return {
|
|
4292
|
+
ok: missing.length === 0,
|
|
4293
|
+
missing,
|
|
4294
|
+
warnings,
|
|
4295
|
+
runtimeClassName: runtimeClassNames[0],
|
|
4296
|
+
runtimeClassNames
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
3555
4299
|
function execKubectlAsync(args, kubeconfig, timeout = 3e3) {
|
|
3556
4300
|
return withKubeconfigAsync(
|
|
3557
4301
|
kubeconfig,
|
|
@@ -3669,7 +4413,7 @@ async function getPodReadyState(namespace, podName, kubeconfig) {
|
|
|
3669
4413
|
throw error;
|
|
3670
4414
|
}
|
|
3671
4415
|
}
|
|
3672
|
-
async function
|
|
4416
|
+
async function waitForAgentSandboxReady2(options) {
|
|
3673
4417
|
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
3674
4418
|
const intervalMs = options.intervalMs ?? 2e3;
|
|
3675
4419
|
const startedAt = Date.now();
|
|
@@ -3875,7 +4619,7 @@ async function restorePvcFromVolumeSnapshot(options) {
|
|
|
3875
4619
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3876
4620
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
3877
4621
|
import { homedir as homedir5 } from "os";
|
|
3878
|
-
import { delimiter as delimiter3, join as
|
|
4622
|
+
import { delimiter as delimiter3, join as join7 } from "path";
|
|
3879
4623
|
import * as automation from "@pulumi/pulumi/automation/index.js";
|
|
3880
4624
|
import { PulumiCommand } from "@pulumi/pulumi/automation/index.js";
|
|
3881
4625
|
|
|
@@ -4143,6 +4887,29 @@ function baseVolumes(configMapName) {
|
|
|
4143
4887
|
{ name: RUNNER_AGENTS_VOLUME_NAME, emptyDir: {} }
|
|
4144
4888
|
];
|
|
4145
4889
|
}
|
|
4890
|
+
function isAgentSandboxBackend2(config) {
|
|
4891
|
+
return (config.deployments?.backend ?? "agent-sandbox") === "agent-sandbox";
|
|
4892
|
+
}
|
|
4893
|
+
function hasKeys(value) {
|
|
4894
|
+
return Boolean(value && typeof value === "object" && Object.keys(value).length > 0);
|
|
4895
|
+
}
|
|
4896
|
+
function resolveSchedulingConfig(config, agent) {
|
|
4897
|
+
const defaults = isAgentSandboxBackend2(config) ? { nodeSelector: { "shadowob.com/sandbox-ready": "true" } } : {};
|
|
4898
|
+
const globalScheduling = config.deployments?.scheduling ?? {};
|
|
4899
|
+
const agentScheduling = agent.scheduling ?? {};
|
|
4900
|
+
const nodeSelector = {
|
|
4901
|
+
...defaults.nodeSelector ?? {},
|
|
4902
|
+
...globalScheduling.nodeSelector ?? {},
|
|
4903
|
+
...agentScheduling.nodeSelector ?? {}
|
|
4904
|
+
};
|
|
4905
|
+
const affinity = agentScheduling.affinity ?? globalScheduling.affinity;
|
|
4906
|
+
const tolerations = agentScheduling.tolerations ?? globalScheduling.tolerations;
|
|
4907
|
+
return {
|
|
4908
|
+
...Object.keys(nodeSelector).length > 0 ? { nodeSelector } : {},
|
|
4909
|
+
...hasKeys(affinity) ? { affinity } : {},
|
|
4910
|
+
...tolerations && tolerations.length > 0 ? { tolerations } : {}
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4146
4913
|
function buildAgentPodSpec(options) {
|
|
4147
4914
|
const runtime = getRuntime(options.agent.runtime);
|
|
4148
4915
|
const image = options.agent.image ?? runtime.defaultImage;
|
|
@@ -4237,6 +5004,7 @@ function buildAgentPodSpec(options) {
|
|
|
4237
5004
|
initContainers,
|
|
4238
5005
|
containers,
|
|
4239
5006
|
volumes,
|
|
5007
|
+
scheduling: resolveSchedulingConfig(options.config, options.agent),
|
|
4240
5008
|
pluginArtifacts
|
|
4241
5009
|
};
|
|
4242
5010
|
}
|
|
@@ -4334,6 +5102,9 @@ function createAgentDeployment(options) {
|
|
|
4334
5102
|
securityContext: buildSecurityContext(),
|
|
4335
5103
|
containers: pod.containers,
|
|
4336
5104
|
volumes: pod.volumes,
|
|
5105
|
+
nodeSelector: pod.scheduling.nodeSelector,
|
|
5106
|
+
affinity: pod.scheduling.affinity,
|
|
5107
|
+
tolerations: pod.scheduling.tolerations,
|
|
4337
5108
|
restartPolicy: "Always"
|
|
4338
5109
|
}
|
|
4339
5110
|
}
|
|
@@ -4539,6 +5310,9 @@ function buildAgentSandboxTemplateManifest(options) {
|
|
|
4539
5310
|
securityContext: buildSecurityContext(),
|
|
4540
5311
|
containers: options.pod.containers,
|
|
4541
5312
|
volumes: options.pod.volumes,
|
|
5313
|
+
nodeSelector: options.pod.scheduling.nodeSelector,
|
|
5314
|
+
affinity: options.pod.scheduling.affinity,
|
|
5315
|
+
tolerations: options.pod.scheduling.tolerations,
|
|
4542
5316
|
restartPolicy: "Always"
|
|
4543
5317
|
}
|
|
4544
5318
|
},
|
|
@@ -4626,18 +5400,18 @@ function createConfigResources(options) {
|
|
|
4626
5400
|
}
|
|
4627
5401
|
|
|
4628
5402
|
// src/infra/hash.ts
|
|
4629
|
-
import { createHash as
|
|
4630
|
-
function
|
|
5403
|
+
import { createHash as createHash3 } from "crypto";
|
|
5404
|
+
function stableStringify2(value) {
|
|
4631
5405
|
if (Array.isArray(value)) {
|
|
4632
|
-
return `[${value.map((item) =>
|
|
5406
|
+
return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
|
|
4633
5407
|
}
|
|
4634
5408
|
if (value && typeof value === "object") {
|
|
4635
|
-
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${
|
|
5409
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify2(item)}`).join(",")}}`;
|
|
4636
5410
|
}
|
|
4637
5411
|
return JSON.stringify(value);
|
|
4638
5412
|
}
|
|
4639
5413
|
function stableHash(value) {
|
|
4640
|
-
return
|
|
5414
|
+
return createHash3("sha256").update(stableStringify2(value)).digest("hex");
|
|
4641
5415
|
}
|
|
4642
5416
|
|
|
4643
5417
|
// src/infra/networking.ts
|
|
@@ -4737,6 +5511,9 @@ function classifyEnv(registrySecretEnv, mergedEnv) {
|
|
|
4737
5511
|
}
|
|
4738
5512
|
return { plainEnv, secretData };
|
|
4739
5513
|
}
|
|
5514
|
+
function omitEnvKeys(env, keys) {
|
|
5515
|
+
for (const key of keys) delete env[key];
|
|
5516
|
+
}
|
|
4740
5517
|
function runtimePackageEnvDefaults(options) {
|
|
4741
5518
|
const env = {};
|
|
4742
5519
|
if (!options.currentEnv.SHADOW_SLASH_COMMANDS_PATH) {
|
|
@@ -4759,6 +5536,7 @@ function buildAgentRuntimePackage(options) {
|
|
|
4759
5536
|
...agent.env ?? {},
|
|
4760
5537
|
...extraEnv ?? {}
|
|
4761
5538
|
};
|
|
5539
|
+
const runtimeEnvOmitKeys = collectPluginRuntimeEnvOmitKeys(agent, config, cwd, runtimeEnv);
|
|
4762
5540
|
const runtimeExtensions = collectPluginRuntimeExtensions(agent, config, cwd, runtimeEnv);
|
|
4763
5541
|
const mergedEnv = {
|
|
4764
5542
|
...collectPluginBuildEnvVars(agent, config, cwd, runtimeEnv),
|
|
@@ -4784,6 +5562,7 @@ function buildAgentRuntimePackage(options) {
|
|
|
4784
5562
|
if (runtimeArtifacts.provisionSecrets) {
|
|
4785
5563
|
Object.assign(mergedEnv, runtimeArtifacts.provisionSecrets);
|
|
4786
5564
|
}
|
|
5565
|
+
omitEnvKeys(mergedEnv, runtimeEnvOmitKeys);
|
|
4787
5566
|
const { plainEnv, secretData } = classifyEnv(registrySecretEnv, mergedEnv);
|
|
4788
5567
|
return {
|
|
4789
5568
|
runtimeKind: runtime.runtimeKind,
|
|
@@ -4796,10 +5575,9 @@ function buildAgentRuntimePackage(options) {
|
|
|
4796
5575
|
}
|
|
4797
5576
|
|
|
4798
5577
|
// src/infra/shared.ts
|
|
4799
|
-
import { readFileSync as readFileSync9 } from "fs";
|
|
4800
5578
|
import * as k8s5 from "@pulumi/kubernetes";
|
|
4801
5579
|
function createSharedResources(options) {
|
|
4802
|
-
const providerConfig = options.kubeConfigPath ? { kubeconfig:
|
|
5580
|
+
const providerConfig = options.kubeConfigPath ? { kubeconfig: readKubeconfigFile(options.kubeConfigPath) } : {
|
|
4803
5581
|
context: options.kubeContext ?? process.env.KUBECONFIG_CONTEXT ?? process.env.K8S_CONTEXT ?? "rancher-desktop"
|
|
4804
5582
|
};
|
|
4805
5583
|
const provider = new k8s5.Provider("k8s-provider", providerConfig);
|
|
@@ -5191,13 +5969,13 @@ function getNonEmptyEnv(name) {
|
|
|
5191
5969
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
5192
5970
|
}
|
|
5193
5971
|
function getDefaultStateDir() {
|
|
5194
|
-
return getNonEmptyEnv("PULUMI_BACKEND_URL") ? "" :
|
|
5972
|
+
return getNonEmptyEnv("PULUMI_BACKEND_URL") ? "" : join7(homedir5(), ".shadowob", "pulumi");
|
|
5195
5973
|
}
|
|
5196
5974
|
function resolvePulumiBackendUrl(stateDir) {
|
|
5197
5975
|
return getNonEmptyEnv("PULUMI_BACKEND_URL") ?? (stateDir ? `file://${stateDir}` : void 0);
|
|
5198
5976
|
}
|
|
5199
5977
|
function ensurePulumiCliOnPath(cliRoot) {
|
|
5200
|
-
const binDir =
|
|
5978
|
+
const binDir = join7(cliRoot, "bin");
|
|
5201
5979
|
const currentPath = process.env.PATH ?? "";
|
|
5202
5980
|
const parts = currentPath.split(delimiter3).filter(Boolean);
|
|
5203
5981
|
if (!parts.includes(binDir)) {
|
|
@@ -5227,8 +6005,8 @@ ${stderr}` : ""}`
|
|
|
5227
6005
|
}
|
|
5228
6006
|
}
|
|
5229
6007
|
async function getOrCreateStack(options) {
|
|
5230
|
-
const cliRoot =
|
|
5231
|
-
const pulumiHome =
|
|
6008
|
+
const cliRoot = join7(homedir5(), ".shadowob", "pulumi", "cli");
|
|
6009
|
+
const pulumiHome = join7(homedir5(), ".shadowob", "pulumi", "home");
|
|
5232
6010
|
const infraOpts = {
|
|
5233
6011
|
config: options.config,
|
|
5234
6012
|
namespace: options.namespace,
|
|
@@ -5419,11 +6197,14 @@ var K8sService = class {
|
|
|
5419
6197
|
});
|
|
5420
6198
|
}
|
|
5421
6199
|
async waitForAgentSandboxReady(options) {
|
|
5422
|
-
return
|
|
6200
|
+
return waitForAgentSandboxReady2(options);
|
|
5423
6201
|
}
|
|
5424
6202
|
async waitForAgentSandboxPaused(options) {
|
|
5425
6203
|
return waitForAgentSandboxPaused(options);
|
|
5426
6204
|
}
|
|
6205
|
+
checkAgentSandboxPreflight(options) {
|
|
6206
|
+
return checkAgentSandboxPreflight(options);
|
|
6207
|
+
}
|
|
5427
6208
|
async restorePvcFromVolumeSnapshot(options) {
|
|
5428
6209
|
await restorePvcFromVolumeSnapshot(options);
|
|
5429
6210
|
}
|