@mifune/agro 0.15.0-minimal.1 → 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 +289 -21
- 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
|
|
@@ -1479,6 +1479,11 @@ fi
|
|
|
1479
1479
|
# shellcheck source=paths.sh
|
|
1480
1480
|
. "$PATHS_SH"
|
|
1481
1481
|
|
|
1482
|
+
MIGRATOR="$SCRIPT_DIR/migrate-harness-yaml.sh"
|
|
1483
|
+
if [ -f "$REPO_DIR/harness.yaml" ] && [ -f "$MIGRATOR" ]; then
|
|
1484
|
+
sh "$MIGRATOR" "$REPO_DIR" >&2 || true
|
|
1485
|
+
fi
|
|
1486
|
+
|
|
1482
1487
|
ENV_FILE="$REPO_DIR/.env"
|
|
1483
1488
|
[ -f "$ENV_FILE" ] || ENV_FILE="$REPO_DIR/.devcontainer/.env"
|
|
1484
1489
|
|
|
@@ -2217,7 +2222,7 @@ function resolveExecutionTarget(opts) {
|
|
|
2217
2222
|
}
|
|
2218
2223
|
|
|
2219
2224
|
// src/lib/version.ts
|
|
2220
|
-
var AGRO_VERSION = true ? "0.15.0
|
|
2225
|
+
var AGRO_VERSION = true ? "0.15.0" : "0.0.0-dev";
|
|
2221
2226
|
var OFFICIAL_IMAGE = "ghcr.io/mifunedev/agro";
|
|
2222
2227
|
var RELEASE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z][a-z0-9]*\.[0-9]+)?$/;
|
|
2223
2228
|
function parseReleaseVersion(value) {
|
|
@@ -4205,14 +4210,15 @@ function assertCloned(result, root) {
|
|
|
4205
4210
|
throw new Error(`could not clone ${AGRO_REPO_URL} into ${root} (exit status ${String(status)})`);
|
|
4206
4211
|
}
|
|
4207
4212
|
}
|
|
4208
|
-
function cloneInto(target, run, root) {
|
|
4209
|
-
|
|
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);
|
|
4210
4216
|
}
|
|
4211
|
-
function cloneThroughStaging(root, run) {
|
|
4217
|
+
function cloneThroughStaging(root, run, ref) {
|
|
4212
4218
|
const staging = mkdtempSync4(join13(dirname9(root), "agro-clone-"));
|
|
4213
4219
|
try {
|
|
4214
4220
|
const checkout = join13(staging, "agro");
|
|
4215
|
-
cloneInto(checkout, run, root);
|
|
4221
|
+
cloneInto(checkout, run, root, ref);
|
|
4216
4222
|
for (const name of readdirSync3(checkout)) {
|
|
4217
4223
|
const destination = join13(root, name);
|
|
4218
4224
|
if (existsSync17(destination)) {
|
|
@@ -4224,13 +4230,13 @@ function cloneThroughStaging(root, run) {
|
|
|
4224
4230
|
rmSync4(staging, { recursive: true, force: true });
|
|
4225
4231
|
}
|
|
4226
4232
|
}
|
|
4227
|
-
function ensureHostWorkspace(path4, run = spawnRunner) {
|
|
4233
|
+
function ensureHostWorkspace(path4, run = spawnRunner, ref) {
|
|
4228
4234
|
const root = resolve9(path4);
|
|
4229
4235
|
if (existsSync17(join13(root, ".git"))) return { root, action: "reused" };
|
|
4230
4236
|
const stats = statSync5(root, { throwIfNoEntry: false });
|
|
4231
4237
|
if (stats === void 0) {
|
|
4232
4238
|
mkdirSync9(dirname9(root), { recursive: true });
|
|
4233
|
-
cloneInto(root, run, root);
|
|
4239
|
+
cloneInto(root, run, root, ref);
|
|
4234
4240
|
return { root, action: "cloned" };
|
|
4235
4241
|
}
|
|
4236
4242
|
if (!stats.isDirectory()) {
|
|
@@ -4242,7 +4248,7 @@ function ensureHostWorkspace(path4, run = spawnRunner) {
|
|
|
4242
4248
|
`${root} holds files but no git checkout (${blocking.sort().join(", ")}) \u2014 choose an empty directory or an existing AGRO checkout`
|
|
4243
4249
|
);
|
|
4244
4250
|
}
|
|
4245
|
-
cloneThroughStaging(root, run);
|
|
4251
|
+
cloneThroughStaging(root, run, ref);
|
|
4246
4252
|
return { root, action: "cloned" };
|
|
4247
4253
|
}
|
|
4248
4254
|
function listHostWorkspaces(env, home) {
|
|
@@ -4431,6 +4437,19 @@ async function runHarnessStatus(name, opts, io) {
|
|
|
4431
4437
|
function hermesTargetRoot(target) {
|
|
4432
4438
|
return target.kind === "docker-compose" ? "/home/sandbox/harness" : target.workspace.targetRoot;
|
|
4433
4439
|
}
|
|
4440
|
+
async function reconcileHermes(target, io, bin, user, root = hermesTargetRoot(target)) {
|
|
4441
|
+
const result = await target.exec({
|
|
4442
|
+
argv: remoteControlDirScript(root, "scripts/link-providers.sh", ["--init", "--hermes-only"]),
|
|
4443
|
+
env: agroEnvPair("PROJECT_ROOT", root),
|
|
4444
|
+
...user ? { user } : {},
|
|
4445
|
+
stdio: "inherit"
|
|
4446
|
+
});
|
|
4447
|
+
if (result.exitCode !== 0) {
|
|
4448
|
+
io.stderr(`${bin} harness: Hermes integration failed (exit ${result.exitCode}); no installation success reported.
|
|
4449
|
+
`);
|
|
4450
|
+
}
|
|
4451
|
+
return result.exitCode;
|
|
4452
|
+
}
|
|
4434
4453
|
function unreachableReason(status) {
|
|
4435
4454
|
return runtimeIsAbsent(status) ? "No container runtime is on PATH." : `The sandbox is not running (${status}).`;
|
|
4436
4455
|
}
|
|
@@ -4513,7 +4532,7 @@ async function installOnHost(entry, opts, io, run, status) {
|
|
|
4513
4532
|
});
|
|
4514
4533
|
if (linked.exitCode !== 0) {
|
|
4515
4534
|
io.stderr(
|
|
4516
|
-
`${bin} harness: could not link provider
|
|
4535
|
+
`${bin} harness: could not link provider skills in ${root} (exit ${linked.exitCode}); nothing was installed.
|
|
4517
4536
|
Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
4518
4537
|
`
|
|
4519
4538
|
);
|
|
@@ -4524,6 +4543,10 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
4524
4543
|
...agroEnvPair("PROJECT_ROOT", root),
|
|
4525
4544
|
HERMES_HOME: `${root}/.hermes`
|
|
4526
4545
|
} : void 0;
|
|
4546
|
+
if (hermes) {
|
|
4547
|
+
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4548
|
+
if (code !== 0) return code;
|
|
4549
|
+
}
|
|
4527
4550
|
if (await probeInstalled(target, entry, prefix, void 0, installEnv) === true) {
|
|
4528
4551
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
4529
4552
|
`);
|
|
@@ -4554,6 +4577,8 @@ Run: bash ${root}/.agro/scripts/link-providers.sh --init
|
|
|
4554
4577
|
`);
|
|
4555
4578
|
return 1;
|
|
4556
4579
|
}
|
|
4580
|
+
const code = await reconcileHermes(target, io, bin, void 0, root);
|
|
4581
|
+
if (code !== 0) return code;
|
|
4557
4582
|
}
|
|
4558
4583
|
const receipt = {
|
|
4559
4584
|
prefix,
|
|
@@ -4707,6 +4732,10 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4707
4732
|
...agroEnvPair("PROJECT_ROOT", hermesTargetRoot(target)),
|
|
4708
4733
|
HERMES_HOME: `${hermesTargetRoot(target)}/.hermes`
|
|
4709
4734
|
} : void 0;
|
|
4735
|
+
if (hermes) {
|
|
4736
|
+
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4737
|
+
if (code !== 0) return code;
|
|
4738
|
+
}
|
|
4710
4739
|
const already = await probeInstalled(target, entry, SANDBOX_HARNESS_PREFIX, "sandbox", installEnv);
|
|
4711
4740
|
if (already === true) {
|
|
4712
4741
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
@@ -4736,6 +4765,8 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4736
4765
|
`);
|
|
4737
4766
|
return 1;
|
|
4738
4767
|
}
|
|
4768
|
+
const code = await reconcileHermes(target, io, opts.bin, "sandbox");
|
|
4769
|
+
if (code !== 0) return code;
|
|
4739
4770
|
}
|
|
4740
4771
|
io.stdout(`${entry.id}: installed \u2014 see ${sourceDocsUrl(entry.docsPath)} for authentication
|
|
4741
4772
|
`);
|
|
@@ -4743,7 +4774,7 @@ async function runHarnessInstall(name, opts, io) {
|
|
|
4743
4774
|
}
|
|
4744
4775
|
|
|
4745
4776
|
// src/commands/workspace.ts
|
|
4746
|
-
import { existsSync as existsSync19 } from "node:fs";
|
|
4777
|
+
import { existsSync as existsSync19, rmSync as rmSync5 } from "node:fs";
|
|
4747
4778
|
import { homedir as homedir4 } from "node:os";
|
|
4748
4779
|
import { join as join15, resolve as resolve10 } from "node:path";
|
|
4749
4780
|
function homeOf2(opts) {
|
|
@@ -4791,15 +4822,25 @@ async function runWorkspaceCreate(name, opts, io) {
|
|
|
4791
4822
|
return 1;
|
|
4792
4823
|
}
|
|
4793
4824
|
let action;
|
|
4825
|
+
const preexisting = existsSync19(root);
|
|
4794
4826
|
try {
|
|
4795
4827
|
if (!opts.json && !existsSync19(join15(root, ".git"))) {
|
|
4796
|
-
|
|
4828
|
+
const at = opts.ref !== void 0 ? ` at ${opts.ref}` : "";
|
|
4829
|
+
io.stdout(`cloning ${AGRO_REPO_URL}${at} into ${root}\u2026
|
|
4797
4830
|
`);
|
|
4798
4831
|
}
|
|
4799
|
-
const created = ensureHostWorkspace(root, run);
|
|
4832
|
+
const created = ensureHostWorkspace(root, run, opts.ref);
|
|
4800
4833
|
root = created.root;
|
|
4801
4834
|
action = created.action;
|
|
4802
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
|
+
}
|
|
4803
4844
|
io.stderr(`${bin} workspace: ${messageOf2(err)}
|
|
4804
4845
|
`);
|
|
4805
4846
|
return 1;
|
|
@@ -4854,7 +4895,7 @@ async function runWorkspaceList(opts, io) {
|
|
|
4854
4895
|
|
|
4855
4896
|
// src/commands/tool.ts
|
|
4856
4897
|
import { existsSync as existsSync20 } from "node:fs";
|
|
4857
|
-
import { homedir as homedir5 } from "node:os";
|
|
4898
|
+
import { homedir as homedir5, userInfo as userInfo2 } from "node:os";
|
|
4858
4899
|
import { delimiter as delimiter3, join as join16 } from "node:path";
|
|
4859
4900
|
|
|
4860
4901
|
// src/lib/tools/catalog.ts
|
|
@@ -5076,6 +5117,178 @@ install -d -m 0700 "$HOME/.tailscale"`
|
|
|
5076
5117
|
`${HARNESS_PREFIX_TOKEN}/bin/tailscaled`
|
|
5077
5118
|
]),
|
|
5078
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
|
|
5079
5292
|
})
|
|
5080
5293
|
]);
|
|
5081
5294
|
function findTool(id) {
|
|
@@ -5138,6 +5351,10 @@ function homeOf3(opts) {
|
|
|
5138
5351
|
function platformOf(opts) {
|
|
5139
5352
|
return opts.platform ?? process.platform;
|
|
5140
5353
|
}
|
|
5354
|
+
function isRootUser(opts) {
|
|
5355
|
+
const identity = opts.identity ?? (() => ({ name: userInfo2().username, uid: userInfo2().uid }));
|
|
5356
|
+
return identity().uid === 0;
|
|
5357
|
+
}
|
|
5141
5358
|
function isInteractive2(opts) {
|
|
5142
5359
|
return opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
5143
5360
|
}
|
|
@@ -5176,6 +5393,7 @@ function rowOf(entry, installed, version, location) {
|
|
|
5176
5393
|
version,
|
|
5177
5394
|
installable: entry.installArgv !== void 0,
|
|
5178
5395
|
hostCapable: entry.hostCapable,
|
|
5396
|
+
hostRoot: entry.hostInstallUser === "root",
|
|
5179
5397
|
location,
|
|
5180
5398
|
docs: sourceDocsUrl(entry.docsPath)
|
|
5181
5399
|
};
|
|
@@ -5223,13 +5441,20 @@ function cell2(value, absent) {
|
|
|
5223
5441
|
function renderTable3(collected, io, bin) {
|
|
5224
5442
|
const rows = collected.rows;
|
|
5225
5443
|
const header2 = ["TOOL", "KIND", "INSTALLED"];
|
|
5226
|
-
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
|
+
]);
|
|
5227
5449
|
const widths = header2.map(
|
|
5228
5450
|
(h, i) => Math.max(h.length, ...body.map((b) => b[i].length))
|
|
5229
5451
|
);
|
|
5230
5452
|
const line = (cols) => cols.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd() + "\n";
|
|
5231
5453
|
io.stdout(line(header2));
|
|
5232
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
|
+
}
|
|
5233
5458
|
if (rows.some((r) => r.location === "host")) {
|
|
5234
5459
|
io.stdout(
|
|
5235
5460
|
`
|
|
@@ -5409,11 +5634,21 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5409
5634
|
}
|
|
5410
5635
|
return 0;
|
|
5411
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
|
+
}
|
|
5412
5646
|
if (!await confirmDownload(entry, opts, io, true)) return 1;
|
|
5413
5647
|
io.stdout(`installing ${entry.title} on the host\u2026
|
|
5414
5648
|
`);
|
|
5649
|
+
const argv = resolveToolInstallArgv(entry, true);
|
|
5415
5650
|
const r = await target.exec({
|
|
5416
|
-
argv:
|
|
5651
|
+
argv: sudo ? ["sudo", "-n", "--", ...argv] : argv,
|
|
5417
5652
|
stdio: "inherit",
|
|
5418
5653
|
env: installEnv
|
|
5419
5654
|
});
|
|
@@ -5445,6 +5680,11 @@ Every tool installer in the catalog is Debian-specific. Install ${entry.id} with
|
|
|
5445
5680
|
`);
|
|
5446
5681
|
return 1;
|
|
5447
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
|
+
}
|
|
5448
5688
|
io.stdout(`${entry.id}: installed at ${prefix} \u2014 see ${sourceDocsUrl(entry.docsPath)}
|
|
5449
5689
|
`);
|
|
5450
5690
|
if (!onPath2(prefix, env)) {
|
|
@@ -5460,7 +5700,8 @@ async function runToolInstall(name, opts, io) {
|
|
|
5460
5700
|
const root = resolveProjectRoot(opts.cwd);
|
|
5461
5701
|
const entry = findTool(name);
|
|
5462
5702
|
if (!entry) return unknownTool(name, io, opts.bin);
|
|
5463
|
-
|
|
5703
|
+
const hostOnly = entry.installArgv === void 0 && entry.hostInstallArgv !== void 0;
|
|
5704
|
+
const refuseContainer = () => {
|
|
5464
5705
|
io.stderr(`${opts.bin} tool: ${entry.id} cannot be installed by this command.
|
|
5465
5706
|
|
|
5466
5707
|
`);
|
|
@@ -5471,12 +5712,16 @@ async function runToolInstall(name, opts, io) {
|
|
|
5471
5712
|
${installableToolIds().map((t) => ` ${t}`).join("\n")}
|
|
5472
5713
|
`);
|
|
5473
5714
|
return 1;
|
|
5715
|
+
};
|
|
5716
|
+
if (entry.installArgv === void 0 && (!hostOnly || runningInsideSandbox(opts.env ?? process.env))) {
|
|
5717
|
+
return refuseContainer();
|
|
5474
5718
|
}
|
|
5475
5719
|
const target = targetFor2(root, run, opts.env);
|
|
5476
5720
|
const status = await resolveTargetStatus(target);
|
|
5477
|
-
if (!isReachable2(status)) {
|
|
5721
|
+
if (!isReachable2(status) || hostOnly && opts.host === true) {
|
|
5478
5722
|
return await installOnHost2(entry, opts, io, run, status);
|
|
5479
5723
|
}
|
|
5724
|
+
if (entry.installArgv === void 0) return refuseContainer();
|
|
5480
5725
|
const already = await probeInstalled2(target, entry, "sandbox");
|
|
5481
5726
|
if (already === true) {
|
|
5482
5727
|
io.stdout(`${entry.id}: already installed (${entry.binary})
|
|
@@ -5951,7 +6196,8 @@ function printWorkspaceHelp(bin = AGRO_PRODUCT.bin) {
|
|
|
5951
6196
|
process.stdout.write(`${bin} workspace \u2014 Create and list host AGRO workspaces
|
|
5952
6197
|
|
|
5953
6198
|
Usage:
|
|
5954
|
-
${bin} workspace create [<name>]
|
|
6199
|
+
${bin} workspace create [<name>] [--ref <ref>]
|
|
6200
|
+
Clone the AGRO repository into a registry entry
|
|
5955
6201
|
${bin} workspace list List every host workspace
|
|
5956
6202
|
|
|
5957
6203
|
A host workspace is an AGRO checkout under \`~/.agro/workspaces/<name>\`. \`create\`
|
|
@@ -5966,6 +6212,9 @@ root as \`harnessRoot\`. \`list\` marks the recorded root.
|
|
|
5966
6212
|
Flags:
|
|
5967
6213
|
--json Machine-readable output
|
|
5968
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.
|
|
5969
6218
|
`);
|
|
5970
6219
|
}
|
|
5971
6220
|
function printGatewayHelp(bin = AGRO_PRODUCT.bin) {
|
|
@@ -6469,16 +6718,24 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6469
6718
|
}
|
|
6470
6719
|
const positionals = [];
|
|
6471
6720
|
let expectPath = false;
|
|
6721
|
+
let expectRef = false;
|
|
6472
6722
|
for (const token of rest) {
|
|
6473
6723
|
if (expectPath) {
|
|
6474
6724
|
expectPath = false;
|
|
6475
6725
|
args.path = token;
|
|
6726
|
+
} else if (expectRef) {
|
|
6727
|
+
expectRef = false;
|
|
6728
|
+
args.ref = token;
|
|
6476
6729
|
} else if (token === "--json") {
|
|
6477
6730
|
args.json = true;
|
|
6478
6731
|
} else if (token === "--path") {
|
|
6479
6732
|
expectPath = true;
|
|
6480
6733
|
} else if (token.startsWith("--path=")) {
|
|
6481
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);
|
|
6482
6739
|
} else if (token.startsWith("-")) {
|
|
6483
6740
|
return { ok: false, error: `${bin} workspace: unknown flag "${token}"` };
|
|
6484
6741
|
} else {
|
|
@@ -6488,6 +6745,9 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6488
6745
|
if (expectPath || args.path === "") {
|
|
6489
6746
|
return { ok: false, error: `${bin} workspace: --path requires a directory` };
|
|
6490
6747
|
}
|
|
6748
|
+
if (expectRef || args.ref === "") {
|
|
6749
|
+
return { ok: false, error: `${bin} workspace: --ref requires a branch or tag` };
|
|
6750
|
+
}
|
|
6491
6751
|
const [sub, name, ...extra] = positionals;
|
|
6492
6752
|
if (sub !== "create" && sub !== "list") {
|
|
6493
6753
|
return {
|
|
@@ -6505,6 +6765,9 @@ function parseWorkspaceArgs(rest, bin = AGRO_PRODUCT.bin) {
|
|
|
6505
6765
|
if (sub === "list" && args.path !== void 0) {
|
|
6506
6766
|
return { ok: false, error: `${bin} workspace list: --path applies to create only` };
|
|
6507
6767
|
}
|
|
6768
|
+
if (sub === "list" && args.ref !== void 0) {
|
|
6769
|
+
return { ok: false, error: `${bin} workspace list: --ref applies to create only` };
|
|
6770
|
+
}
|
|
6508
6771
|
if (args.path !== void 0 && name !== void 0) {
|
|
6509
6772
|
return {
|
|
6510
6773
|
ok: false,
|
|
@@ -6695,7 +6958,7 @@ function readPayloadVersion(checkoutDir) {
|
|
|
6695
6958
|
async function runWithRemoteSource(hooks, fn) {
|
|
6696
6959
|
const fetch2 = hooks.fetch ?? fetchRemoteSource;
|
|
6697
6960
|
const mkdtemp = hooks.mkdtemp ?? (() => mkdtempSync5(join17(tmpdir4(), "oh-remote-")));
|
|
6698
|
-
const rm = hooks.rm ?? ((dir) =>
|
|
6961
|
+
const rm = hooks.rm ?? ((dir) => rmSync6(dir, { recursive: true, force: true }));
|
|
6699
6962
|
const out = hooks.stdout ?? ((s) => process.stdout.write(s));
|
|
6700
6963
|
const checkoutDir = mkdtemp();
|
|
6701
6964
|
try {
|
|
@@ -6957,7 +7220,12 @@ async function main(argv) {
|
|
|
6957
7220
|
}
|
|
6958
7221
|
return await runWorkspaceCreate(
|
|
6959
7222
|
a.name,
|
|
6960
|
-
{
|
|
7223
|
+
{
|
|
7224
|
+
bin,
|
|
7225
|
+
json: a.json,
|
|
7226
|
+
...a.path !== void 0 ? { path: a.path } : {},
|
|
7227
|
+
...a.ref !== void 0 ? { ref: a.ref } : {}
|
|
7228
|
+
},
|
|
6961
7229
|
io
|
|
6962
7230
|
);
|
|
6963
7231
|
}
|
package/package.json
CHANGED