thinkncollab-shell 2.0.0-beta.1 → 2.0.0-beta.2
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.
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
|
|
5
5
|
* UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
|
|
6
6
|
*/
|
|
7
|
-
"use strict";const fs=require("fs"),path=require("path"),chalk=require("chalk"),{spawn}=require("child_process"),{dockPaths}=require("../../dock/paths"),{request,ping}=require("../../dock/client"),{readPid,pidAlive}=require("../../dock/daemon"),{PHASE}=require("../../dock/version"),sleep=e=>new Promise(o=>setTimeout(o,e));async function setupCmd(e){const{setup:o}=require("../../dock/setup"),s=o=>{const s=e.indexOf(o);return-1===s?void 0:e[s+1]};try{const t=await o({from:s("--from"),baseUrl:s("--url"),force:e.includes("--force"),log:e=>console.log(chalk.dim(` ${e}`))});return console.log(chalk.green(`✓ dock runtime ${t} installed for ${process.platform}/${process.arch}`)),console.log(chalk.dim(" next: thinknsh dock start")),!0}catch(e){return console.log(chalk.red(`✗ setup failed: ${e.message}`)),!1}}async function start(e){if(await ping(e.socket))return console.log(chalk.green(`dock is already running (pid ${readPid(e.pidFile)||"?"})`)),!0;fs.mkdirSync(e.home,{recursive:!0,mode:448});const o=fs.openSync(e.logFile,"a"),s=spawn(process.execPath,[path.join(__dirname,"..","..","dock","daemon-main.js")],{detached:!0,stdio:["ignore",o,o],env:process.env});s.unref(),fs.closeSync(o);for(let o=0;o<50;o++){if(await ping(e.socket))return console.log(chalk.green(`✓ dock started (pid ${s.pid})`)),console.log(chalk.dim(` socket: ${e.socket}`)),console.log(chalk.dim(" try: thinknsh run --rm alpine echo hello")),!0;if(null!==s.exitCode)break;await sleep(100)}console.log(chalk.red(`✗ dock failed to start. Last log lines (${e.logFile}):`));try{console.log(chalk.dim(fs.readFileSync(e.logFile,"utf8").trim().split("\n").slice(-5).join("\n")))}catch(e){}return!1}async function stop(e){const o=readPid(e.pidFile);if(!o||!pidAlive(o))return console.log(chalk.dim("dock is not running")),!0;process.kill(o,"SIGTERM");for(let e=0;e<50;e++){if(!pidAlive(o))return console.log(chalk.green(`✓ dock stopped (pid ${o})`)),!0;await sleep(100)}return console.log(chalk.red(`✗ dock (pid ${o}) did not stop within 5s`)),!1}async function status(e){if(!await ping(e.socket))return console.log(`${chalk.red("●")} dock is not running ${chalk.dim("start it with: thinknsh dock start")}`),!1;const[o,s]=await Promise.all([request(e.socket,"GET","/version"),request(e.socket,"GET","/info")]);if(console.log(`${chalk.green("●")} dock ${o.body.Version} running (pid ${readPid(e.pidFile)||"?"})`),console.log(` socket ${e.socket}`),console.log(` API version ${o.body.ApiVersion} (min ${o.body.MinAPIVersion})`),console.log(` phase ${PHASE}`),console.log(` containers ${s.body.Containers} (${s.body.ContainersRunning} running)`),console.log(` images ${s.body.Images}`),"darwin"===process.platform){const e=require("../../dock/setup/autostart").installed();console.log(` autostart ${e?"on (starts when you log in)":chalk.dim("off (turn on: thinknsh dock autostart)")}`)}for(const e of s.body.Warnings||[])console.log(chalk.yellow(` ${e}`));return!0}async function compat(e,o){const{runCompat:s,toMarkdown:t}=require("../../dock/compat/runner");let n=e.socket,r=null;if(!await ping(n)){const e=require("os"),{DockDaemon:o}=require("../../dock/daemon"),s=fs.mkdtempSync(path.join(e.tmpdir(),"tnc-dock-check-"));r={daemon:new o({home:s}),home:s},await r.daemon.start(),n=r.daemon.paths.socket}try{const a=await s({socketPath:n,home:r?r.home:e.home}),c={pass:chalk.green,fail:chalk.red,pending:chalk.dim,early:chalk.cyan};console.log(chalk.bold(`\ndock check — phase ${a.phase}${r?chalk.dim(" (temporary daemon)"):""}\n`));for(const e of a.results){const o=c[e.result](e.result.padEnd(8));console.log(` ${o} ${e.phase} ${e.id.padEnd(20)} ${chalk.dim(e.detail)}`)}const i=a.tally;console.log(`\n ${chalk.green(`${i.pass||0} pass`)} ${chalk.red(`${i.fail||0} fail`)} ${i.pending||0} pending ${i.early||0} early`);const l=o.indexOf("--md");return-1!==l&&o[l+1]&&(fs.writeFileSync(o[l+1],t(a)),console.log(chalk.dim(` report written to ${o[l+1]}`))),a.ok}finally{r&&(await r.daemon.stop(),fs.rmSync(r.home,{recursive:!0,force:!0}))}}async function selftest(e){if(!await ping(e.socket)&&!await start(e))return!1;console.log(chalk.bold(`\ndock selftest — ${process.platform}/${process.arch}\n`));const{runSelftest:o}=require("../../dock/selftest"),{report:s,file:t}=await o({log:e=>console.log(` ${e.startsWith("✓")?chalk.green(e):chalk.red(e)}`)}),n=s.passed===s.total?chalk.green:chalk.red;return console.log(n(`\n ${s.passed}/${s.total} checks passed`)),console.log(chalk.dim(` report: ${t} (share this file)\n`)),s.passed===s.total}function parseSize(e){const o=/^(\d+(?:\.\d+)?)\s*([mg]?)b?$/i.exec(String(e||"").trim());if(!o)throw new Error(`invalid size "${e}" (examples: 4g, 6144m)`);return Math.round(parseFloat(o[1])*("m"===o[2].toLowerCase()?1:1024))}async function configCmd(e){const{readConfig:o,writeConfig:s,effective:t,configFile:n}=require("../../dock/config"),r=o();let a=!1;for(let o=0;o<e.length;o++){const s=e[o],t=()=>{if(o+1>=e.length)throw new Error(`${s} needs a value`);return e[++o]};if("--cpus"===s)r.cpus=Math.max(1,parseInt(t(),10)),a=!0;else if("--memory"===s||"-m"===s)r.memoryMB=parseSize(t()),a=!0;else if("--disk-size"===s)r.diskGB=parseInt(t(),10),a=!0;else if("--share"===s){const e=path.resolve(t().replace(/^~(?=\/|$)/,require("os").homedir()));if(!fs.existsSync(e)||!fs.statSync(e).isDirectory())throw new Error(`${e} is not a folder`);r.shares=[...new Set([...r.shares||[],e])],a=!0}else if("--unshare"===s){const e=path.resolve(t());r.shares=(r.shares||[]).filter(o=>o!==e),a=!0}else if("--publish-address"===s){const e=t();if(!require("net").isIP(e))throw new Error(`--publish-address needs an IP address (127.0.0.1, 0.0.0.0 …), not "${e}"`);r.publishAddress=e,a=!0}else{if("--reset"!==s)throw new Error(`unknown option ${s} (--cpus N, --memory 8g, --disk-size GB, --share PATH, --unshare PATH, --publish-address IP, --reset)`);for(const e of Object.keys(r))delete r[e];a=!0}}a&&s(r);const c=t(),i=(e,s)=>void 0!==o()[e]?s:`${s} ${chalk.dim("(default)")}`;console.log(` cpus ${i("cpus",c.cpus)}`),console.log(` memory ${i("memoryMB",`${c.memoryMB} MB`)} ${chalk.dim("a ceiling: macOS backs only what containers use")}`),console.log(` disk size ${i("diskGB",`${c.diskGB} GB`)} ${chalk.dim("used when the data disk is created")}`),console.log(` shared ${[require("os").homedir(),"/private/tmp","/private/var/folders","/Volumes",...c.shares].join(", ")}`),console.log(` ports ${i("publishAddress",c.publishAddress)} ${chalk.dim("127.0.0.1"===c.publishAddress?"-p without an address: this machine only":"-p without an address: open to the network")}`),console.log(chalk.dim(` file: ${n()}`));const l=e.filter(e=>e.startsWith("-")&&"--publish-address"!==e);return a&&l.length?console.log(chalk.yellow(" applies when the Linux VM next boots: thinknsh dock vm-restart (stops running containers)")):a&&console.log(chalk.dim(" ports: applies to containers started from now on")),!0}async function migrateCmd(e,o){const{migrate:s}=require("../../dock/setup/migrate"),t=e=>{const s=o.indexOf(e);return-1===s?void 0:o[s+1]};if(!await ping(e.socket))return console.log(chalk.red("✗ dock is not running. Start it with: thinknsh dock start")),!1;try{const n=await s({from:t("--from"),to:`unix://${e.socket}`,images:o.includes("--images"),volumes:o.includes("--volumes"),dryRun:o.includes("--dry-run"),overwrite:o.includes("--overwrite"),log:e=>console.log(chalk.dim(` ${e}`))}),r=(e,o)=>o?`${e}: ${o.moved} copied, ${o.skipped} already here`:null;return console.log(chalk.green(`✓ ${[r("images",n.images),r("volumes",n.volumes)].filter(Boolean).join("; ")}`)),console.log(chalk.dim(" containers are not moved: recreate them here (thinknsh compose up / thinknsh run)")),!0}catch(e){return console.log(chalk.red(`✗ migrate failed: ${e.message}`)),!1}}async function uiCmd(e,o){const{startUi:s}=require("../../dock/ui/server");if(!await ping(e.socket))return console.log(chalk.red("✗ dock is not running. Start it with: thinknsh dock start")),!1;const t=await s({socketPath:e.socket,port:Number((()=>{const e=o.indexOf("--port");return-1===e?void 0:o[e+1]})())||0});if(console.log(chalk.green("✓ dock dashboard")),console.log(` ${t.url}`),console.log(chalk.dim(" the link carries this session's token; Ctrl-C stops the dashboard")),!o.includes("--no-open")){const e="darwin"===process.platform?["open",[t.url]]:"win32"===process.platform?["cmd",["/c","start","",t.url]]:["xdg-open",[t.url]];try{spawn(e[0],e[1],{stdio:"ignore",detached:!0}).on("error",()=>{}).unref()}catch(e){}}return await new Promise(e=>{const o=()=>{t.close().then(e)};process.once("SIGINT",o),process.once("SIGTERM",o)}),!0}async function useCmd(e,o){const{use:s}=require("../../dock/setup/integrate");try{for(const t of s(e.socket,{off:o.includes("--off"),system:o.includes("--system")}))console.log(` ${t}`);return o.includes("--off")||console.log(chalk.green("✓ tools built for other engines now use dock")+chalk.dim(" (undo: thinknsh dock use --off)")),!0}catch(e){return console.log(chalk.red(`✗ ${e.message}`)),!1}}async function vmRestart(e,o){if(await ping(e.socket)){const s=((await request(e.socket,"GET","/containers/json")).body||[]).map(e=>e.Names[0].slice(1));if(s.length&&!o.includes("--force"))return console.log(chalk.yellow(` running containers would stop: ${s.join(", ")}`)),console.log(chalk.dim(" stop them first, or pass --force")),!1}if(await stop(e),"darwin"===process.platform){const{spawnSync:e}=require("child_process");e(path.join(require("os").homedir(),".tnc","bin","tnc-vz"),["stop"],{stdio:"ignore"})}else if("win32"===process.platform){const{spawnSync:e}=require("child_process");e("wsl.exe",["--terminate",require("../../dock/setup").WSL_DISTRO],{stdio:"ignore"})}const s=await start(e);return s&&console.log(chalk.dim(" the Linux VM boots with the new settings on the next command that needs it")),s}function appCmd(e,o){try{const s=require("../../dock/setup/app").app({off:o.includes("--off"),home:e.home,socket:e.socket,cli:process.argv[1],version:require("../../dock/setup").BUNDLE_VERSION,log:e=>console.log(chalk.dim(` ${e}`))});for(const e of s)console.log(` ${e}`);return!0}catch(e){return console.log(chalk.red(`✗ ${e.message}`)),!1}}function autostartCmd(e,o){try{const s=require("../../dock/setup/autostart").autostart({off:o.includes("--off"),cli:process.argv[1],logFile:e.logFile});for(const e of s)console.log(` ${e}`);return!0}catch(e){return console.log(chalk.red(`✗ ${e.message}`)),!1}}async function foreground(){require("../../dock/daemon-main"),await new Promise(()=>{})}module.exports={name:"dock",description:"dock: ThinkNCollab container engine daemon (TNC Engine API)",usage:"thinknsh dock <setup|start|stop|status|check|selftest|config|use|env|autostart|app|vm-restart|migrate|ui|daemon>",requiresAuth:!1,examples:["thinknsh dock setup","thinknsh dock start","thinknsh dock selftest","thinknsh dock check --md check.md","thinknsh dock config --memory 8g --cpus 6","thinknsh dock use","thinknsh dock autostart","thinknsh dock app"],async execute(e=[]){const o=(e[0]||"status").toLowerCase(),s=dockPaths();let t=!0;switch(o){case"setup":t=await setupCmd(e.slice(1));break;case"start":t=await start(s);break;case"stop":t=await stop(s);break;case"status":t=await status(s);break;case"check":t=await compat(s,e.slice(1));break;case"selftest":t=await selftest(s);break;case"daemon":await foreground();break;case"config":try{t=await configCmd(e.slice(1))}catch(e){console.log(chalk.red(`✗ ${e.message}`)),t=!1}break;case"use":t=await useCmd(s,e.slice(1));break;case"env":console.log(require("../../dock/setup/integrate").env(s.socket));break;case"vm-restart":t=await vmRestart(s,e.slice(1));break;case"migrate":t=await migrateCmd(s,e.slice(1));break;case"ui":t=await uiCmd(s,e.slice(1));break;case"autostart":t=autostartCmd(s,e.slice(1));break;case"app":t=appCmd(s,e.slice(1));break;default:console.log(chalk.yellow(`unknown subcommand "${o}". Usage: ${module.exports.usage}`)),t=!1}t||(process.exitCode=1)}};
|
|
7
|
+
"use strict";const fs=require("fs"),path=require("path"),chalk=require("chalk"),{spawn}=require("child_process"),{dockPaths}=require("../../dock/paths"),{request,ping}=require("../../dock/client"),{readPid,pidAlive}=require("../../dock/daemon"),{PHASE}=require("../../dock/version"),sleep=e=>new Promise(o=>setTimeout(o,e));async function setupCmd(e){const{setup:o}=require("../../dock/setup"),s=o=>{const s=e.indexOf(o);return-1===s?void 0:e[s+1]};try{const t=await o({from:s("--from"),baseUrl:s("--url"),force:e.includes("--force"),log:e=>console.log(chalk.dim(` ${e}`))});return console.log(chalk.green(`✓ dock runtime ${t} installed for ${process.platform}/${process.arch}`)),console.log(chalk.dim(" next: thinknsh dock start")),!0}catch(e){return console.log(chalk.red(`✗ setup failed: ${e.message}`)),!1}}async function start(e){if(await ping(e.socket))return console.log(chalk.green(`dock is already running (pid ${readPid(e.pidFile)||"?"})`)),!0;fs.mkdirSync(e.home,{recursive:!0,mode:448});const o=fs.openSync(e.logFile,"a"),s=[];try{require("node:sqlite")}catch(e){s.push("--experimental-sqlite")}const t=spawn(process.execPath,[...s,path.join(__dirname,"..","..","dock","daemon-main.js")],{detached:!0,stdio:["ignore",o,o],env:process.env});t.unref(),fs.closeSync(o);for(let o=0;o<50;o++){if(await ping(e.socket))return console.log(chalk.green(`✓ dock started (pid ${t.pid})`)),console.log(chalk.dim(` socket: ${e.socket}`)),console.log(chalk.dim(" try: thinknsh run --rm alpine echo hello")),!0;if(null!==t.exitCode)break;await sleep(100)}console.log(chalk.red(`✗ dock failed to start. Last log lines (${e.logFile}):`));try{console.log(chalk.dim(fs.readFileSync(e.logFile,"utf8").trim().split("\n").slice(-5).join("\n")))}catch(e){}return!1}async function stop(e){const o=readPid(e.pidFile);if(!o||!pidAlive(o))return console.log(chalk.dim("dock is not running")),!0;process.kill(o,"SIGTERM");for(let e=0;e<50;e++){if(!pidAlive(o))return console.log(chalk.green(`✓ dock stopped (pid ${o})`)),!0;await sleep(100)}return console.log(chalk.red(`✗ dock (pid ${o}) did not stop within 5s`)),!1}async function status(e){if(!await ping(e.socket))return console.log(`${chalk.red("●")} dock is not running ${chalk.dim("start it with: thinknsh dock start")}`),!1;const[o,s]=await Promise.all([request(e.socket,"GET","/version"),request(e.socket,"GET","/info")]);if(console.log(`${chalk.green("●")} dock ${o.body.Version} running (pid ${readPid(e.pidFile)||"?"})`),console.log(` socket ${e.socket}`),console.log(` API version ${o.body.ApiVersion} (min ${o.body.MinAPIVersion})`),console.log(` phase ${PHASE}`),console.log(` containers ${s.body.Containers} (${s.body.ContainersRunning} running)`),console.log(` images ${s.body.Images}`),"darwin"===process.platform){const e=require("../../dock/setup/autostart").installed();console.log(` autostart ${e?"on (starts when you log in)":chalk.dim("off (turn on: thinknsh dock autostart)")}`)}for(const e of s.body.Warnings||[])console.log(chalk.yellow(` ${e}`));return!0}async function compat(e,o){const{runCompat:s,toMarkdown:t}=require("../../dock/compat/runner");let n=e.socket,r=null;if(!await ping(n)){const e=require("os"),{DockDaemon:o}=require("../../dock/daemon"),s=fs.mkdtempSync(path.join(e.tmpdir(),"tnc-dock-check-"));r={daemon:new o({home:s}),home:s},await r.daemon.start(),n=r.daemon.paths.socket}try{const a=await s({socketPath:n,home:r?r.home:e.home}),c={pass:chalk.green,fail:chalk.red,pending:chalk.dim,early:chalk.cyan};console.log(chalk.bold(`\ndock check — phase ${a.phase}${r?chalk.dim(" (temporary daemon)"):""}\n`));for(const e of a.results){const o=c[e.result](e.result.padEnd(8));console.log(` ${o} ${e.phase} ${e.id.padEnd(20)} ${chalk.dim(e.detail)}`)}const i=a.tally;console.log(`\n ${chalk.green(`${i.pass||0} pass`)} ${chalk.red(`${i.fail||0} fail`)} ${i.pending||0} pending ${i.early||0} early`);const l=o.indexOf("--md");return-1!==l&&o[l+1]&&(fs.writeFileSync(o[l+1],t(a)),console.log(chalk.dim(` report written to ${o[l+1]}`))),a.ok}finally{r&&(await r.daemon.stop(),fs.rmSync(r.home,{recursive:!0,force:!0}))}}async function selftest(e){if(!await ping(e.socket)&&!await start(e))return!1;console.log(chalk.bold(`\ndock selftest — ${process.platform}/${process.arch}\n`));const{runSelftest:o}=require("../../dock/selftest"),{report:s,file:t}=await o({log:e=>console.log(` ${e.startsWith("✓")?chalk.green(e):chalk.red(e)}`)}),n=s.passed===s.total?chalk.green:chalk.red;return console.log(n(`\n ${s.passed}/${s.total} checks passed`)),console.log(chalk.dim(` report: ${t} (share this file)\n`)),s.passed===s.total}function parseSize(e){const o=/^(\d+(?:\.\d+)?)\s*([mg]?)b?$/i.exec(String(e||"").trim());if(!o)throw new Error(`invalid size "${e}" (examples: 4g, 6144m)`);return Math.round(parseFloat(o[1])*("m"===o[2].toLowerCase()?1:1024))}async function configCmd(e){const{readConfig:o,writeConfig:s,effective:t,configFile:n}=require("../../dock/config"),r=o();let a=!1;for(let o=0;o<e.length;o++){const s=e[o],t=()=>{if(o+1>=e.length)throw new Error(`${s} needs a value`);return e[++o]};if("--cpus"===s)r.cpus=Math.max(1,parseInt(t(),10)),a=!0;else if("--memory"===s||"-m"===s)r.memoryMB=parseSize(t()),a=!0;else if("--disk-size"===s)r.diskGB=parseInt(t(),10),a=!0;else if("--share"===s){const e=path.resolve(t().replace(/^~(?=\/|$)/,require("os").homedir()));if(!fs.existsSync(e)||!fs.statSync(e).isDirectory())throw new Error(`${e} is not a folder`);r.shares=[...new Set([...r.shares||[],e])],a=!0}else if("--unshare"===s){const e=path.resolve(t());r.shares=(r.shares||[]).filter(o=>o!==e),a=!0}else if("--publish-address"===s){const e=t();if(!require("net").isIP(e))throw new Error(`--publish-address needs an IP address (127.0.0.1, 0.0.0.0 …), not "${e}"`);r.publishAddress=e,a=!0}else{if("--reset"!==s)throw new Error(`unknown option ${s} (--cpus N, --memory 8g, --disk-size GB, --share PATH, --unshare PATH, --publish-address IP, --reset)`);for(const e of Object.keys(r))delete r[e];a=!0}}a&&s(r);const c=t(),i=(e,s)=>void 0!==o()[e]?s:`${s} ${chalk.dim("(default)")}`;console.log(` cpus ${i("cpus",c.cpus)}`),console.log(` memory ${i("memoryMB",`${c.memoryMB} MB`)} ${chalk.dim("a ceiling: macOS backs only what containers use")}`),console.log(` disk size ${i("diskGB",`${c.diskGB} GB`)} ${chalk.dim("used when the data disk is created")}`),console.log(` shared ${[require("os").homedir(),"/private/tmp","/private/var/folders","/Volumes",...c.shares].join(", ")}`),console.log(` ports ${i("publishAddress",c.publishAddress)} ${chalk.dim("127.0.0.1"===c.publishAddress?"-p without an address: this machine only":"-p without an address: open to the network")}`),console.log(chalk.dim(` file: ${n()}`));const l=e.filter(e=>e.startsWith("-")&&"--publish-address"!==e);return a&&l.length?console.log(chalk.yellow(" applies when the Linux VM next boots: thinknsh dock vm-restart (stops running containers)")):a&&console.log(chalk.dim(" ports: applies to containers started from now on")),!0}async function migrateCmd(e,o){const{migrate:s}=require("../../dock/setup/migrate"),t=e=>{const s=o.indexOf(e);return-1===s?void 0:o[s+1]};if(!await ping(e.socket))return console.log(chalk.red("✗ dock is not running. Start it with: thinknsh dock start")),!1;try{const n=await s({from:t("--from"),to:`unix://${e.socket}`,images:o.includes("--images"),volumes:o.includes("--volumes"),dryRun:o.includes("--dry-run"),overwrite:o.includes("--overwrite"),log:e=>console.log(chalk.dim(` ${e}`))}),r=(e,o)=>o?`${e}: ${o.moved} copied, ${o.skipped} already here`:null;return console.log(chalk.green(`✓ ${[r("images",n.images),r("volumes",n.volumes)].filter(Boolean).join("; ")}`)),console.log(chalk.dim(" containers are not moved: recreate them here (thinknsh compose up / thinknsh run)")),!0}catch(e){return console.log(chalk.red(`✗ migrate failed: ${e.message}`)),!1}}async function uiCmd(e,o){const{startUi:s}=require("../../dock/ui/server");if(!await ping(e.socket))return console.log(chalk.red("✗ dock is not running. Start it with: thinknsh dock start")),!1;const t=await s({socketPath:e.socket,port:Number((()=>{const e=o.indexOf("--port");return-1===e?void 0:o[e+1]})())||0});if(console.log(chalk.green("✓ dock dashboard")),console.log(` ${t.url}`),console.log(chalk.dim(" the link carries this session's token; Ctrl-C stops the dashboard")),!o.includes("--no-open")){const e="darwin"===process.platform?["open",[t.url]]:"win32"===process.platform?["cmd",["/c","start","",t.url]]:["xdg-open",[t.url]];try{spawn(e[0],e[1],{stdio:"ignore",detached:!0}).on("error",()=>{}).unref()}catch(e){}}return await new Promise(e=>{const o=()=>{t.close().then(e)};process.once("SIGINT",o),process.once("SIGTERM",o)}),!0}async function useCmd(e,o){const{use:s}=require("../../dock/setup/integrate");try{for(const t of s(e.socket,{off:o.includes("--off"),system:o.includes("--system")}))console.log(` ${t}`);return o.includes("--off")||console.log(chalk.green("✓ tools built for other engines now use dock")+chalk.dim(" (undo: thinknsh dock use --off)")),!0}catch(e){return console.log(chalk.red(`✗ ${e.message}`)),!1}}async function vmRestart(e,o){if(await ping(e.socket)){const s=((await request(e.socket,"GET","/containers/json")).body||[]).map(e=>e.Names[0].slice(1));if(s.length&&!o.includes("--force"))return console.log(chalk.yellow(` running containers would stop: ${s.join(", ")}`)),console.log(chalk.dim(" stop them first, or pass --force")),!1}if(await stop(e),"darwin"===process.platform){const{spawnSync:e}=require("child_process");e(path.join(require("os").homedir(),".tnc","bin","tnc-vz"),["stop"],{stdio:"ignore"})}else if("win32"===process.platform){const{spawnSync:e}=require("child_process");e("wsl.exe",["--terminate",require("../../dock/setup").WSL_DISTRO],{stdio:"ignore"})}const s=await start(e);return s&&console.log(chalk.dim(" the Linux VM boots with the new settings on the next command that needs it")),s}function appCmd(e,o){try{const s=require("../../dock/setup/app").app({off:o.includes("--off"),home:e.home,socket:e.socket,cli:process.argv[1],version:require("../../dock/setup").BUNDLE_VERSION,log:e=>console.log(chalk.dim(` ${e}`))});for(const e of s)console.log(` ${e}`);return!0}catch(e){return console.log(chalk.red(`✗ ${e.message}`)),!1}}function autostartCmd(e,o){try{const s=require("../../dock/setup/autostart").autostart({off:o.includes("--off"),cli:process.argv[1],logFile:e.logFile});for(const e of s)console.log(` ${e}`);return!0}catch(e){return console.log(chalk.red(`✗ ${e.message}`)),!1}}async function foreground(){require("../../dock/daemon-main"),await new Promise(()=>{})}module.exports={name:"dock",description:"dock: ThinkNCollab container engine daemon (TNC Engine API)",usage:"thinknsh dock <setup|start|stop|status|check|selftest|config|use|env|autostart|app|vm-restart|migrate|ui|daemon>",requiresAuth:!1,examples:["thinknsh dock setup","thinknsh dock start","thinknsh dock selftest","thinknsh dock check --md check.md","thinknsh dock config --memory 8g --cpus 6","thinknsh dock use","thinknsh dock autostart","thinknsh dock app"],async execute(e=[]){const o=(e[0]||"status").toLowerCase(),s=dockPaths();let t=!0;switch(o){case"setup":t=await setupCmd(e.slice(1));break;case"start":t=await start(s);break;case"stop":t=await stop(s);break;case"status":t=await status(s);break;case"check":t=await compat(s,e.slice(1));break;case"selftest":t=await selftest(s);break;case"daemon":await foreground();break;case"config":try{t=await configCmd(e.slice(1))}catch(e){console.log(chalk.red(`✗ ${e.message}`)),t=!1}break;case"use":t=await useCmd(s,e.slice(1));break;case"env":console.log(require("../../dock/setup/integrate").env(s.socket));break;case"vm-restart":t=await vmRestart(s,e.slice(1));break;case"migrate":t=await migrateCmd(s,e.slice(1));break;case"ui":t=await uiCmd(s,e.slice(1));break;case"autostart":t=autostartCmd(s,e.slice(1));break;case"app":t=appCmd(s,e.slice(1));break;default:console.log(chalk.yellow(`unknown subcommand "${o}". Usage: ${module.exports.usage}`)),t=!1}t||(process.exitCode=1)}};
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
|
|
5
5
|
* UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
|
|
6
6
|
*/
|
|
7
|
-
"use strict";const fs=require("fs"),os=require("os"),path=require("path"),crypto=require("crypto"),{spawnSync}=require("child_process"),TNC_DIR=path.join(os.homedir(),".tnc"),BUNDLE_VERSION="0.2.2",DEFAULT_BASE_URL=process.env.TNC_DOCK_BUNDLE_URL||"https://thinkncollab.com/downloads/dock/0.2.2",WSL_DISTRO="tnc-dock",linuxArch=(n=process.arch)=>({arm64:"aarch64",x64:"x86_64"}[n]);function setupError(n){const t=new Error(n);return t.setup=!0,t}function sha256File(n){const t=crypto.createHash("sha256"),e=fs.openSync(n,"r"),r=Buffer.alloc(1<<20);let i;for(;(i=fs.readSync(e,r,0,r.length,null))>0;)t.update(r.subarray(0,i));return fs.closeSync(e),t.digest("hex")}const OPTIONAL_FILES=/^(tnc-busybox-linux-|tnc-menubar-)/;function wantedFiles(n=process.platform,t=process.arch){const e=linuxArch(t);if(!e)throw setupError(`unsupported CPU architecture ${t}`);if("darwin"===n)return[`tnc-dock-vm-${e}.tar.gz`,"tnc-vz-darwin-universal","tnc-menubar-darwin-universal"];if("win32"===n)return[`tnc-dock-wsl-${e}.tar.gz`];if("linux"===n)return[`tnc-shim-linux-${e}`,`crun-linux-${e}`,`tnc-busybox-linux-${e}`];throw setupError(`unsupported platform ${n}`)}async function download(n,t){const e=await fetch(n,{redirect:"follow"});if(!e.ok)throw setupError(`download failed: ${n} (HTTP ${e.status})`);fs.mkdirSync(path.dirname(t),{recursive:!0}),fs.writeFileSync(`${t}.part`,Buffer.from(await e.arrayBuffer())),fs.renameSync(`${t}.part`,t)}async function fetchBundle({from:n,baseUrl:t=DEFAULT_BASE_URL,log:e}){let r,i;if(n)i=path.resolve(n),r=JSON.parse(fs.readFileSync(path.join(i,"manifest.json"),"utf8"));else{
|
|
7
|
+
"use strict";const fs=require("fs"),os=require("os"),path=require("path"),crypto=require("crypto"),{spawnSync}=require("child_process"),TNC_DIR=path.join(os.homedir(),".tnc"),BUNDLE_VERSION="0.2.2",DEFAULT_BASE_URL=process.env.TNC_DOCK_BUNDLE_URL||"https://thinkncollab.com/downloads/dock/0.2.2",WSL_DISTRO="tnc-dock",linuxArch=(n=process.arch)=>({arm64:"aarch64",x64:"x86_64"}[n]);function setupError(n){const t=new Error(n);return t.setup=!0,t}function sha256File(n){const t=crypto.createHash("sha256"),e=fs.openSync(n,"r"),r=Buffer.alloc(1<<20);let i;for(;(i=fs.readSync(e,r,0,r.length,null))>0;)t.update(r.subarray(0,i));return fs.closeSync(e),t.digest("hex")}const OPTIONAL_FILES=/^(tnc-busybox-linux-|tnc-menubar-)/;function wantedFiles(n=process.platform,t=process.arch){const e=linuxArch(t);if(!e)throw setupError(`unsupported CPU architecture ${t}`);if("darwin"===n)return[`tnc-dock-vm-${e}.tar.gz`,"tnc-vz-darwin-universal","tnc-menubar-darwin-universal"];if("win32"===n)return[`tnc-dock-wsl-${e}.tar.gz`];if("linux"===n)return[`tnc-shim-linux-${e}`,`crun-linux-${e}`,`tnc-busybox-linux-${e}`];throw setupError(`unsupported platform ${n}`)}async function download(n,t){const e=await fetch(n,{redirect:"follow"});if(!e.ok)throw setupError(`download failed: ${n} (HTTP ${e.status})`);fs.mkdirSync(path.dirname(t),{recursive:!0}),fs.writeFileSync(`${t}.part`,Buffer.from(await e.arrayBuffer())),fs.renameSync(`${t}.part`,t)}async function fetchBundle({from:n,baseUrl:t=DEFAULT_BASE_URL,log:e}){let r,i;if(n)i=path.resolve(n),r=JSON.parse(fs.readFileSync(path.join(i,"manifest.json"),"utf8"));else{let n=await fetch(`${t}/manifest.json`,{redirect:"follow"});if(!n.ok){const e="https://thinkncollab.com/downloads/dock",i=await fetch(`${e}/manifest.json`,{redirect:"follow"});i.ok&&(r=await i.json(),t=`${e}/${r.version}`,n=i)}if(!n.ok)throw setupError(`cannot fetch bundle manifest from ${t} (HTTP ${n.status})`);r||(r=await n.json()),i=path.join(TNC_DIR,"cache","bundles",r.version)}const s={};for(const o of wantedFiles()){const c=r.files[o];if(!c&&OPTIONAL_FILES.test(o)){e(`bundle ${r.version} has no ${o}; ${o.startsWith("tnc-menubar")?"the menu bar app":"cp"} needs a newer bundle`);continue}if(!c)throw setupError(`bundle ${r.version} has no ${o}`);const a=path.join(i,o);if(n||fs.existsSync(a)&&sha256File(a)===c.sha256||(e(`downloading ${o} (${(c.size/1024/1024).toFixed(1)} MB)`),await download(`${t}/${o}`,a)),!fs.existsSync(a))throw setupError(`missing ${a}`);if(sha256File(a)!==c.sha256)throw setupError(`sha256 mismatch for ${o}; refusing to install`);s[o]=a}return{manifest:r,files:s}}function run(n,t,e={}){const r=spawnSync(n,t,{encoding:"utf8",...e});return{code:r.status,out:`${r.stdout||""}${r.stderr||""}`.replace(/\0/g,"")}}function installDarwin(n,t){const e=linuxArch(),r=path.join(TNC_DIR,"kernel"),i=path.join(TNC_DIR,"bin");fs.mkdirSync(r,{recursive:!0}),fs.mkdirSync(i,{recursive:!0}),run(path.join(i,"tnc-vz"),["stop"]);for(const n of["vmlinuz","initramfs.img"]){const t=path.join(r,n);fs.existsSync(t)&&!fs.existsSync(`${t}.pre-bundle`)&&fs.copyFileSync(t,`${t}.pre-bundle`)}const s=run("tar",["-xzf",n[`tnc-dock-vm-${e}.tar.gz`],"-C",r]);if(0!==s.code)throw setupError(`cannot unpack VM image: ${s.out}`);const o=path.join(i,"tnc-vz");if(fs.existsSync(o)&&!fs.existsSync(`${o}.pre-bundle`)&&fs.copyFileSync(o,`${o}.pre-bundle`),fs.copyFileSync(n["tnc-vz-darwin-universal"],`${o}.new`),fs.chmodSync(`${o}.new`,493),fs.renameSync(`${o}.new`,o),t(`MicroVM image (${e}) and tnc-vz installed`),n["tnc-menubar-darwin-universal"]){const e=path.join(i,"tnc-menubar");fs.copyFileSync(n["tnc-menubar-darwin-universal"],`${e}.new`),fs.chmodSync(`${e}.new`,493),fs.renameSync(`${e}.new`,e),t("menu bar app installed (open it with: thinknsh dock app)")}}function wslDistros(){const n=run("wsl.exe",["--list","--quiet"]);return 0===n.code?n.out.split(/\r?\n/).map(n=>n.trim()).filter(Boolean):null}function sanitizeWslTar(n){const t=require("zlib");let e;try{e=t.gunzipSync(fs.readFileSync(n))}catch(t){return{path:n,cleanup:()=>{}}}let r=0;const i=[];let s=!1;for(;r<e.length;){const n=e.subarray(r,r+512);if(n.length<512||n.every(n=>0===n)){i.push(e.subarray(r));break}const t=n.subarray(124,136).toString("latin1").replace(/\0.*$/,"").trim(),o=parseInt(t,8)||0,c=String.fromCharCode(n[156]),a=512+512*Math.ceil(o/512);"x"===c||"g"===c?s=!0:i.push(e.subarray(r,r+a)),r+=a}if(!s)return{path:n,cleanup:()=>{}};const o=path.join(os.tmpdir(),`tnc-dock-wsl-clean-${Date.now()}.tar.gz`);return fs.writeFileSync(o,t.gzipSync(Buffer.concat(i))),{path:o,cleanup:()=>{try{fs.unlinkSync(o)}catch(n){}}}}function installWindows(n,t,{force:e=!1}={}){const r=linuxArch();if(0!==run("wsl.exe",["--status"]).code)throw setupError("WSL is not installed. Run in an admin PowerShell: wsl --install --no-distribution, reboot, then run thinknsh dock setup again");const i=wslDistros()||[],s=n[`tnc-dock-wsl-${r}.tar.gz`],{path:o,cleanup:c}=sanitizeWslTar(s);try{if(i.includes("tnc-dock")&&!e){const n=run("wsl.exe",["-d","tnc-dock","-u","root","--","tar","-xzf","-","-C","/","./usr/bin/tnc-shim","./usr/bin/crun","./usr/local/bin/tnc-wsl-init","./etc/wsl.conf","./bin/busybox.static"],{input:fs.readFileSync(o)});if(0!==n.code)throw setupError(`cannot upgrade WSL distro: ${n.out}`);return run("wsl.exe",["--terminate","tnc-dock"]),void t("WSL distro tnc-dock upgraded in place (layers and volumes kept)")}i.includes("tnc-dock")&&run("wsl.exe",["--unregister","tnc-dock"]);const n=path.join(TNC_DIR,"wsl","tnc-dock");fs.mkdirSync(n,{recursive:!0});const r=run("wsl.exe",["--import","tnc-dock",n,o,"--version","2"]);if(0!==r.code)throw setupError(`wsl --import failed: ${r.out.trim()}`);t(`WSL2 distro tnc-dock created in ${n}`)}finally{c()}}function installLinux(n,t){const e=linuxArch(),r=path.join(TNC_DIR,"bin");fs.mkdirSync(r,{recursive:!0});for(const t of[`tnc-shim-linux-${e}`,`crun-linux-${e}`,`tnc-busybox-linux-${e}`]){const e=t;n[t]&&(fs.copyFileSync(n[t],path.join(r,`${e}.new`)),fs.chmodSync(path.join(r,`${e}.new`),493),fs.renameSync(path.join(r,`${e}.new`),path.join(r,e)))}t(`tnc-shim, crun and busybox installed in ${r}${0===process.getuid?.()?"":" (dock must run as root on Linux)"}`)}async function prefetchBuildKit(n){if("0"===process.env.TNC_DOCK_BUILDKIT)return;const{installBuildKit:t,buildKitDir:e,buildKitInstalled:r,VERSION:i}=require("../build/buildkit"),s={arm64:"arm64",x64:"amd64"}[process.arch];if(!s)return;const o=e(process.env.TNC_DOCK_HOME||path.join(TNC_DIR,"dock"),s);if(r(o))return n(`BuildKit ${i} already installed`);try{n(`downloading BuildKit ${i} (linux/${s})`),await t(o,s),n(`BuildKit ${i} installed in ${o}`)}catch(t){n(`warning: ${t.message}; it will be downloaded on the first BuildKit build`)}}async function setup({from:n,baseUrl:t,force:e=!1,log:r=()=>{}}={}){const{manifest:i,files:s}=await fetchBundle({from:n,baseUrl:t,log:r});return"darwin"===process.platform?installDarwin(s,r):"win32"===process.platform?installWindows(s,r,{force:e}):installLinux(s,r),await prefetchBuildKit(r),fs.mkdirSync(path.join(TNC_DIR,"dock"),{recursive:!0}),fs.writeFileSync(path.join(TNC_DIR,"dock","runtime.json"),JSON.stringify({version:i.version,platform:process.platform,arch:process.arch,installedAt:(new Date).toISOString()},null,2)),i.version}module.exports={setup,prefetchBuildKit,wantedFiles,fetchBundle,linuxArch,WSL_DISTRO:"tnc-dock",sha256File,BUNDLE_VERSION:"0.2.2",sanitizeWslTar};
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* PROPRIETARY INTELLECTUAL PROPERTY & TRADE SECRETS.
|
|
5
5
|
* UNAUTHORIZED REVERSE ENGINEERING, DECOMPILATION, OR RE-DISTRIBUTION IS STRICTLY PROHIBITED.
|
|
6
6
|
*/
|
|
7
|
-
"use strict";let DatabaseSync=null;try{({DatabaseSync}=require("node:sqlite"))}catch(e){}const MIGRATIONS=[{version:1,sql:"\n CREATE TABLE containers (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n image TEXT NOT NULL,\n state TEXT NOT NULL, -- created | running | paused | exited | dead\n config TEXT NOT NULL, -- JSON: container create request\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n CREATE TABLE images (\n id TEXT PRIMARY KEY, -- sha256 of image config\n repo_tags TEXT NOT NULL DEFAULT '[]',\n digest TEXT,\n size INTEGER NOT NULL DEFAULT 0,\n config TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE networks (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n driver TEXT NOT NULL,\n subnet TEXT,\n config TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE volumes (\n name TEXT PRIMARY KEY,\n driver TEXT NOT NULL DEFAULT 'local',\n mountpoint TEXT NOT NULL,\n labels TEXT NOT NULL DEFAULT '{}',\n created_at INTEGER NOT NULL\n );\n CREATE TABLE events (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n time INTEGER NOT NULL,\n type TEXT NOT NULL,\n action TEXT NOT NULL,\n actor_id TEXT NOT NULL DEFAULT '',\n attributes TEXT NOT NULL DEFAULT '{}'\n );\n "},{version:2,sql:"\n ALTER TABLE containers ADD COLUMN runtime TEXT NOT NULL DEFAULT '{}'; -- JSON: pid, ip, exit code, timestamps\n ALTER TABLE volumes ADD COLUMN anonymous INTEGER NOT NULL DEFAULT 0;\n CREATE INDEX events_time ON events (time);\n "},{version:3,sql:"\n CREATE TABLE build_cache (\n key TEXT PRIMARY KEY, -- sha256 of parent chain + instruction + inputs\n digest TEXT NOT NULL, -- layer blob digest ('' for steps without a layer)\n diff_id TEXT NOT NULL,\n size INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n "}],parse=(e,t)=>{try{return JSON.parse(e)}catch(e){return t}},IMAGE_ID=/^(sha256:)?[0-9a-f]{4,64}$/;class Store{constructor(e){if(!DatabaseSync)throw new Error(`dock needs Node.js >= 22.5 (built-in node:sqlite). Current: ${process.version}`);this.db=new DatabaseSync(e),this.db.exec("\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = NORMAL;\n PRAGMA foreign_keys = ON;\n PRAGMA busy_timeout = 5000;\n "),this._migrate()}_migrate(){this.db.exec("CREATE TABLE IF NOT EXISTS schema_migrations (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )");const e=new Set(this.db.prepare("SELECT version FROM schema_migrations").all().map(e=>e.version));for(const t of MIGRATIONS)if(!e.has(t.version)){this.db.exec("BEGIN IMMEDIATE");try{this.db.exec(t.sql),this.db.prepare("INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)").run(t.version,Date.now()),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),new Error(`dock state migration ${t.version} failed: ${e.message}`)}}}schemaVersion(){return this.db.prepare("SELECT COALESCE(MAX(version), 0) AS v FROM schema_migrations").get().v}journalMode(){return this.db.prepare("PRAGMA journal_mode").get().journal_mode}counts(){const e={};for(const t of this.db.prepare("SELECT state, COUNT(*) AS n FROM containers GROUP BY state").all())e[t.state]=t.n;const t=Object.values(e).reduce((e,t)=>e+t,0),n=this.db.prepare("SELECT COUNT(*) AS n FROM containers WHERE state = 'running' AND json_extract(runtime, '$.paused') = 1").get().n;return{containers:t,running:(e.running||0)-n,paused:(e.paused||0)+n,stopped:t-(e.running||0)-(e.paused||0),images:this.db.prepare("SELECT COUNT(*) AS n FROM images").get().n}}recordEvent(e,t,n="",r={},i=Date.now()){const a=this.db.prepare("INSERT INTO events (time, type, action, actor_id, attributes) VALUES (?, ?, ?, ?, ?)").run(i,e,t,n,JSON.stringify(r));return{seq:Number(a.lastInsertRowid),time:i,type:e,action:t,actorId:n,attributes:r}}eventsBetween(e,t){return this.db.prepare("SELECT * FROM events WHERE time >= ? AND time <= ? ORDER BY seq").all(e,t).map(e=>({seq:e.seq,time:e.time,type:e.type,action:e.action,actorId:e.actor_id,attributes:parse(e.attributes,{})}))}_container(e){return e?{id:e.id,name:e.name,image:e.image,state:e.state,config:parse(e.config,{}),runtime:parse(e.runtime,{}),createdAt:e.created_at,updatedAt:e.updated_at}:null}insertContainer(e){const t=Date.now();return this.db.prepare("INSERT INTO containers (id, name, image, state, config, runtime, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e.id,e.name,e.image,e.state,JSON.stringify(e.config),JSON.stringify(e.runtime||{}),t,t),this.getContainer(e.id)}updateContainer(e,{state:t,runtime:n,name:r}){const i=this.getContainer(e);return i?(this.db.prepare("UPDATE containers SET state = ?, runtime = ?, name = ?, updated_at = ? WHERE id = ?").run(t??i.state,JSON.stringify(n??i.runtime),r??i.name,Date.now(),e),this.getContainer(e)):null}getContainer(e){return this._container(this.db.prepare("SELECT * FROM containers WHERE id = ?").get(e))}findContainer(e){if(!e)return null;const t=e.replace(/^\//,""),n=this.getContainer(e);if(n)return n;const r=this._container(this.db.prepare("SELECT * FROM containers WHERE name = ?").get(t));if(r)return r;if(!/^[0-9a-f]+$/.test(e))return null;const i=this.db.prepare("SELECT * FROM containers WHERE id LIKE ? LIMIT 2").all(e+"%");if(i.length>1){const t=new Error(`multiple containers found with provided prefix: ${e}`);throw t.statusCode=400,t}return this._container(i[0])}listContainers(){return this.db.prepare("SELECT * FROM containers ORDER BY created_at DESC").all().map(e=>this._container(e))}deleteContainer(e){this.db.prepare("DELETE FROM containers WHERE id = ?").run(e)}_image(e){return e?{id:e.id,repoTags:parse(e.repo_tags,[]),digest:e.digest,size:e.size,...parse(e.config,{}),createdAt:e.created_at}:null}saveImage(e){this.db.exec("BEGIN IMMEDIATE");try{for(const t of e.repoTags)for(const n of this.listImages())n.id!==e.id&&n.repoTags.includes(t)&&this.db.prepare("UPDATE images SET repo_tags = ? WHERE id = ?").run(JSON.stringify(n.repoTags.filter(e=>e!==t)),n.id);const t=this.getImage(e.id),n=[...new Set([...t?t.repoTags:[],...e.repoTags])],{id:r,repoTags:i,digest:a,size:s,createdAt:E,...o}=e;this.db.prepare("INSERT INTO images (id, repo_tags, digest, size, config, created_at) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET repo_tags = excluded.repo_tags, digest = excluded.digest,\n size = excluded.size, config = excluded.config").run(r,JSON.stringify(n),a||null,s||0,JSON.stringify(o),E||Date.now()),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}return this.getImage(e.id)}setImageTags(e,t){this.db.prepare("UPDATE images SET repo_tags = ? WHERE id = ?").run(JSON.stringify(t),e)}getImage(e){return this._image(this.db.prepare("SELECT * FROM images WHERE id = ?").get(e))}findImage(e,t){for(const n of this.listImages()){if(n.repoTags.includes(t||e))return n;if((n.repoDigests||[]).some(n=>n===t||n===e))return n}if(IMAGE_ID.test(e)){const t=e.replace(/^sha256:/,""),n=this.db.prepare("SELECT * FROM images WHERE id LIKE ? LIMIT 2").all("sha256:"+t+"%");if(1===n.length)return this._image(n[0])}return null}listImages(){return this.db.prepare("SELECT * FROM images ORDER BY created_at DESC").all().map(e=>this._image(e))}deleteImage(e){this.db.prepare("DELETE FROM images WHERE id = ?").run(e)}getBuildCache(e){const t=this.db.prepare("SELECT * FROM build_cache WHERE key = ?").get(e);return t?{digest:t.digest,diffId:t.diff_id,size:t.size}:null}putBuildCache(e,{digest:t,diffId:n,size:r}){this.db.prepare("INSERT OR REPLACE INTO build_cache (key, digest, diff_id, size, created_at) VALUES (?, ?, ?, ?, ?)").run(e,t,n,r,Date.now())}_network(e){return e?{id:e.id,name:e.name,driver:e.driver,subnet:e.subnet,...parse(e.config,{}),createdAt:e.created_at}:null}insertNetwork(e){const{id:t,name:n,driver:r,subnet:i,createdAt:a,...s}=e;return this.db.prepare("INSERT INTO networks (id, name, driver, subnet, config, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,n,r,i,JSON.stringify(s),Date.now()),this.getNetwork(t)}getNetwork(e){return this._network(this.db.prepare("SELECT * FROM networks WHERE id = ?").get(e))}findNetwork(e){if(!e)return null;const t=this.db.prepare("SELECT * FROM networks WHERE id = ? OR name = ?").get(e,e);if(t)return this._network(t);if(!/^[0-9a-f]+$/.test(e))return null;const n=this.db.prepare("SELECT * FROM networks WHERE id LIKE ? LIMIT 2").all(e+"%");return 1===n.length?this._network(n[0]):null}listNetworks(){return this.db.prepare("SELECT * FROM networks ORDER BY created_at").all().map(e=>this._network(e))}deleteNetwork(e){this.db.prepare("DELETE FROM networks WHERE id = ?").run(e)}_volume(e){return e?{name:e.name,driver:e.driver,mountpoint:e.mountpoint,labels:parse(e.labels,{}),anonymous:!!e.anonymous,createdAt:e.created_at}:null}insertVolume(e){return this.db.prepare("INSERT INTO volumes (name, driver, mountpoint, labels, anonymous, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(e.name,e.driver||"local",e.mountpoint,JSON.stringify(e.labels||{}),e.anonymous?1:0,Date.now()),this.getVolume(e.name)}getVolume(e){return this._volume(this.db.prepare("SELECT * FROM volumes WHERE name = ?").get(e))}listVolumes(){return this.db.prepare("SELECT * FROM volumes ORDER BY created_at DESC").all().map(e=>this._volume(e))}deleteVolume(e){this.db.prepare("DELETE FROM volumes WHERE name = ?").run(e)}close(){try{this.db.close()}catch(e){}}}Store.isSupported=()=>null!==DatabaseSync,module.exports=Store;
|
|
7
|
+
"use strict";let DatabaseSync=null;try{({DatabaseSync}=require("node:sqlite"))}catch(e){}const MIGRATIONS=[{version:1,sql:"\n CREATE TABLE containers (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n image TEXT NOT NULL,\n state TEXT NOT NULL, -- created | running | paused | exited | dead\n config TEXT NOT NULL, -- JSON: container create request\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n CREATE TABLE images (\n id TEXT PRIMARY KEY, -- sha256 of image config\n repo_tags TEXT NOT NULL DEFAULT '[]',\n digest TEXT,\n size INTEGER NOT NULL DEFAULT 0,\n config TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE networks (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n driver TEXT NOT NULL,\n subnet TEXT,\n config TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE volumes (\n name TEXT PRIMARY KEY,\n driver TEXT NOT NULL DEFAULT 'local',\n mountpoint TEXT NOT NULL,\n labels TEXT NOT NULL DEFAULT '{}',\n created_at INTEGER NOT NULL\n );\n CREATE TABLE events (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n time INTEGER NOT NULL,\n type TEXT NOT NULL,\n action TEXT NOT NULL,\n actor_id TEXT NOT NULL DEFAULT '',\n attributes TEXT NOT NULL DEFAULT '{}'\n );\n "},{version:2,sql:"\n ALTER TABLE containers ADD COLUMN runtime TEXT NOT NULL DEFAULT '{}'; -- JSON: pid, ip, exit code, timestamps\n ALTER TABLE volumes ADD COLUMN anonymous INTEGER NOT NULL DEFAULT 0;\n CREATE INDEX events_time ON events (time);\n "},{version:3,sql:"\n CREATE TABLE build_cache (\n key TEXT PRIMARY KEY, -- sha256 of parent chain + instruction + inputs\n digest TEXT NOT NULL, -- layer blob digest ('' for steps without a layer)\n diff_id TEXT NOT NULL,\n size INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n "}],parse=(e,t)=>{try{return JSON.parse(e)}catch(e){return t}},IMAGE_ID=/^(sha256:)?[0-9a-f]{4,64}$/;class Store{constructor(e){if(!DatabaseSync){const e=process.version.startsWith("v22.")||process.version.startsWith("v23.")?' (run with NODE_OPTIONS="--experimental-sqlite" or update Node to >= 22.13 / >= 23.4)':"";throw new Error(`dock needs Node.js >= 22.5 (built-in node:sqlite). Current: ${process.version}${e}`)}this.db=new DatabaseSync(e),this.db.exec("\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = NORMAL;\n PRAGMA foreign_keys = ON;\n PRAGMA busy_timeout = 5000;\n "),this._migrate()}_migrate(){this.db.exec("CREATE TABLE IF NOT EXISTS schema_migrations (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )");const e=new Set(this.db.prepare("SELECT version FROM schema_migrations").all().map(e=>e.version));for(const t of MIGRATIONS)if(!e.has(t.version)){this.db.exec("BEGIN IMMEDIATE");try{this.db.exec(t.sql),this.db.prepare("INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)").run(t.version,Date.now()),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),new Error(`dock state migration ${t.version} failed: ${e.message}`)}}}schemaVersion(){return this.db.prepare("SELECT COALESCE(MAX(version), 0) AS v FROM schema_migrations").get().v}journalMode(){return this.db.prepare("PRAGMA journal_mode").get().journal_mode}counts(){const e={};for(const t of this.db.prepare("SELECT state, COUNT(*) AS n FROM containers GROUP BY state").all())e[t.state]=t.n;const t=Object.values(e).reduce((e,t)=>e+t,0),n=this.db.prepare("SELECT COUNT(*) AS n FROM containers WHERE state = 'running' AND json_extract(runtime, '$.paused') = 1").get().n;return{containers:t,running:(e.running||0)-n,paused:(e.paused||0)+n,stopped:t-(e.running||0)-(e.paused||0),images:this.db.prepare("SELECT COUNT(*) AS n FROM images").get().n}}recordEvent(e,t,n="",r={},i=Date.now()){const s=this.db.prepare("INSERT INTO events (time, type, action, actor_id, attributes) VALUES (?, ?, ?, ?, ?)").run(i,e,t,n,JSON.stringify(r));return{seq:Number(s.lastInsertRowid),time:i,type:e,action:t,actorId:n,attributes:r}}eventsBetween(e,t){return this.db.prepare("SELECT * FROM events WHERE time >= ? AND time <= ? ORDER BY seq").all(e,t).map(e=>({seq:e.seq,time:e.time,type:e.type,action:e.action,actorId:e.actor_id,attributes:parse(e.attributes,{})}))}_container(e){return e?{id:e.id,name:e.name,image:e.image,state:e.state,config:parse(e.config,{}),runtime:parse(e.runtime,{}),createdAt:e.created_at,updatedAt:e.updated_at}:null}insertContainer(e){const t=Date.now();return this.db.prepare("INSERT INTO containers (id, name, image, state, config, runtime, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e.id,e.name,e.image,e.state,JSON.stringify(e.config),JSON.stringify(e.runtime||{}),t,t),this.getContainer(e.id)}updateContainer(e,{state:t,runtime:n,name:r}){const i=this.getContainer(e);return i?(this.db.prepare("UPDATE containers SET state = ?, runtime = ?, name = ?, updated_at = ? WHERE id = ?").run(t??i.state,JSON.stringify(n??i.runtime),r??i.name,Date.now(),e),this.getContainer(e)):null}getContainer(e){return this._container(this.db.prepare("SELECT * FROM containers WHERE id = ?").get(e))}findContainer(e){if(!e)return null;const t=e.replace(/^\//,""),n=this.getContainer(e);if(n)return n;const r=this._container(this.db.prepare("SELECT * FROM containers WHERE name = ?").get(t));if(r)return r;if(!/^[0-9a-f]+$/.test(e))return null;const i=this.db.prepare("SELECT * FROM containers WHERE id LIKE ? LIMIT 2").all(e+"%");if(i.length>1){const t=new Error(`multiple containers found with provided prefix: ${e}`);throw t.statusCode=400,t}return this._container(i[0])}listContainers(){return this.db.prepare("SELECT * FROM containers ORDER BY created_at DESC").all().map(e=>this._container(e))}deleteContainer(e){this.db.prepare("DELETE FROM containers WHERE id = ?").run(e)}_image(e){return e?{id:e.id,repoTags:parse(e.repo_tags,[]),digest:e.digest,size:e.size,...parse(e.config,{}),createdAt:e.created_at}:null}saveImage(e){this.db.exec("BEGIN IMMEDIATE");try{for(const t of e.repoTags)for(const n of this.listImages())n.id!==e.id&&n.repoTags.includes(t)&&this.db.prepare("UPDATE images SET repo_tags = ? WHERE id = ?").run(JSON.stringify(n.repoTags.filter(e=>e!==t)),n.id);const t=this.getImage(e.id),n=[...new Set([...t?t.repoTags:[],...e.repoTags])],{id:r,repoTags:i,digest:s,size:a,createdAt:o,...E}=e;this.db.prepare("INSERT INTO images (id, repo_tags, digest, size, config, created_at) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET repo_tags = excluded.repo_tags, digest = excluded.digest,\n size = excluded.size, config = excluded.config").run(r,JSON.stringify(n),s||null,a||0,JSON.stringify(E),o||Date.now()),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}return this.getImage(e.id)}setImageTags(e,t){this.db.prepare("UPDATE images SET repo_tags = ? WHERE id = ?").run(JSON.stringify(t),e)}getImage(e){return this._image(this.db.prepare("SELECT * FROM images WHERE id = ?").get(e))}findImage(e,t){for(const n of this.listImages()){if(n.repoTags.includes(t||e))return n;if((n.repoDigests||[]).some(n=>n===t||n===e))return n}if(IMAGE_ID.test(e)){const t=e.replace(/^sha256:/,""),n=this.db.prepare("SELECT * FROM images WHERE id LIKE ? LIMIT 2").all("sha256:"+t+"%");if(1===n.length)return this._image(n[0])}return null}listImages(){return this.db.prepare("SELECT * FROM images ORDER BY created_at DESC").all().map(e=>this._image(e))}deleteImage(e){this.db.prepare("DELETE FROM images WHERE id = ?").run(e)}getBuildCache(e){const t=this.db.prepare("SELECT * FROM build_cache WHERE key = ?").get(e);return t?{digest:t.digest,diffId:t.diff_id,size:t.size}:null}putBuildCache(e,{digest:t,diffId:n,size:r}){this.db.prepare("INSERT OR REPLACE INTO build_cache (key, digest, diff_id, size, created_at) VALUES (?, ?, ?, ?, ?)").run(e,t,n,r,Date.now())}_network(e){return e?{id:e.id,name:e.name,driver:e.driver,subnet:e.subnet,...parse(e.config,{}),createdAt:e.created_at}:null}insertNetwork(e){const{id:t,name:n,driver:r,subnet:i,createdAt:s,...a}=e;return this.db.prepare("INSERT INTO networks (id, name, driver, subnet, config, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(t,n,r,i,JSON.stringify(a),Date.now()),this.getNetwork(t)}getNetwork(e){return this._network(this.db.prepare("SELECT * FROM networks WHERE id = ?").get(e))}findNetwork(e){if(!e)return null;const t=this.db.prepare("SELECT * FROM networks WHERE id = ? OR name = ?").get(e,e);if(t)return this._network(t);if(!/^[0-9a-f]+$/.test(e))return null;const n=this.db.prepare("SELECT * FROM networks WHERE id LIKE ? LIMIT 2").all(e+"%");return 1===n.length?this._network(n[0]):null}listNetworks(){return this.db.prepare("SELECT * FROM networks ORDER BY created_at").all().map(e=>this._network(e))}deleteNetwork(e){this.db.prepare("DELETE FROM networks WHERE id = ?").run(e)}_volume(e){return e?{name:e.name,driver:e.driver,mountpoint:e.mountpoint,labels:parse(e.labels,{}),anonymous:!!e.anonymous,createdAt:e.created_at}:null}insertVolume(e){return this.db.prepare("INSERT INTO volumes (name, driver, mountpoint, labels, anonymous, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(e.name,e.driver||"local",e.mountpoint,JSON.stringify(e.labels||{}),e.anonymous?1:0,Date.now()),this.getVolume(e.name)}getVolume(e){return this._volume(this.db.prepare("SELECT * FROM volumes WHERE name = ?").get(e))}listVolumes(){return this.db.prepare("SELECT * FROM volumes ORDER BY created_at DESC").all().map(e=>this._volume(e))}deleteVolume(e){this.db.prepare("DELETE FROM volumes WHERE name = ?").run(e)}close(){try{this.db.close()}catch(e){}}}Store.isSupported=()=>null!==DatabaseSync,module.exports=Store;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkncollab-shell",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"description": "Lightweight MicroVM Sandbox & Zero-Daemon Dev Runner: Fast MicroVM Containers, Native Microservices, Live Database Snapshots, and End-to-End Encrypted State Sync",
|
|
5
5
|
"main": "dist/src/core/shell.js",
|
|
6
6
|
"bin": {
|