agent-dev-env 0.1.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/LICENSE +21 -0
- package/dist/assets/bridge/bridge.js +202 -0
- package/dist/assets/guest/guest-agent-mac.js +586 -0
- package/dist/assets/guest/guest-agent-ubuntu.js +481 -0
- package/dist/assets/guest/guest-agent-windows.js +466 -0
- package/dist/assets/images/mac/CHANGELOG.md +265 -0
- package/dist/assets/images/mac/README.md +63 -0
- package/dist/assets/images/mac/sandbox.pkr.hcl +468 -0
- package/dist/assets/images/mac/vars/sandbox-macos-tahoe.pkrvars.hcl +39 -0
- package/dist/assets/images/ubuntu-arm64-vmware/CHANGELOG.md +239 -0
- package/dist/assets/images/ubuntu-arm64-vmware/README.md +177 -0
- package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/meta-data +2 -0
- package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/user-data +34 -0
- package/dist/assets/images/ubuntu-arm64-vmware/sandbox.pkr.hcl +776 -0
- package/dist/assets/images/ubuntu-arm64-vmware/vars/sandbox-ubuntu-24-04-arm64-vmware.pkrvars.hcl +71 -0
- package/dist/assets/images/windows-arm64-qemu/CHANGELOG.md +209 -0
- package/dist/assets/images/windows-arm64-qemu/README.md +142 -0
- package/dist/assets/images/windows-arm64-qemu/autounattend.xml +325 -0
- package/dist/assets/images/windows-arm64-qemu/qemu-with-tpm.sh +170 -0
- package/dist/assets/images/windows-arm64-qemu/sandbox.pkr.hcl +1012 -0
- package/dist/assets/images/windows-arm64-qemu/vars/sandbox-windows-11-arm64-qemu.pkrvars.hcl +83 -0
- package/dist/assets/images/windows-arm64-vmware/CHANGELOG.md +270 -0
- package/dist/assets/images/windows-arm64-vmware/README.md +171 -0
- package/dist/assets/images/windows-arm64-vmware/autounattend.xml +311 -0
- package/dist/assets/images/windows-arm64-vmware/sandbox.pkr.hcl +1036 -0
- package/dist/assets/images/windows-arm64-vmware/vars/sandbox-windows-11-arm64-vmware.pkrvars.hcl +83 -0
- package/dist/assets/rules/agent-rules-linux.md +68 -0
- package/dist/assets/rules/agent-rules.md +71 -0
- package/dist/assets/watchdog/watch-build-ocr.swift +40 -0
- package/dist/assets/watchdog/watch-build.py +275 -0
- package/dist/cli.js +52 -0
- package/dist/commands/delete.js +179 -0
- package/dist/commands/doctor.js +165 -0
- package/dist/commands/list.js +38 -0
- package/dist/commands/not-yet.js +15 -0
- package/dist/commands/register.js +150 -0
- package/dist/commands/run.js +65 -0
- package/dist/commands/status.js +142 -0
- package/dist/commands/stop.js +186 -0
- package/dist/commands/sync.js +98 -0
- package/dist/lib/exec.js +250 -0
- package/dist/lib/ghcr.js +82 -0
- package/dist/lib/git.js +100 -0
- package/dist/lib/logger.js +87 -0
- package/dist/lib/network.js +63 -0
- package/dist/lib/paths.js +116 -0
- package/dist/lib/platform.js +87 -0
- package/dist/lib/prompt.js +66 -0
- package/dist/lib/qemu.js +373 -0
- package/dist/lib/regex.js +9 -0
- package/dist/lib/ssh.js +230 -0
- package/dist/lib/tart.js +210 -0
- package/dist/lib/template.js +35 -0
- package/dist/lib/vars.js +99 -0
- package/dist/lib/vmrun.js +334 -0
- package/dist/lifecycle/build-macos.js +23 -0
- package/dist/lifecycle/build-qemu.js +204 -0
- package/dist/lifecycle/build-shared.js +355 -0
- package/dist/lifecycle/build-ubuntu.js +184 -0
- package/dist/lifecycle/build-watchdog.js +146 -0
- package/dist/lifecycle/build-windows-vmware.js +112 -0
- package/dist/lifecycle/build.js +55 -0
- package/dist/lifecycle/catalog.js +165 -0
- package/dist/lifecycle/deploy.js +161 -0
- package/dist/lifecycle/tag.js +77 -0
- package/dist/lifecycle/watch-build.js +64 -0
- package/dist/runners/bridges.js +153 -0
- package/dist/runners/framework.js +66 -0
- package/dist/runners/macos-bridges.js +143 -0
- package/dist/runners/macos-guest.js +76 -0
- package/dist/runners/macos-rules.js +77 -0
- package/dist/runners/macos-summary.js +145 -0
- package/dist/runners/macos.js +258 -0
- package/dist/runners/openchamber.js +60 -0
- package/dist/runners/options.js +56 -0
- package/dist/runners/qemu-image.js +183 -0
- package/dist/runners/rules.js +49 -0
- package/dist/runners/ubuntu-bridges.js +154 -0
- package/dist/runners/ubuntu-guest.js +136 -0
- package/dist/runners/ubuntu-image.js +26 -0
- package/dist/runners/ubuntu-rules.js +86 -0
- package/dist/runners/ubuntu-shared.js +65 -0
- package/dist/runners/ubuntu-summary.js +141 -0
- package/dist/runners/ubuntu.js +117 -0
- package/dist/runners/vmware-common.js +158 -0
- package/dist/runners/vmware-image.js +242 -0
- package/dist/runners/windows-autologon.js +119 -0
- package/dist/runners/windows-bridges.js +136 -0
- package/dist/runners/windows-guest.js +213 -0
- package/dist/runners/windows-image.js +23 -0
- package/dist/runners/windows-qemu-summary.js +85 -0
- package/dist/runners/windows-qemu.js +189 -0
- package/dist/runners/windows-shared.js +68 -0
- package/dist/runners/windows-summary.js +104 -0
- package/dist/runners/windows.js +96 -0
- package/dist/settings/common.js +107 -0
- package/dist/settings/macos-copy.js +223 -0
- package/dist/settings/macos.js +41 -0
- package/dist/settings/ubuntu-copy.js +223 -0
- package/dist/settings/ubuntu.js +68 -0
- package/package.json +52 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
packer {
|
|
2
|
+
required_version = ">= 1.10.0"
|
|
3
|
+
|
|
4
|
+
required_plugins {
|
|
5
|
+
vmware = {
|
|
6
|
+
version = ">= 2.1.5"
|
|
7
|
+
source = "github.com/vmware/vmware"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Variables
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
#
|
|
16
|
+
# The Ubuntu build reuses the vmware-iso mechanics of
|
|
17
|
+
# images/windows-arm64-vmware/sandbox.pkr.hcl (Fusion on Apple Silicon,
|
|
18
|
+
# NAT + vmxnet3 + EFI, tar.gz OCI artifact) with a Linux twist: it is
|
|
19
|
+
# installed from the Ubuntu Server ARM64 ISO via Subiquity autoinstall
|
|
20
|
+
# (cloud-init seed served over HTTP), provisioned over SSH, and uses
|
|
21
|
+
# open-vm-tools from the Ubuntu archive instead of a VMware Tools ISO —
|
|
22
|
+
# Fusion ships no Linux tools for arm64 guests (open-vm-tools are the
|
|
23
|
+
# only in-guest tools available for arm64 Linux VMs).
|
|
24
|
+
|
|
25
|
+
variable "ubuntu_version" {
|
|
26
|
+
type = string
|
|
27
|
+
description = "Ubuntu guest version, e.g. '24-04'; part of the image name (sandbox-ubuntu-<ubuntu_version>-vmware)."
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
variable "image_version" {
|
|
31
|
+
type = string
|
|
32
|
+
description = "Semantic version this image is published under; bump it + add a CHANGELOG.md entry per release."
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# --- Build layout ---
|
|
36
|
+
#
|
|
37
|
+
# Built images and their scratch land in a top-level build/ directory:
|
|
38
|
+
# build/ubuntu-arm64-vmware/output (Packer's output_directory). The
|
|
39
|
+
# agent-dev-env CLI build flow computes the directory and passes it
|
|
40
|
+
# in; the default keeps a bare `packer build` from the platform dir working.
|
|
41
|
+
|
|
42
|
+
variable "build_dir" {
|
|
43
|
+
type = string
|
|
44
|
+
default = "."
|
|
45
|
+
description = "Per-image build directory: <build_dir>/output holds the built VM. Set by `agent-dev-env build` to build/ubuntu-arm64-vmware/."
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# --- Ubuntu install ISO (bring-your-own, see images/ubuntu-arm64-vmware/README.md) ---
|
|
49
|
+
|
|
50
|
+
variable "iso_path" {
|
|
51
|
+
type = string
|
|
52
|
+
description = "Absolute path to the Ubuntu Server 24.04 ARM64 ISO. Set by the agent-dev-env CLI build flow (PKR_VAR_iso_path); the ISO is not redistributable so it cannot live in the repo. Required — validate with `-var iso_path=/path/to/iso`."
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
variable "iso_sha256" {
|
|
56
|
+
type = string
|
|
57
|
+
default = ""
|
|
58
|
+
description = "SHA256 of the Ubuntu ISO as published in the release SHA256SUMS. Read by the agent-dev-env CLI build flow for verification, not by Packer itself (iso_checksum is 'none')."
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# --- SSH credentials (baked into autoinstall/user-data — keep in sync) ---
|
|
62
|
+
|
|
63
|
+
variable "ssh_username" {
|
|
64
|
+
type = string
|
|
65
|
+
default = "admin"
|
|
66
|
+
description = "User created by the autoinstall seed; used for SSH provisioning and as the sandbox login."
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
variable "ssh_password" {
|
|
70
|
+
type = string
|
|
71
|
+
default = "sandbox1"
|
|
72
|
+
sensitive = true
|
|
73
|
+
description = "Password of the provisioning account; must match the crypt hash in autoinstall/user-data."
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# --- Toolchain versions ---
|
|
77
|
+
#
|
|
78
|
+
# Pinned by direct download (like the Windows image's Chrome CfT approach):
|
|
79
|
+
# the URL and SHA256 of each artifact are in the vars file; apt-installed
|
|
80
|
+
# tools (gcc, git, python3, ...) come from the Ubuntu archive. Node.js is
|
|
81
|
+
# installed via nvm (like the macOS image) and Rust via rustup (like the
|
|
82
|
+
# Windows image), so only the major/minor version is pinned here.
|
|
83
|
+
|
|
84
|
+
variable "node_version" {
|
|
85
|
+
type = string
|
|
86
|
+
description = "Node.js major version installed via nvm and set as the default, e.g. '22' (nvm resolves the latest patch)."
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
variable "python_version" {
|
|
90
|
+
type = string
|
|
91
|
+
description = "Python version from the Ubuntu archive that is installed, e.g. '3.12' (informational — apt resolves the exact version)."
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
variable "github_cli_version" {
|
|
95
|
+
type = string
|
|
96
|
+
description = "GitHub CLI version, e.g. '2.98.0' (linux-arm64 deb from the GitHub releases)."
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
variable "github_cli_sha256" {
|
|
100
|
+
type = string
|
|
101
|
+
description = "SHA256 of the gh_<version>_linux_arm64.deb for github_cli_version."
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
variable "open_code_review_version" {
|
|
105
|
+
type = string
|
|
106
|
+
description = "open-code-review (ocr) version installed via npm."
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
variable "go_version" {
|
|
110
|
+
type = string
|
|
111
|
+
description = "Go version, e.g. '1.27.0' (linux-arm64 tarball from go.dev/dl)."
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
variable "go_sha256" {
|
|
115
|
+
type = string
|
|
116
|
+
description = "SHA256 of the go<version>.linux-arm64.tar.gz for go_version."
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
variable "rust_version" {
|
|
120
|
+
type = string
|
|
121
|
+
description = "Rust toolchain version installed via rustup (e.g. '1.95'; rustup resolves the latest patch)."
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
variable "vscode_version" {
|
|
125
|
+
type = string
|
|
126
|
+
description = "Visual Studio Code version, e.g. '1.134.0' (linux-deb-arm64 from update.code.visualstudio.com)."
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
variable "vscode_sha256" {
|
|
130
|
+
type = string
|
|
131
|
+
description = "SHA256 of the Code <version> arm64 deb for vscode_version."
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
variable "firefox_version" {
|
|
135
|
+
type = string
|
|
136
|
+
description = "Mozilla Firefox version, e.g. '154.0' (linux-aarch64 en-US tar.xz from ftp.mozilla.org)."
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
variable "firefox_sha256" {
|
|
140
|
+
type = string
|
|
141
|
+
description = "SHA256 of the firefox-<version>.tar.xz for firefox_version."
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
variable "docker_version" {
|
|
145
|
+
type = string
|
|
146
|
+
description = "Docker CLI version, e.g. '29.7.2' (linux/static/stable/aarch64 tarball from download.docker.com)."
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
variable "docker_sha256" {
|
|
150
|
+
type = string
|
|
151
|
+
description = "SHA256 of the docker-<version>.tgz for docker_version."
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
variable "docker_compose_version" {
|
|
155
|
+
type = string
|
|
156
|
+
description = "Docker Compose plugin version, e.g. '5.5.0' (docker-compose-linux-aarch64 from the GitHub releases)."
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
variable "docker_compose_sha256" {
|
|
160
|
+
type = string
|
|
161
|
+
description = "SHA256 of the docker-compose-linux-aarch64 binary for docker_compose_version."
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
variable "docker_buildx_version" {
|
|
165
|
+
type = string
|
|
166
|
+
description = "Docker Buildx plugin version, e.g. '0.36.1' (buildx-<v>.linux-arm64 from the GitHub releases)."
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
variable "docker_buildx_sha256" {
|
|
170
|
+
type = string
|
|
171
|
+
description = "SHA256 of the buildx-<v>.linux-arm64 binary for docker_buildx_version."
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# --- VM resources ---
|
|
175
|
+
|
|
176
|
+
variable "disk_size" {
|
|
177
|
+
type = number
|
|
178
|
+
default = 100
|
|
179
|
+
description = "VM disk size in GB."
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
variable "cpu_count" {
|
|
183
|
+
type = number
|
|
184
|
+
default = 4
|
|
185
|
+
description = "CPU count of the VM."
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
variable "memory_gb" {
|
|
189
|
+
type = number
|
|
190
|
+
default = 8
|
|
191
|
+
description = "RAM of the VM in GB."
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# --- OpenChamber web UI ---
|
|
195
|
+
|
|
196
|
+
variable "openchamber_ui_password" {
|
|
197
|
+
type = string
|
|
198
|
+
default = "sandbox"
|
|
199
|
+
description = "Password protecting the OpenChamber web UI; required because the server binds to 0.0.0.0 (host access: http://<guest-ip>:4000)."
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
variable "openchamber_port" {
|
|
203
|
+
type = number
|
|
204
|
+
default = 4000
|
|
205
|
+
description = "TCP port the OpenChamber web UI listens on inside the guest."
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
# Source: VMware (Fusion on Apple Silicon, ARM64 guest)
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
#
|
|
212
|
+
# Ubuntu Server ARM64 runs natively on VMware Fusion on Apple Silicon —
|
|
213
|
+
# Fusion supports Ubuntu 24.04 LTS, and the guest uses the in-box
|
|
214
|
+
# open-vm-tools (Fusion ships no Linux tools ISO at all for arm64 guests:
|
|
215
|
+
# isoimages/arm64/ contains only windows.iso; open-vm-tools are the
|
|
216
|
+
# VMware-recommended tools for Linux and the only in-guest tools available
|
|
217
|
+
# for arm64 Linux VMs). The recipe mirrors the reference implementation in
|
|
218
|
+
# gusztavvargadr/packer (src/ubuntu/source.vmware.pkr.hcl, Unlicense),
|
|
219
|
+
# adapted to this repo's conventions and the Ubuntu Server ARM64 installer:
|
|
220
|
+
#
|
|
221
|
+
# - guest_os_type "arm-ubuntu-64" (Fusion's identifier for Ubuntu 64-bit
|
|
222
|
+
# ARM — verified in the VMX binary's guestOS table), hardware version
|
|
223
|
+
# 20, NVMe disk (Ubuntu's kernel has an in-box NVMe driver), vmxnet3
|
|
224
|
+
# NIC under NAT (vmxnet3.ko ships in the base linux-modules package,
|
|
225
|
+
# verified for 24.04 arm64), EFI firmware — the proven ARM64 combo,
|
|
226
|
+
# - the autoinstall seed is served over HTTP (autoinstall/ directory,
|
|
227
|
+
# http_directory) and passed to the installer kernel with
|
|
228
|
+
# ds=nocloud-net — the proven way to autoinstall Ubuntu Server under
|
|
229
|
+
# the vmware-iso builder; the boot_command is kept minimal (no wait
|
|
230
|
+
# tokens) so it cannot overrun grub's 30 s menu auto-boot,
|
|
231
|
+
# - Subiquity takes care of the partition layout (LVM over the whole
|
|
232
|
+
# disk), the network profile (DHCP on the NAT), and reboots into the
|
|
233
|
+
# installed system; Packer provisions over SSH afterwards,
|
|
234
|
+
# - open-vm-tools (installed by the autoinstall seed) is what makes
|
|
235
|
+
# vmrun's getGuestIPAddress and HGFS work in the sandbox runner.
|
|
236
|
+
#
|
|
237
|
+
# No snapshot is created (snapshot_name): the sandbox runner makes a full
|
|
238
|
+
# vmrun clone as its working VM, and a snapshot inside the published image
|
|
239
|
+
# would only complicate disk compaction and re-clones.
|
|
240
|
+
|
|
241
|
+
source "vmware-iso" "ubuntu" {
|
|
242
|
+
vm_name = "sandbox-ubuntu-${var.ubuntu_version}-arm64-vmware"
|
|
243
|
+
output_directory = "${var.build_dir}/output"
|
|
244
|
+
|
|
245
|
+
# The build flow (agent-dev-env build) verifies the ISO
|
|
246
|
+
# against var.iso_sha256 before Packer runs; iso_checksum is "none" so
|
|
247
|
+
# Packer does not re-verify.
|
|
248
|
+
iso_url = var.iso_path
|
|
249
|
+
iso_checksum = "none"
|
|
250
|
+
|
|
251
|
+
cpus = var.cpu_count
|
|
252
|
+
memory = var.memory_gb * 1024
|
|
253
|
+
disk_size = var.disk_size * 1024
|
|
254
|
+
|
|
255
|
+
# --- ARM64 guest wiring (identical shape to the Windows template) ---
|
|
256
|
+
version = 20
|
|
257
|
+
guest_os_type = "arm-ubuntu-64"
|
|
258
|
+
disk_type_id = "0"
|
|
259
|
+
disk_adapter_type = "nvme"
|
|
260
|
+
network = "nat"
|
|
261
|
+
network_adapter_type = "vmxnet3"
|
|
262
|
+
firmware = "efi"
|
|
263
|
+
cdrom_adapter_type = "sata"
|
|
264
|
+
|
|
265
|
+
# Autoinstall seed: user-data + meta-data, served by the build flow's
|
|
266
|
+
# own HTTP server (the flow runs `python3 -m http.server 8004` over
|
|
267
|
+
# autoinstall/; the installer kernel fetches it via ds=nocloud-net). The
|
|
268
|
+
# flow's server is used instead of Packer's http_directory: its port is
|
|
269
|
+
# random and the vmware plugin does not accept an http_port override,
|
|
270
|
+
# while the autoinstall URL has to be known at grub-typing time (see the
|
|
271
|
+
# watchdog note below). The guest reaches the server at the vmnet8 host
|
|
272
|
+
# address (x.y.z.1 — vmnetd delivers guest traffic to the host's
|
|
273
|
+
# loopback, proven by the bridge runners).
|
|
274
|
+
|
|
275
|
+
vmx_data = {
|
|
276
|
+
# The install ISO rides on the SATA controller.
|
|
277
|
+
"sata1.present" = "TRUE"
|
|
278
|
+
# USB 3.x controller — the plugin auto-enables USB on Apple Silicon
|
|
279
|
+
# for its own VNC typer; keeping it present makes USB input work.
|
|
280
|
+
"usb_xhci.present" = "TRUE"
|
|
281
|
+
# Boot the install CD before the (empty) NVMe disk, so the firmware
|
|
282
|
+
# reaches the ISO's EFI/grub boot menu as early as possible.
|
|
283
|
+
"bios.bootorder" = "cdrom,hdd"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# SSH, not WinRM: the installed system ships openssh-server (the
|
|
287
|
+
# autoinstall enables it), and the VM is on a NAT network where the
|
|
288
|
+
# builder can reach it directly. The password must match the crypt hash
|
|
289
|
+
# in autoinstall/user-data (identity.password).
|
|
290
|
+
communicator = "ssh"
|
|
291
|
+
ssh_username = var.ssh_username
|
|
292
|
+
ssh_password = var.ssh_password
|
|
293
|
+
ssh_timeout = "60m"
|
|
294
|
+
ssh_port = 22
|
|
295
|
+
|
|
296
|
+
# Headless: no Fusion window, VNC pinned for the build watchdog
|
|
297
|
+
# (the bundled VNC build watchdog) — same as the Windows templates.
|
|
298
|
+
headless = true
|
|
299
|
+
|
|
300
|
+
vnc_bind_address = "127.0.0.1"
|
|
301
|
+
vnc_port_min = 5901
|
|
302
|
+
vnc_port_max = 5901
|
|
303
|
+
vnc_disable_password = true
|
|
304
|
+
|
|
305
|
+
# No boot_command: the VNC typing raced the firmware's variable-length
|
|
306
|
+
# No-Media/PXE probe cycle (the CD only becomes bootable after a retry,
|
|
307
|
+
# 20-40 s in), and stray keys at the probe screen make the firmware
|
|
308
|
+
# attempt devices from the probe. The BUILD WATCHDOG does the typing
|
|
309
|
+
# instead (the bundled watch-build.py + WATCH_BUILD_BOOT_CMD, set by
|
|
310
|
+
# the build flow): it waits for the grub menu or
|
|
311
|
+
# shell in the OCR and then types the autoinstall command. The VNC is
|
|
312
|
+
# still opened (pinned below) for the watchdog, and the plugin's
|
|
313
|
+
# boot_wait simply gives the VM a moment to power on.
|
|
314
|
+
boot_wait = "5s"
|
|
315
|
+
boot_command = []
|
|
316
|
+
|
|
317
|
+
shutdown_command = "echo '${var.ssh_password}' | sudo -S shutdown -P now"
|
|
318
|
+
shutdown_timeout = "10m"
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
# ---------------------------------------------------------------------------
|
|
322
|
+
# Build
|
|
323
|
+
# ---------------------------------------------------------------------------
|
|
324
|
+
#
|
|
325
|
+
# All provisioners run as root: the execute_command pipes the sandbox
|
|
326
|
+
# password into sudo. User-level installs (nvm, rustup, npm globals, the
|
|
327
|
+
# OpenChamber systemd user service) are run as the sandbox user explicitly
|
|
328
|
+
# (`sudo -H -u <user>`), inside a single bash -c so $HOME, PATH and
|
|
329
|
+
# XDG_RUNTIME_DIR stay correct.
|
|
330
|
+
#
|
|
331
|
+
# "$" characters in the heredocs are plain bash variables: HCL only
|
|
332
|
+
# interpolates "${...}", so do NOT double them to "$$" (bash would expand
|
|
333
|
+
# "$$" to its PID — observed as "4330HOME" in a built image).
|
|
334
|
+
|
|
335
|
+
build {
|
|
336
|
+
sources = ["source.vmware-iso.ubuntu"]
|
|
337
|
+
|
|
338
|
+
# ===== Preamble: base system setup =====
|
|
339
|
+
provisioner "shell" {
|
|
340
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
341
|
+
inline = [<<-END
|
|
342
|
+
set -e -x
|
|
343
|
+
# --- timezone: UTC, like every other sandbox image ---
|
|
344
|
+
timedatectl set-timezone UTC
|
|
345
|
+
# --- apt maintenance: a sandbox reboots rarely and must boot fast ---
|
|
346
|
+
systemctl disable --now apt-daily.timer apt-daily-upgrade.timer \
|
|
347
|
+
unattended-upgrades.service fstrim.timer motd-news.service \
|
|
348
|
+
2>/dev/null || true
|
|
349
|
+
# --- open-vm-tools: the guest tools (no Fusion tools ISO exists for
|
|
350
|
+
# arm64 Linux); vmtoolsd + vmhgfs-fuse live here ---
|
|
351
|
+
systemctl enable --now open-vm-tools 2>/dev/null || true
|
|
352
|
+
# --- shared-folder mount point (the sandbox runner mounts HGFS here) ---
|
|
353
|
+
mkdir -p /mnt/hgfs/work
|
|
354
|
+
# --- linger: the sandbox user's systemd user services (OpenChamber,
|
|
355
|
+
# bridge relays) must start at boot without a login ---
|
|
356
|
+
loginctl enable-linger ${var.ssh_username}
|
|
357
|
+
# --- home ownership safety: a root-owned path inside the sandbox
|
|
358
|
+
# user's home breaks first-run client writes (EACCES observed with
|
|
359
|
+
# opencode's ~/.local/share) — normalize before any user-level step ---
|
|
360
|
+
chown -R ${var.ssh_username}:${var.ssh_username} /home/${var.ssh_username} || true
|
|
361
|
+
echo "base system ready"
|
|
362
|
+
END
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# ===== Base toolchain via apt =====
|
|
367
|
+
provisioner "shell" {
|
|
368
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
369
|
+
inline = [<<-END
|
|
370
|
+
set -e -x
|
|
371
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
372
|
+
apt-get update -y
|
|
373
|
+
apt-get install -y --no-install-recommends \
|
|
374
|
+
build-essential pkg-config make cmake autoconf automake \
|
|
375
|
+
git curl wget jq ripgrep vim tmux unzip zip xz-utils \
|
|
376
|
+
ca-certificates openssl gnupg \
|
|
377
|
+
python3 python3-pip python3-venv python3-dev \
|
|
378
|
+
ruby ruby-dev \
|
|
379
|
+
socat \
|
|
380
|
+
libfuse2t64 libfuse3-3 \
|
|
381
|
+
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
|
|
382
|
+
libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 libxdamage1 \
|
|
383
|
+
libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2 \
|
|
384
|
+
libgtk-3-0t64 libdbus-glib-1-2 libglib2.0-0
|
|
385
|
+
# apt's nodejs/npm are ancient on 24.04 — Node comes via nvm below.
|
|
386
|
+
python3 --version
|
|
387
|
+
git --version
|
|
388
|
+
echo "apt toolchain ready"
|
|
389
|
+
END
|
|
390
|
+
]
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# ===== GNOME desktop (minimal) =====
|
|
394
|
+
# The base installer is Ubuntu Server, so the guest boots to a text
|
|
395
|
+
# console by default. A desktop is part of the sandbox: GNOME Shell under
|
|
396
|
+
# GDM3 with automatic login for the sandbox user — the Fusion window is
|
|
397
|
+
# the human interface for VS Code, Firefox and OpenChamber in a browser.
|
|
398
|
+
# Graphics are software-rendered (llvmpipe — Fusion's arm64 guests get no
|
|
399
|
+
# GPU acceleration), and the Xorg session is the default (the virtualhw
|
|
400
|
+
# SVGA works better with the open-vm-tools input drivers than Wayland's
|
|
401
|
+
# compositor here).
|
|
402
|
+
provisioner "shell" {
|
|
403
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
404
|
+
inline = [<<-END
|
|
405
|
+
set -e -x
|
|
406
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
407
|
+
apt-get update -y
|
|
408
|
+
# ubuntu-desktop-minimal (GNOME Shell + GDM3 + core apps, arm64) and
|
|
409
|
+
# open-vm-tools-desktop (the SVGA Xorg driver, clipboard and
|
|
410
|
+
# drag-and-drop). Recommends stay ON: a desktop without its fonts and
|
|
411
|
+
# theme pieces is a broken desktop (the toolchain installs above keep
|
|
412
|
+
# --no-install-recommends on purpose).
|
|
413
|
+
apt-get install -y ubuntu-desktop-minimal open-vm-tools-desktop
|
|
414
|
+
# Boot straight to a login-free desktop: the VM window is the primary
|
|
415
|
+
# human interface, so the sandbox user must not have to log in first
|
|
416
|
+
# (SSH does not go through the GUI anyway).
|
|
417
|
+
cat >/etc/gdm3/custom.conf <<'GDM'
|
|
418
|
+
[daemon]
|
|
419
|
+
AutomaticLoginEnable=true
|
|
420
|
+
AutomaticLogin=${var.ssh_username}
|
|
421
|
+
WaylandEnable=false
|
|
422
|
+
GDM
|
|
423
|
+
systemctl set-default graphical.target
|
|
424
|
+
systemctl enable gdm3
|
|
425
|
+
echo "GNOME desktop ready"
|
|
426
|
+
END
|
|
427
|
+
]
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# ===== GitHub CLI (pinned deb) =====
|
|
431
|
+
provisioner "shell" {
|
|
432
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
433
|
+
inline = [<<-END
|
|
434
|
+
set -e -x
|
|
435
|
+
cd /tmp
|
|
436
|
+
DEB="gh_${var.github_cli_version}_linux_arm64.deb"
|
|
437
|
+
curl -fsSL -o "$DEB" "https://github.com/cli/cli/releases/download/v${var.github_cli_version}/$DEB"
|
|
438
|
+
echo "${var.github_cli_sha256} $DEB" | sha256sum -c -
|
|
439
|
+
dpkg -i "$DEB" || apt-get install -y -f
|
|
440
|
+
rm -f "$DEB"
|
|
441
|
+
gh --version
|
|
442
|
+
END
|
|
443
|
+
]
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# ===== Go (pinned tarball) =====
|
|
447
|
+
provisioner "shell" {
|
|
448
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
449
|
+
inline = [<<-END
|
|
450
|
+
set -e -x
|
|
451
|
+
cd /tmp
|
|
452
|
+
TARBALL="go${var.go_version}.linux-arm64.tar.gz"
|
|
453
|
+
curl -fsSL -o "$TARBALL" "https://go.dev/dl/$TARBALL"
|
|
454
|
+
echo "${var.go_sha256} $TARBALL" | sha256sum -c -
|
|
455
|
+
rm -rf /usr/local/go
|
|
456
|
+
tar -C /usr/local -xzf "$TARBALL"
|
|
457
|
+
rm -f "$TARBALL"
|
|
458
|
+
# Persist for login shells (profile.d runs before every login shell).
|
|
459
|
+
echo 'export PATH=$PATH:/usr/local/go/bin' | tee /etc/profile.d/agent-sandbox-go.sh
|
|
460
|
+
/usr/local/go/bin/go version
|
|
461
|
+
END
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
# ===== Node.js via nvm (pinned major) =====
|
|
466
|
+
# Same approach as the macOS image: nvm picks the version and makes it
|
|
467
|
+
# the default; the npm global installs (opencode, openchamber) land in
|
|
468
|
+
# the nvm dir. Runs as the sandbox user — npm caches and user configs
|
|
469
|
+
# must not be owned by root.
|
|
470
|
+
provisioner "shell" {
|
|
471
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
472
|
+
inline = [<<-END
|
|
473
|
+
set -e -x
|
|
474
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
475
|
+
set -e
|
|
476
|
+
export NVM_DIR="$HOME/.nvm"
|
|
477
|
+
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
|
|
478
|
+
. "$NVM_DIR/nvm.sh"
|
|
479
|
+
nvm install ${var.node_version}
|
|
480
|
+
nvm alias default ${var.node_version}
|
|
481
|
+
node --version
|
|
482
|
+
npm --version
|
|
483
|
+
'
|
|
484
|
+
echo "node ready"
|
|
485
|
+
END
|
|
486
|
+
]
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
# ===== Rust via rustup (pinned toolchain) =====
|
|
490
|
+
provisioner "shell" {
|
|
491
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
492
|
+
inline = [<<-END
|
|
493
|
+
set -e -x
|
|
494
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
495
|
+
set -e
|
|
496
|
+
curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain ${var.rust_version}
|
|
497
|
+
. "$HOME/.cargo/env"
|
|
498
|
+
rustc --version
|
|
499
|
+
cargo --version
|
|
500
|
+
'
|
|
501
|
+
echo 'export PATH=$PATH:$HOME/.cargo/bin' | tee /etc/profile.d/agent-sandbox-rust.sh
|
|
502
|
+
echo "rust ready"
|
|
503
|
+
END
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
# ===== Visual Studio Code (pinned arm64 deb) =====
|
|
508
|
+
provisioner "shell" {
|
|
509
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
510
|
+
inline = [<<-END
|
|
511
|
+
set -e -x
|
|
512
|
+
cd /tmp
|
|
513
|
+
DEB="code_${var.vscode_version}_arm64.deb"
|
|
514
|
+
curl -fsSL -o "$DEB" "https://update.code.visualstudio.com/${var.vscode_version}/linux-deb-arm64/stable"
|
|
515
|
+
echo "${var.vscode_sha256} $DEB" | sha256sum -c -
|
|
516
|
+
dpkg -i "$DEB" || apt-get install -y -f
|
|
517
|
+
rm -f "$DEB"
|
|
518
|
+
ln -sf /usr/bin/code /usr/local/bin/code
|
|
519
|
+
code --version | head -n1
|
|
520
|
+
END
|
|
521
|
+
]
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
# ===== Mozilla Firefox (pinned linux-aarch64 release) =====
|
|
525
|
+
# No Google Chrome: Chrome for Testing publishes no linux-arm64 build
|
|
526
|
+
# (only x86_64 linux64) and Ubuntu's chromium is snap-only, so Firefox
|
|
527
|
+
# is the browser in the image (linux-aarch64 releases are official).
|
|
528
|
+
provisioner "shell" {
|
|
529
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
530
|
+
inline = [<<-END
|
|
531
|
+
set -e -x
|
|
532
|
+
cd /tmp
|
|
533
|
+
ARCHIVE="firefox-${var.firefox_version}.tar.xz"
|
|
534
|
+
curl -fsSL -o "$ARCHIVE" "https://ftp.mozilla.org/pub/firefox/releases/${var.firefox_version}/linux-aarch64/en-US/$ARCHIVE"
|
|
535
|
+
echo "${var.firefox_sha256} $ARCHIVE" | sha256sum -c -
|
|
536
|
+
rm -rf /opt/firefox
|
|
537
|
+
mkdir -p /opt/firefox
|
|
538
|
+
tar -xJf "$ARCHIVE" -C /opt/firefox --strip-components=1
|
|
539
|
+
rm -f "$ARCHIVE"
|
|
540
|
+
ln -sf /opt/firefox/firefox /usr/local/bin/firefox
|
|
541
|
+
firefox --version
|
|
542
|
+
END
|
|
543
|
+
]
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
# ===== Docker CLI + plugins (client only, pinned static binaries) =====
|
|
547
|
+
# The guest never runs a container engine (it is bridged to the host's);
|
|
548
|
+
# only the CLI + compose + buildx are installed. Plugin search paths:
|
|
549
|
+
# the CLI checks /usr/local/lib/docker/cli-plugins after the user dirs.
|
|
550
|
+
provisioner "shell" {
|
|
551
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
552
|
+
inline = [<<-END
|
|
553
|
+
set -e -x
|
|
554
|
+
cd /tmp
|
|
555
|
+
TGZ="docker-${var.docker_version}.tgz"
|
|
556
|
+
curl -fsSL -o "$TGZ" "https://download.docker.com/linux/static/stable/aarch64/$TGZ"
|
|
557
|
+
echo "${var.docker_sha256} $TGZ" | sha256sum -c -
|
|
558
|
+
tar -xzf "$TGZ"
|
|
559
|
+
install -Dm755 "docker/docker" /usr/local/bin/docker
|
|
560
|
+
rm -rf docker "$TGZ"
|
|
561
|
+
mkdir -p /usr/local/lib/docker/cli-plugins
|
|
562
|
+
curl -fsSL -o /usr/local/lib/docker/cli-plugins/docker-compose \
|
|
563
|
+
"https://github.com/docker/compose/releases/download/v${var.docker_compose_version}/docker-compose-linux-aarch64"
|
|
564
|
+
echo "${var.docker_compose_sha256} /usr/local/lib/docker/cli-plugins/docker-compose" | sha256sum -c -
|
|
565
|
+
chmod 755 /usr/local/lib/docker/cli-plugins/docker-compose
|
|
566
|
+
curl -fsSL -o /usr/local/lib/docker/cli-plugins/docker-buildx \
|
|
567
|
+
"https://github.com/docker/buildx/releases/download/v${var.docker_buildx_version}/buildx-v${var.docker_buildx_version}.linux-arm64"
|
|
568
|
+
echo "${var.docker_buildx_sha256} /usr/local/lib/docker/cli-plugins/docker-buildx" | sha256sum -c -
|
|
569
|
+
chmod 755 /usr/local/lib/docker/cli-plugins/docker-buildx
|
|
570
|
+
# The static CLI looks for plugins in $HOME/.docker/cli-plugins (and
|
|
571
|
+
# the system dir next to /usr/local/bin); mirror the plugins into the
|
|
572
|
+
# caller's (root) and the sandbox user's plugin dirs so both root and
|
|
573
|
+
# admin shells see `docker compose` / `docker buildx`.
|
|
574
|
+
for pdir in "$HOME/.docker/cli-plugins" "/home/${var.ssh_username}/.docker/cli-plugins"; do
|
|
575
|
+
mkdir -p "$pdir"
|
|
576
|
+
ln -sf /usr/local/lib/docker/cli-plugins/docker-compose "$pdir/docker-compose"
|
|
577
|
+
ln -sf /usr/local/lib/docker/cli-plugins/docker-buildx "$pdir/docker-buildx"
|
|
578
|
+
done
|
|
579
|
+
# docker context 'host' pointing at the runtime bridge socket (the
|
|
580
|
+
# runner's guest bridge creates /tmp/docker.sock); nice-to-have only —
|
|
581
|
+
# the profile.d DOCKER_HOST export (written by the runner) covers
|
|
582
|
+
# clients that ignore contexts, so a context failure must not fail
|
|
583
|
+
# the build.
|
|
584
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
585
|
+
docker context create host --description "Host engine via /tmp/docker.sock bridge" \
|
|
586
|
+
--docker "host=unix:///tmp/docker.sock" 2>&1 || true
|
|
587
|
+
docker context use host 2>&1 || true
|
|
588
|
+
'
|
|
589
|
+
docker --version
|
|
590
|
+
docker compose version
|
|
591
|
+
docker buildx version
|
|
592
|
+
END
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
# ===== OpenCode, OpenCodeReview, OpenChamber web UI =====
|
|
597
|
+
# npm globals as the sandbox user. OpenChamber runs as a systemd user
|
|
598
|
+
# service (systemd is the Linux equivalent of the macOS LaunchAgent /
|
|
599
|
+
# Windows ONLOGON task): a deterministic unit is written by this image
|
|
600
|
+
# (framed exactly like the Windows image's wrapper approach — the
|
|
601
|
+
# openchamber CLI's own `startup enable` prefers an interactive user
|
|
602
|
+
# session to detect the service backend). The unit pins PATH (systemd
|
|
603
|
+
# user services start with a minimal environment — nvm/node and cargo
|
|
604
|
+
# paths must be explicit), OPENCODE_BINARY, OPENCHAMBER_UI_PASSWORD and
|
|
605
|
+
# SSH_AUTH_SOCK / DOCKER_HOST (the runner's bridge sockets).
|
|
606
|
+
provisioner "shell" {
|
|
607
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
608
|
+
inline = [<<-END
|
|
609
|
+
set -e -x
|
|
610
|
+
# nvm dir of the sandbox user (node lives there; nvm only loads in
|
|
611
|
+
# interactive shells, so non-login shells need the path directly).
|
|
612
|
+
NPM_BIN=$(sudo -H -u ${var.ssh_username} bash -c 'ls -d $HOME/.nvm/versions/node/v* 2>/dev/null | tail -n1')/bin
|
|
613
|
+
[ -n "$NPM_BIN" ] && [ -x "$NPM_BIN/npm" ] || { echo "nvm node dir not found"; exit 1; }
|
|
614
|
+
# Root-side guarantees (the user shell below has privileges to write
|
|
615
|
+
# only what its owner owns): create the XDG dirs with explicit
|
|
616
|
+
# ownership so first-run writes (opencode's ~/.local/share) can
|
|
617
|
+
# never hit an EACCES. `.local` must be listed as its own operand:
|
|
618
|
+
# install -d only applies -o/-g to the operands, and an intermediate
|
|
619
|
+
# `.local` would be created root-owned (breaking `tar -C $HOME` and
|
|
620
|
+
# any write the sandbox user makes directly into ~/.local).
|
|
621
|
+
install -d -o ${var.ssh_username} -g ${var.ssh_username} \
|
|
622
|
+
/home/${var.ssh_username}/.local \
|
|
623
|
+
/home/${var.ssh_username}/.local/share \
|
|
624
|
+
/home/${var.ssh_username}/.local/state \
|
|
625
|
+
/home/${var.ssh_username}/.config /home/${var.ssh_username}/.cache
|
|
626
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
627
|
+
set -e
|
|
628
|
+
export NVM_DIR="$HOME/.nvm"
|
|
629
|
+
. "$NVM_DIR/nvm.sh"
|
|
630
|
+
nvm use default >/dev/null
|
|
631
|
+
# opencode via the official installer: the npm package postinstall
|
|
632
|
+
# mis-selects the arm64-musl binary on glibc systems (EBADPLATFORM).
|
|
633
|
+
curl -fsSL https://opencode.ai/install | bash
|
|
634
|
+
export PATH="$HOME/.opencode/bin:$PATH"
|
|
635
|
+
opencode --version
|
|
636
|
+
npm install -g @alibaba-group/open-code-review@${var.open_code_review_version}
|
|
637
|
+
npm install -g @openchamber/web
|
|
638
|
+
ocr --version
|
|
639
|
+
openchamber --version
|
|
640
|
+
'
|
|
641
|
+
OPENCODE_BIN="$ADMIN_HOME/.opencode/bin/opencode"
|
|
642
|
+
ADMIN_HOME=$(sudo -H -u ${var.ssh_username} bash -c 'printf %s "$HOME"')
|
|
643
|
+
# Keep the CLI's own env file consistent (openchamber status/logs
|
|
644
|
+
# read it; the runtime service unit is ours).
|
|
645
|
+
sudo -H -u ${var.ssh_username} mkdir -p "$ADMIN_HOME/.config/openchamber"
|
|
646
|
+
cat > /tmp/startup.env <<EOF
|
|
647
|
+
OPENCHAMBER_UI_PASSWORD='${var.openchamber_ui_password}'
|
|
648
|
+
OPENCODE_BINARY='$OPENCODE_BIN'
|
|
649
|
+
EOF
|
|
650
|
+
sudo -H -u ${var.ssh_username} cp /tmp/startup.env "$ADMIN_HOME/.config/openchamber/startup.env"
|
|
651
|
+
rm -f /tmp/startup.env
|
|
652
|
+
|
|
653
|
+
# systemd user unit — the deterministic service the sandbox runner
|
|
654
|
+
# also restarts after the bridges come up.
|
|
655
|
+
sudo -H -u ${var.ssh_username} mkdir -p "$ADMIN_HOME/.config/systemd/user"
|
|
656
|
+
cat > /tmp/agent-sandbox-openchamber.service <<EOF
|
|
657
|
+
[Unit]
|
|
658
|
+
Description=OpenChamber Web Server
|
|
659
|
+
After=network.target
|
|
660
|
+
|
|
661
|
+
[Service]
|
|
662
|
+
Type=simple
|
|
663
|
+
WorkingDirectory=%h
|
|
664
|
+
ExecStart=$NPM_BIN/openchamber serve --port ${var.openchamber_port} --host 0.0.0.0 --foreground
|
|
665
|
+
Environment=PATH=$NPM_BIN:$ADMIN_HOME/.opencode/bin:/usr/local/go/bin:$ADMIN_HOME/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
666
|
+
Environment=OPENCODE_BINARY=$OPENCODE_BIN
|
|
667
|
+
Environment=OPENCHAMBER_UI_PASSWORD=${var.openchamber_ui_password}
|
|
668
|
+
Environment=SSH_AUTH_SOCK=/tmp/ssh-agent.sock
|
|
669
|
+
Environment=DOCKER_HOST=unix:///tmp/docker.sock
|
|
670
|
+
Restart=on-failure
|
|
671
|
+
RestartSec=5
|
|
672
|
+
|
|
673
|
+
[Install]
|
|
674
|
+
WantedBy=default.target
|
|
675
|
+
EOF
|
|
676
|
+
sudo -H -u ${var.ssh_username} cp /tmp/agent-sandbox-openchamber.service \
|
|
677
|
+
"$ADMIN_HOME/.config/systemd/user/agent-sandbox-openchamber.service"
|
|
678
|
+
rm -f /tmp/agent-sandbox-openchamber.service
|
|
679
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
680
|
+
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
|
|
681
|
+
systemctl --user daemon-reload
|
|
682
|
+
systemctl --user enable --now agent-sandbox-openchamber
|
|
683
|
+
'
|
|
684
|
+
END
|
|
685
|
+
]
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
# ===== OpenChamber health check + cleanup =====
|
|
689
|
+
# The service runs as a systemd user unit (linger enabled in the base
|
|
690
|
+
# provisioner): poll the HTTP endpoint until it answers (the first start
|
|
691
|
+
# may take a couple of minutes — npm modules + OpenCode spawn).
|
|
692
|
+
provisioner "shell" {
|
|
693
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
694
|
+
inline = [<<-END
|
|
695
|
+
set -e -x
|
|
696
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
697
|
+
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
|
|
698
|
+
systemctl --user is-active agent-sandbox-openchamber
|
|
699
|
+
'
|
|
700
|
+
n=0
|
|
701
|
+
while [ "$n" -lt 60 ]; do
|
|
702
|
+
if curl -fsS --connect-timeout 2 --max-time 3 "http://127.0.0.1:${var.openchamber_port}" >/dev/null 2>&1; then
|
|
703
|
+
echo "OpenChamber web UI is up on port ${var.openchamber_port}"
|
|
704
|
+
break
|
|
705
|
+
fi
|
|
706
|
+
n=$((n + 1))
|
|
707
|
+
sleep 5
|
|
708
|
+
done
|
|
709
|
+
if [ "$n" -ge 60 ]; then
|
|
710
|
+
echo "WARN: OpenChamber did not answer on 127.0.0.1:${var.openchamber_port}"
|
|
711
|
+
fi
|
|
712
|
+
apt-get autoclean -y || true
|
|
713
|
+
apt-get autoremove -y || true
|
|
714
|
+
rm -rf /var/lib/apt/lists/* /tmp/* 2>/dev/null || true
|
|
715
|
+
echo "Ubuntu sandbox image complete"
|
|
716
|
+
END
|
|
717
|
+
]
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
# ===== Final verification =====
|
|
721
|
+
provisioner "shell" {
|
|
722
|
+
execute_command = "echo '${var.ssh_password}' | sudo -S -E bash '{{.Path}}'"
|
|
723
|
+
inline = [<<-END
|
|
724
|
+
set -e -x
|
|
725
|
+
echo "=== Versions ==="
|
|
726
|
+
lsb_release -ds
|
|
727
|
+
uname -m
|
|
728
|
+
systemctl is-active open-vm-tools
|
|
729
|
+
# Desktop (GNOME): the packages and the boot target — run as root,
|
|
730
|
+
# no display needed for the checks.
|
|
731
|
+
dpkg -s ubuntu-desktop-minimal open-vm-tools-desktop >/dev/null
|
|
732
|
+
systemctl is-enabled gdm3
|
|
733
|
+
default_target=$(systemctl get-default)
|
|
734
|
+
[ "$default_target" = "graphical.target" ] || echo "WARN: default target is $default_target"
|
|
735
|
+
# Node/npm/opencode/ocr/openchamber live in the sandbox user's home
|
|
736
|
+
# (nvm + .opencode/bin) — the verification shell runs as root, so
|
|
737
|
+
# those have to be checked as the user.
|
|
738
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
739
|
+
export PATH="$HOME/.opencode/bin:$PATH"
|
|
740
|
+
export NVM_DIR="$HOME/.nvm"
|
|
741
|
+
. "$NVM_DIR/nvm.sh" >/dev/null 2>&1
|
|
742
|
+
nvm use default >/dev/null
|
|
743
|
+
node --version
|
|
744
|
+
npm --version
|
|
745
|
+
opencode --version | head -n1
|
|
746
|
+
ocr --version
|
|
747
|
+
openchamber --version | head -n1
|
|
748
|
+
'
|
|
749
|
+
python3 --version
|
|
750
|
+
git --version
|
|
751
|
+
gh --version
|
|
752
|
+
rg --version
|
|
753
|
+
jq --version
|
|
754
|
+
/usr/local/go/bin/go version
|
|
755
|
+
sudo -H -u ${var.ssh_username} bash -c '. "$HOME/.cargo/env"; rustc --version; cargo --version'
|
|
756
|
+
code --version | head -n1
|
|
757
|
+
docker --version
|
|
758
|
+
docker compose version
|
|
759
|
+
docker buildx version
|
|
760
|
+
# check-and-warn: a missing helper must never fail the build
|
|
761
|
+
for tool in firefox; do
|
|
762
|
+
if command -v "$tool" >/dev/null 2>&1; then
|
|
763
|
+
"$tool" --version 2>/dev/null | head -n1 || true
|
|
764
|
+
else
|
|
765
|
+
echo "WARN: $tool not found on PATH"
|
|
766
|
+
fi
|
|
767
|
+
done
|
|
768
|
+
sudo -H -u ${var.ssh_username} bash -c '
|
|
769
|
+
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
|
|
770
|
+
systemctl --user list-unit-files | grep agent-sandbox-openchamber || true
|
|
771
|
+
docker context ls | grep host || true
|
|
772
|
+
'
|
|
773
|
+
END
|
|
774
|
+
]
|
|
775
|
+
}
|
|
776
|
+
}
|