@mifune/agro 0.14.0 → 0.15.0
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/dist/agro.js +261 -22
- package/package.json +1 -1
package/dist/agro.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { dirname as dirname10, join as join17, resolve as resolve11 } from "node:path";
|
|
11
|
-
import { existsSync as existsSync21, mkdtempSync as mkdtempSync5, readFileSync as readFileSync12, rmSync as
|
|
11
|
+
import { existsSync as existsSync21, mkdtempSync as mkdtempSync5, readFileSync as readFileSync12, rmSync as rmSync6 } from "node:fs";
|
|
12
12
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
13
13
|
|
|
14
14
|
// src/commands/update.ts
|
|
@@ -2222,9 +2222,9 @@ function resolveExecutionTarget(opts) {
|
|
|
2222
2222
|
}
|
|
2223
2223
|
|
|
2224
2224
|
// src/lib/version.ts
|
|
2225
|
-
var AGRO_VERSION = true ? "0.
|
|
2225
|
+
var AGRO_VERSION = true ? "0.15.0" : "0.0.0-dev";
|
|
2226
2226
|
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2227
|
-
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]
|
|
2227
|
+
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2228
2228
|
function parseReleaseVersion(value) {
|
|
2229
2229
|
const bare = value.startsWith("v") ? value.slice(1) : value;
|
|
2230
2230
|
return RELEASE_VERSION.test(bare) ? bare : void 0;
|
|
@@ -4210,14 +4210,15 @@ function assertCloned(result, root) {
|
|
|
4210
4210
|
throw new Error(`could not clone ${AGRO_REPO_URL} into ${root} (exit status ${String(status)})`);
|
|
4211
4211
|
}
|
|
4212
4212
|
}
|
|
4213
|
-
function cloneInto(target, run, root) {
|
|
4214
|
-
|
|
4213
|
+
function cloneInto(target, run, root, ref) {
|
|
4214
|
+
const branch = ref !== void 0 ? ["--branch", ref] : [];
|
|
4215
|
+
assertCloned(run("git", ["clone", ...branch, AGRO_REPO_URL, target], { stdio: "inherit" }), root);
|
|
4215
4216
|
}
|
|
4216
|
-
function cloneThroughStaging(root, run) {
|
|
4217
|
+
function cloneThroughStaging(root, run, ref) {
|
|
4217
4218
|
const staging = mkdtempSync4(join13(dirname9(root), "agro-clone-"));
|
|
4218
4219
|
try {
|
|
4219
4220
|
const checkout = join13(staging, "agro");
|
|
4220
|
-
cloneInto(checkout, run, root);
|
|
4221
|
+
cloneInto(checkout, run, root, ref);
|
|
4221
4222
|
for (const name of readdirSync3(checkout)) {
|
|
4222
4223
|
const destination = join13(root, name);
|
|
4223
4224
|
if (existsSync17(destination)) {
|
|
@@ -4229,13 +4230,13 @@ function cloneThroughStaging(root, run) {
|
|
|
4229
4230
|
rmSync4(staging, { recursive: true, force: true });
|
|
4230
4231
|
}
|
|
4231
4232
|
}
|
|
4232
|
-
function ensureHostWorkspace(path4, run = spawnRunner) {
|
|
4233
|
+
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4233
4234
|
const root = resolve9(path4);
|
|
4234
4235
|
if (existsSync17(join13(root, ".git"))) return { root, action: "reused" };
|
|
4235
4236
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4236
4237
|
if (stats === void 0) {
|
|
4237
4238
|
mkdirSync9(dirname9(root), { recursive: true });
|
|
4238
|
-
cloneInto(root, run, root);
|
|
4239
|
+
cloneInto(root, run, root, ref);
|
|
4239
4240
|
return { root, action: "cloned" };
|
|
4240
4241
|
}
|
|
4241
4242
|
if (!stats.isDirectory()) {
|
|
@@ -4247,7 +4248,7 @@ function ensureHostWorkspace(path4, run = spawnRunner) {
|
|
|
4247
4248
|
`${root} holds files but no git checkout (${blocking.sort().join(", ")}) \u2014 choose an empty directory or an existing AGRO checkout`
|
|
4248
4249
|
);
|
|
4249
4250
|
}
|
|
4250
|
-
cloneThroughStaging(root, run);
|
|
4251
|
+
cloneThroughStaging(root, run, ref);
|
|
4251
4252
|
return { root, action: "cloned" };
|
|
4252
4253
|
}
|
|
4253
4254
|
function listHostWorkspaces(env, home) {
|
|
@@ -4773,7 +4774,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4773
4774
|
}
|
|
4774
4775
|
|
|
4775
4776
|
// src/commands/workspace.ts
|
|
4776
|
-
import { existsSync as existsSync19 } from "node:fs";
|
|
4777
|
+
import { existsSync as existsSync19, rmSync as rmSync5 } from "node:fs";
|
|
4777
4778
|
import { homedir as homedir4 } from "node:os";
|
|
4778
4779
|
import { join as join15, resolve as resolve10 } from "node:path";
|
|
4779
4780
|
function homeOf2(opts) {
|
|
@@ -4821,15 +4822,25 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
4821
4822
|
return 1;
|
|
4822
4823
|
}
|
|
4823
4824
|
let action;
|
|
4825
|
+
const preexisting = existsSync19(root);
|
|
4824
4826
|
try {
|
|
4825
4827
|
if (!opts.json && !existsSync19(join15(root, ".git"))) {
|
|
4826
|
-
|
|
4828
|
+
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
4829
|
+
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
4827
4830
|
`);
|
|
4828
4831
|
}
|
|
4829
|
-
const created = ensureHostWorkspace(root, run);
|
|
4832
|
+
const created = ensureHostWorkspace(root, run, opts.ref);
|
|
4830
4833
|
root = created.root;
|
|
4831
4834
|
action = created.action;
|
|
4832
4835
|
} catch (err) {
|
|
4836
|
+
if (opts.ref !== void 0) {
|
|
4837
|
+
if (!preexisting) rmSync5(root, { recursive: true, force: true });
|
|
4838
|
+
io.stderr(
|
|
4839
|
+
`${bin} workspace: could not clone ref "${opts.ref}" \u2014 the ref does not exist in ${AGRO_REPO_URL} or the clone failed (${messageOf2(err)})
|
|
4840
|
+
`
|
|
4841
|
+
);
|
|
4842
|
+
return 1;
|
|
4843
|
+
}
|
|
4833
4844
|
io.stderr(`${bin} workspace: ${messageOf2(err)}
|
|
4834
4845
|
`);
|
|
4835
4846
|
return 1;
|
|
@@ -4884,7 +4895,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
4884
4895
|
|
|
4885
4896
|
// src/commands/tool.ts
|
|
4886
4897
|
import { existsSync as existsSync20 } from "node:fs";
|
|
4887
|
-
import { homedir as homedir5 } from "node:os";
|
|
4898
|
+
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
4888
4899
|
import { delimiter as delimiter3, join as join16 } from "node:path";
|
|
4889
4900
|
|
|
4890
4901
|
// src/lib/tools/catalog.ts
|
|
@@ -5106,6 +5117,178 @@ install -d -m 0700 "$HOME/.tailscale"`
|
|
|
5106
5117
|
`${HARNESS_PREFIX_TOKEN}/bin/tailscaled`
|
|
5107
5118
|
]),
|
|
5108
5119
|
docsPath: TOOLS_DOC
|
|
5120
|
+
}),
|
|
5121
|
+
Object.freeze({
|
|
5122
|
+
id: "code-server",
|
|
5123
|
+
title: "code-server",
|
|
5124
|
+
kind: "installable",
|
|
5125
|
+
binary: "code-server",
|
|
5126
|
+
verifyArgv: Object.freeze(["bash", "-lc", "command -v code-server >/dev/null"]),
|
|
5127
|
+
versionArgv: Object.freeze(["code-server", "--version"]),
|
|
5128
|
+
installArgv: Object.freeze([
|
|
5129
|
+
"bash",
|
|
5130
|
+
"-lc",
|
|
5131
|
+
[
|
|
5132
|
+
"set -e",
|
|
5133
|
+
"version=4.129.0",
|
|
5134
|
+
'arch="$(dpkg --print-architecture)"',
|
|
5135
|
+
'case "$arch" in',
|
|
5136
|
+
" amd64) sha=889b09ff3a167a293f53cb68a5a7f38dbab6bd2b50d7a5951c757e56ba51a2b0 ;;",
|
|
5137
|
+
" arm64) sha=62f7886018923a18cc16112ccfbcd51aee80f8e0c1bb7abc48773d1fd32a7617 ;;",
|
|
5138
|
+
' *) echo "no pinned code-server build for $arch" >&2; exit 1 ;;',
|
|
5139
|
+
"esac",
|
|
5140
|
+
'prefix="${NPM_USER_PREFIX:-$HOME/.local}"',
|
|
5141
|
+
'dest="$prefix/lib/code-server-$version"',
|
|
5142
|
+
'tmp="$(mktemp -d)"',
|
|
5143
|
+
`trap 'rm -rf "$tmp"' EXIT`,
|
|
5144
|
+
'curl -fsSL "https://github.com/coder/code-server/releases/download/v$version/code-server-$version-linux-$arch.tar.gz" -o "$tmp/code-server.tar.gz"',
|
|
5145
|
+
'echo "$sha $tmp/code-server.tar.gz" | sha256sum -c -',
|
|
5146
|
+
'install -d "$tmp/release" "$prefix/lib" "$prefix/bin"',
|
|
5147
|
+
'tar -xzf "$tmp/code-server.tar.gz" -C "$tmp/release" --strip-components=1',
|
|
5148
|
+
'rm -rf "$dest"',
|
|
5149
|
+
'mv "$tmp/release" "$dest"',
|
|
5150
|
+
'ln -sfn "$dest/bin/code-server" "$prefix/bin/code-server"',
|
|
5151
|
+
'if ! "$prefix/bin/code-server" --version | grep -q "^$version "; then',
|
|
5152
|
+
' echo "$prefix/bin/code-server does not report version $version" >&2',
|
|
5153
|
+
" exit 1",
|
|
5154
|
+
"fi"
|
|
5155
|
+
].join("\n")
|
|
5156
|
+
]),
|
|
5157
|
+
installUser: "sandbox",
|
|
5158
|
+
hostCapable: true,
|
|
5159
|
+
uninstallArgv: Object.freeze([
|
|
5160
|
+
"rm",
|
|
5161
|
+
"-rf",
|
|
5162
|
+
`${HARNESS_PREFIX_TOKEN}/bin/code-server`,
|
|
5163
|
+
`${HARNESS_PREFIX_TOKEN}/lib/code-server-4.129.0`
|
|
5164
|
+
]),
|
|
5165
|
+
docsPath: TOOLS_DOC
|
|
5166
|
+
}),
|
|
5167
|
+
Object.freeze({
|
|
5168
|
+
id: "docker-engine",
|
|
5169
|
+
title: "Docker Engine + Compose",
|
|
5170
|
+
kind: "installable",
|
|
5171
|
+
binary: "docker",
|
|
5172
|
+
verifyArgv: Object.freeze([
|
|
5173
|
+
"bash",
|
|
5174
|
+
"-lc",
|
|
5175
|
+
'command -v docker >/dev/null && docker compose version >/dev/null && systemctl is-enabled --quiet docker && id -nG "${SUDO_USER:-$(id -un)}" | tr " " "\\n" | grep -qx docker'
|
|
5176
|
+
]),
|
|
5177
|
+
versionArgv: Object.freeze(["docker", "--version"]),
|
|
5178
|
+
hostInstallArgv: Object.freeze([
|
|
5179
|
+
"bash",
|
|
5180
|
+
"-lc",
|
|
5181
|
+
[
|
|
5182
|
+
"set -e",
|
|
5183
|
+
". /etc/os-release",
|
|
5184
|
+
'if [ "${ID:-}" != ubuntu ]; then',
|
|
5185
|
+
' echo "docker-engine installs from Docker\'s Ubuntu repository; this host is ${ID:-unknown}" >&2',
|
|
5186
|
+
" exit 1",
|
|
5187
|
+
"fi",
|
|
5188
|
+
"fingerprint=9DC858229FC7DD38854AE2D88D81803C0EBFCD88",
|
|
5189
|
+
'user="${SUDO_USER:-$(id -un)}"',
|
|
5190
|
+
"export DEBIAN_FRONTEND=noninteractive",
|
|
5191
|
+
'tmp="$(mktemp -d)"',
|
|
5192
|
+
`trap 'rm -rf "$tmp"' EXIT`,
|
|
5193
|
+
"apt-get update",
|
|
5194
|
+
"apt-get install -y ca-certificates curl gnupg",
|
|
5195
|
+
"install -m 0755 -d /etc/apt/keyrings",
|
|
5196
|
+
'curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o "$tmp/docker.asc"',
|
|
5197
|
+
`actual="$(gpg --homedir "$tmp" --show-keys --with-colons "$tmp/docker.asc" | awk -F: '$1 == "fpr" { print $10; exit }')"`,
|
|
5198
|
+
'if [ "$actual" != "$fingerprint" ]; then',
|
|
5199
|
+
' echo "Docker\'s repository key has fingerprint ${actual:-none}, expected $fingerprint" >&2',
|
|
5200
|
+
" exit 1",
|
|
5201
|
+
"fi",
|
|
5202
|
+
'install -m 0644 "$tmp/docker.asc" /etc/apt/keyrings/docker.asc',
|
|
5203
|
+
'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME:-$VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list',
|
|
5204
|
+
"apt-get update",
|
|
5205
|
+
"apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin",
|
|
5206
|
+
'usermod -aG docker "$user"',
|
|
5207
|
+
"systemctl enable --now docker",
|
|
5208
|
+
'echo "$user is in the docker group. Log out and back in before you run docker without sudo."'
|
|
5209
|
+
].join("\n")
|
|
5210
|
+
]),
|
|
5211
|
+
hostInstallUser: "root",
|
|
5212
|
+
notInstallableReason: (bin) => `Docker Engine installs on the host only, with \`${bin} tool install docker-engine --host\`. To use Docker inside the sandbox, set \`access.dockerSocket\` to true in agro.json on the host and recreate the sandbox. That mounts the host Docker socket, which is effectively host root.`,
|
|
5213
|
+
hostCapable: true,
|
|
5214
|
+
uninstallArgv: null,
|
|
5215
|
+
docsPath: TOOLS_DOC
|
|
5216
|
+
}),
|
|
5217
|
+
Object.freeze({
|
|
5218
|
+
id: "desktop",
|
|
5219
|
+
title: "XFCE desktop over XRDP",
|
|
5220
|
+
kind: "installable",
|
|
5221
|
+
binary: "xrdp",
|
|
5222
|
+
verifyArgv: Object.freeze([
|
|
5223
|
+
"bash",
|
|
5224
|
+
"-lc",
|
|
5225
|
+
'command -v xrdp >/dev/null && systemctl is-enabled --quiet xrdp && systemctl is-enabled --quiet tailscaled && test -f /etc/systemd/system/xrdp.service.d/agro-tailscale-only.conf && grep -qx xfce4-session "$HOME/.xsession"'
|
|
5226
|
+
]),
|
|
5227
|
+
hostInstallArgv: Object.freeze([
|
|
5228
|
+
"bash",
|
|
5229
|
+
"-lc",
|
|
5230
|
+
[
|
|
5231
|
+
"set -e",
|
|
5232
|
+
". /etc/os-release",
|
|
5233
|
+
'if [ "${ID:-}" != ubuntu ]; then',
|
|
5234
|
+
' echo "desktop installs Ubuntu packages; this host is ${ID:-unknown}" >&2',
|
|
5235
|
+
" exit 1",
|
|
5236
|
+
"fi",
|
|
5237
|
+
'user="${SUDO_USER:-$(id -un)}"',
|
|
5238
|
+
'home="$(getent passwd "$user" | cut -d: -f6)"',
|
|
5239
|
+
'codename="${UBUNTU_CODENAME:-$VERSION_CODENAME}"',
|
|
5240
|
+
"export DEBIAN_FRONTEND=noninteractive",
|
|
5241
|
+
'tmp="$(mktemp -d)"',
|
|
5242
|
+
`trap 'rm -rf "$tmp"' EXIT`,
|
|
5243
|
+
"apt-get update",
|
|
5244
|
+
"apt-get install -y ca-certificates curl gnupg nftables",
|
|
5245
|
+
"if ! systemctl cat tailscaled.service >/dev/null 2>&1; then",
|
|
5246
|
+
" fingerprint=2596A99EAAB33821893C0A79458CA832957F5868",
|
|
5247
|
+
' curl -fsSL "https://pkgs.tailscale.com/stable/ubuntu/$codename.noarmor.gpg" -o "$tmp/tailscale.gpg"',
|
|
5248
|
+
` actual="$(gpg --homedir "$tmp" --show-keys --with-colons "$tmp/tailscale.gpg" | awk -F: '$1 == "fpr" { print $10; exit }')"`,
|
|
5249
|
+
' if [ "$actual" != "$fingerprint" ]; then',
|
|
5250
|
+
' echo "Tailscale\'s repository key has fingerprint ${actual:-none}, expected $fingerprint" >&2',
|
|
5251
|
+
" exit 1",
|
|
5252
|
+
" fi",
|
|
5253
|
+
' install -m 0644 "$tmp/tailscale.gpg" /usr/share/keyrings/tailscale-archive-keyring.gpg',
|
|
5254
|
+
' echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/ubuntu $codename main" > /etc/apt/sources.list.d/tailscale.list',
|
|
5255
|
+
" apt-get update",
|
|
5256
|
+
" apt-get install -y tailscale",
|
|
5257
|
+
"fi",
|
|
5258
|
+
"systemctl enable --now tailscaled",
|
|
5259
|
+
"install -d -m 0755 /etc/xrdp /etc/systemd/system/xrdp.service.d",
|
|
5260
|
+
"cat > /etc/xrdp/agro-tailscale-only.nft <<'EOF'",
|
|
5261
|
+
"table inet agro_xrdp",
|
|
5262
|
+
"delete table inet agro_xrdp",
|
|
5263
|
+
"table inet agro_xrdp {",
|
|
5264
|
+
" chain input {",
|
|
5265
|
+
" type filter hook input priority filter; policy accept;",
|
|
5266
|
+
' iifname { "lo", "tailscale0" } accept',
|
|
5267
|
+
" tcp dport 3389 reject with tcp reset",
|
|
5268
|
+
" }",
|
|
5269
|
+
"}",
|
|
5270
|
+
"EOF",
|
|
5271
|
+
"nft -f /etc/xrdp/agro-tailscale-only.nft",
|
|
5272
|
+
`printf '[Service]\\nExecStartPre=%s -f /etc/xrdp/agro-tailscale-only.nft\\n' "$(command -v nft)" > /etc/systemd/system/xrdp.service.d/agro-tailscale-only.conf`,
|
|
5273
|
+
"systemctl daemon-reload",
|
|
5274
|
+
"apt-get install -y xfce4 xfce4-goodies xrdp xorgxrdp",
|
|
5275
|
+
`printf '%s\\n' xfce4-session > "$home/.xsession"`,
|
|
5276
|
+
'chown "$user:$(id -gn "$user")" "$home/.xsession"',
|
|
5277
|
+
"adduser xrdp ssl-cert",
|
|
5278
|
+
"systemctl daemon-reload",
|
|
5279
|
+
"systemctl enable xrdp",
|
|
5280
|
+
"systemctl restart xrdp",
|
|
5281
|
+
"systemctl is-active --quiet xrdp",
|
|
5282
|
+
'echo "The desktop is installed. Two steps remain:"',
|
|
5283
|
+
'echo " sudo tailscale up"',
|
|
5284
|
+
'echo " sudo passwd $user"'
|
|
5285
|
+
].join("\n")
|
|
5286
|
+
]),
|
|
5287
|
+
hostInstallUser: "root",
|
|
5288
|
+
notInstallableReason: (bin) => `The desktop installs on the host only, with \`${bin} tool install desktop --host\`. It installs Tailscale and serves XFCE over XRDP on TCP 3389 through Tailscale only.`,
|
|
5289
|
+
hostCapable: true,
|
|
5290
|
+
uninstallArgv: null,
|
|
5291
|
+
docsPath: TOOLS_DOC
|
|
5109
5292
|
})
|
|
5110
5293
|
]);
|
|
5111
5294
|
function findTool(id) {
|
|
@@ -5168,6 +5351,10 @@ function homeOf3(opts) {
|
|
|
5168
5351
|
function platformOf(opts) {
|
|
5169
5352
|
return opts.platform ?? process.platform;
|
|
5170
5353
|
}
|
|
5354
|
+
function isRootUser(opts) {
|
|
5355
|
+
const identity = opts.identity ?? (() => ({ name: userInfo2().username, uid: userInfo2().uid }));
|
|
5356
|
+
return identity().uid === 0;
|
|
5357
|
+
}
|
|
5171
5358
|
function isInteractive2(opts) {
|
|
5172
5359
|
return opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
5173
5360
|
}
|
|
@@ -5206,6 +5393,7 @@ function rowOf(entry, installed, version, location) {
|
|
|
5206
5393
|
version,
|
|
5207
5394
|
installable: entry.installArgv !== void 0,
|
|
5208
5395
|
hostCapable: entry.hostCapable,
|
|
5396
|
+
hostRoot: entry.hostInstallUser === "root",
|
|
5209
5397
|
location,
|
|
5210
5398
|
docs: sourceDocsUrl(entry.docsPath)
|
|
5211
5399
|
};
|
|
@@ -5253,13 +5441,20 @@ function cell2(value, absent) {
|
|
|
5253
5441
|
function renderTable3(collected, io, bin) {
|
|
5254
5442
|
const rows = collected.rows;
|
|
5255
5443
|
const header2 = ["TOOL", "KIND", "INSTALLED"];
|
|
5256
|
-
const body = rows.map((r) => [
|
|
5444
|
+
const body = rows.map((r) => [
|
|
5445
|
+
r.id,
|
|
5446
|
+
r.hostRoot ? `${r.kind} (root)` : r.kind,
|
|
5447
|
+
cell2(r.installed, "?")
|
|
5448
|
+
]);
|
|
5257
5449
|
const widths = header2.map(
|
|
5258
5450
|
(h, i) => Math.max(h.length, ...body.map((b) => b[i].length))
|
|
5259
5451
|
);
|
|
5260
5452
|
const line = (cols) => cols.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd() + "\n";
|
|
5261
5453
|
io.stdout(line(header2));
|
|
5262
5454
|
for (const row of body) io.stdout(line(row));
|
|
5455
|
+
if (rows.some((r) => r.hostRoot)) {
|
|
5456
|
+
io.stdout("\n(root) installs on the host as root through `sudo -n`.\n");
|
|
5457
|
+
}
|
|
5263
5458
|
if (rows.some((r) => r.location === "host")) {
|
|
5264
5459
|
io.stdout(
|
|
5265
5460
|
`
|
|
@@ -5439,11 +5634,21 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5439
5634
|
}
|
|
5440
5635
|
return 0;
|
|
5441
5636
|
}
|
|
5637
|
+
const sudo = entry.hostInstallUser === "root" && !isRootUser(opts);
|
|
5638
|
+
if (sudo && (await tryExec(target, ["sudo", "-n", "true"], void 0))?.exitCode !== 0) {
|
|
5639
|
+
io.stderr(
|
|
5640
|
+
`${bin} tool: ${entry.id} installs as root on the host and needs passwordless \`sudo\`.
|
|
5641
|
+
\`sudo -n true\` failed. Configure passwordless sudo for this user, or run as root.
|
|
5642
|
+
`
|
|
5643
|
+
);
|
|
5644
|
+
return 1;
|
|
5645
|
+
}
|
|
5442
5646
|
if (!await confirmDownload(entry, opts, io, true)) return 1;
|
|
5443
5647
|
io.stdout(`installing ${entry.title} on the host\u2026
|
|
5444
5648
|
`);
|
|
5649
|
+
const argv = resolveToolInstallArgv(entry, true);
|
|
5445
5650
|
const r = await target.exec({
|
|
5446
|
-
argv:
|
|
5651
|
+
argv: sudo ? ["sudo", "-n", "--", ...argv] : argv,
|
|
5447
5652
|
stdio: "inherit",
|
|
5448
5653
|
env: installEnv
|
|
5449
5654
|
});
|
|
@@ -5475,6 +5680,11 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5475
5680
|
`);
|
|
5476
5681
|
return 1;
|
|
5477
5682
|
}
|
|
5683
|
+
if (entry.hostInstallUser === "root") {
|
|
5684
|
+
io.stdout(`${entry.id} installed on the host \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5685
|
+
`);
|
|
5686
|
+
return 0;
|
|
5687
|
+
}
|
|
5478
5688
|
io.stdout(`${entry.id}: installed at ${prefix} \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5479
5689
|
`);
|
|
5480
5690
|
if (!onPath2(prefix, env)) {
|
|
@@ -5490,7 +5700,8 @@ async function runToolInstall(name, opts, io) {
|
|
|
5490
5700
|
const root = resolveProjectRoot(opts.cwd);
|
|
5491
5701
|
const entry = findTool(name);
|
|
5492
5702
|
if (!entry) return unknownTool(name, io, opts.bin);
|
|
5493
|
-
|
|
5703
|
+
const hostOnly = entry.installArgv === void 0 && entry.hostInstallArgv !== void 0;
|
|
5704
|
+
const refuseContainer = () => {
|
|
5494
5705
|
io.stderr(`${opts.bin} tool: ${entry.id} cannot be installed by this command.
|
|
5495
5706
|
|
|
5496
5707
|
`);
|
|
@@ -5501,12 +5712,16 @@ async function runToolInstall(name, opts, io) {
|
|
|
5501
5712
|
${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
5502
5713
|
`);
|
|
5503
5714
|
return 1;
|
|
5715
|
+
};
|
|
5716
|
+
if (entry.installArgv === void 0 && (!hostOnly || runningInsideSandbox(opts.env ?? process.env))) {
|
|
5717
|
+
return refuseContainer();
|
|
5504
5718
|
}
|
|
5505
5719
|
const target = targetFor2(root, run, opts.env);
|
|
5506
5720
|
const status = await resolveTargetStatus(target);
|
|
5507
|
-
if (!isReachable2(status)) {
|
|
5721
|
+
if (!isReachable2(status) || hostOnly && opts.host === true) {
|
|
5508
5722
|
return await installOnHost2(entry, opts, io, run, status);
|
|
5509
5723
|
}
|
|
5724
|
+
if (entry.installArgv === void 0) return refuseContainer();
|
|
5510
5725
|
const already = await probeInstalled2(target, entry, "sandbox");
|
|
5511
5726
|
if (already === true) {
|
|
5512
5727
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
@@ -5914,7 +6129,8 @@ Flags:
|
|
|
5914
6129
|
--yes Non-interactive: keep every default and ask nothing
|
|
5915
6130
|
--version <X.Y.Z>
|
|
5916
6131
|
Run the official release image ghcr.io/mifunedev/agro:<X.Y.Z>
|
|
5917
|
-
(implies --image). A leading v
|
|
6132
|
+
(implies --image). A leading v and a -<channel>.<n>
|
|
6133
|
+
pre-release suffix are accepted
|
|
5918
6134
|
--image Run the prebuilt image instead of building (implies
|
|
5919
6135
|
--no-build). The ref resolves first-match: --version or
|
|
5920
6136
|
--image=<ref> > ${stateNames(bin).envPrefix}SANDBOX_IMAGE >
|
|
@@ -5980,7 +6196,8 @@ function printWorkspaceHelp(bin = AGRO_PRODUCT.bin) {
|
|
|
5980
6196
|
process.stdout.write(`${bin} workspace \u2014 Create and list host AGRO workspaces
|
|
5981
6197
|
|
|
5982
6198
|
Usage:
|
|
5983
|
-
${bin} workspace create [<name>]
|
|
6199
|
+
${bin} workspace create [<name>] [--ref <ref>]
|
|
6200
|
+
Clone the AGRO repository into a registry entry
|
|
5984
6201
|
${bin} workspace list List every host workspace
|
|
5985
6202
|
|
|
5986
6203
|
A host workspace is an AGRO checkout under \`~/.agro/workspaces/<name>\`. \`create\`
|
|
@@ -5995,6 +6212,9 @@ root as \`harnessRoot\`. \`list\` marks the recorded root.
|
|
|
5995
6212
|
Flags:
|
|
5996
6213
|
--json Machine-readable output
|
|
5997
6214
|
--path <dir> Create the workspace at a directory outside the registry
|
|
6215
|
+
--ref <ref> Clone this branch or tag instead of the default branch (create
|
|
6216
|
+
only). A missing ref exits 1 and leaves no target directory.
|
|
6217
|
+
An existing checkout is reused as is.
|
|
5998
6218
|
`);
|
|
5999
6219
|
}
|
|
6000
6220
|
function printGatewayHelp(bin = AGRO_PRODUCT.bin) {
|
|
@@ -6498,16 +6718,24 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6498
6718
|
}
|
|
6499
6719
|
const positionals = [];
|
|
6500
6720
|
let expectPath = false;
|
|
6721
|
+
let expectRef = false;
|
|
6501
6722
|
for (const token of rest) {
|
|
6502
6723
|
if (expectPath) {
|
|
6503
6724
|
expectPath = false;
|
|
6504
6725
|
args.path = token;
|
|
6726
|
+
} else if (expectRef) {
|
|
6727
|
+
expectRef = false;
|
|
6728
|
+
args.ref = token;
|
|
6505
6729
|
} else if (token === "--json") {
|
|
6506
6730
|
args.json = true;
|
|
6507
6731
|
} else if (token === "--path") {
|
|
6508
6732
|
expectPath = true;
|
|
6509
6733
|
} else if (token.startsWith("--path=")) {
|
|
6510
6734
|
args.path = token.slice("--path=".length);
|
|
6735
|
+
} else if (token === "--ref") {
|
|
6736
|
+
expectRef = true;
|
|
6737
|
+
} else if (token.startsWith("--ref=")) {
|
|
6738
|
+
args.ref = token.slice("--ref=".length);
|
|
6511
6739
|
} else if (token.startsWith("-")) {
|
|
6512
6740
|
return { ok: false, error: `${bin} workspace: unknown flag "${token}"` };
|
|
6513
6741
|
} else {
|
|
@@ -6517,6 +6745,9 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6517
6745
|
if (expectPath || args.path === "") {
|
|
6518
6746
|
return { ok: false, error: `${bin} workspace: --path requires a directory` };
|
|
6519
6747
|
}
|
|
6748
|
+
if (expectRef || args.ref === "") {
|
|
6749
|
+
return { ok: false, error: `${bin} workspace: --ref requires a branch or tag` };
|
|
6750
|
+
}
|
|
6520
6751
|
const [sub, name, ...extra] = positionals;
|
|
6521
6752
|
if (sub !== "create" && sub !== "list") {
|
|
6522
6753
|
return {
|
|
@@ -6534,6 +6765,9 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6534
6765
|
if (sub === "list" && args.path !== void 0) {
|
|
6535
6766
|
return { ok: false, error: `${bin} workspace list: --path applies to create only` };
|
|
6536
6767
|
}
|
|
6768
|
+
if (sub === "list" && args.ref !== void 0) {
|
|
6769
|
+
return { ok: false, error: `${bin} workspace list: --ref applies to create only` };
|
|
6770
|
+
}
|
|
6537
6771
|
if (args.path !== void 0 && name !== void 0) {
|
|
6538
6772
|
return {
|
|
6539
6773
|
ok: false,
|
|
@@ -6724,7 +6958,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
6724
6958
|
async function runWithRemoteSource(hooks, fn) {
|
|
6725
6959
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
6726
6960
|
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join17(tmpdir4(), "oh-remote-")));
|
|
6727
|
-
const rm = hooks.rm ?? ((dir) =>
|
|
6961
|
+
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
6728
6962
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
6729
6963
|
const checkoutDir = mkdtemp();
|
|
6730
6964
|
try {
|
|
@@ -6986,7 +7220,12 @@ async function main(argv) {
|
|
|
6986
7220
|
}
|
|
6987
7221
|
return await runWorkspaceCreate(
|
|
6988
7222
|
a.name,
|
|
6989
|
-
{
|
|
7223
|
+
{
|
|
7224
|
+
bin,
|
|
7225
|
+
json: a.json,
|
|
7226
|
+
...a.path !== void 0 ? { path: a.path } : {},
|
|
7227
|
+
...a.ref !== void 0 ? { ref: a.ref } : {}
|
|
7228
|
+
},
|
|
6990
7229
|
io
|
|
6991
7230
|
);
|
|
6992
7231
|
}
|