@crvouga/dev-fleet 0.3.0 → 0.5.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/README.md +25 -17
- package/ansible/github/git.yml +66 -0
- package/ansible/macos/policy.json +12 -1
- package/ansible/monitoring/compose.yml +10 -0
- package/ansible/monitoring/container-watchdog.py +118 -0
- package/ansible/monitoring/hub-darwin.yml +146 -0
- package/ansible/net/remote-probe.sh +5 -0
- package/ansible/platforms.json +111 -0
- package/ansible/ubuntu/authorize-sudo.sh +7 -16
- package/ansible/ubuntu/baseline.yml +6 -28
- package/ansible/ubuntu/disk-gc.py +211 -7
- package/ansible/ubuntu/disk-guard.py +24 -5
- package/ansible/ubuntu/policy.json +68 -4
- package/ansible/ubuntu/t3-desktop.yml +2 -2
- package/ansible/ubuntu/tailscale.yml +20 -9
- package/ansible/ubuntu/tasks/browser.yml +56 -0
- package/ansible/ubuntu/tasks/docker.yml +52 -0
- package/ansible/ubuntu/tasks/dpkg.yml +46 -0
- package/ansible/ubuntu/tasks/install.yml +38 -0
- package/ansible/ubuntu/tasks/packages.yml +4 -55
- package/ansible/ubuntu/tasks/release.yml +25 -0
- package/ansible/ubuntu/tasks/resources.yml +7 -6
- package/ansible/ubuntu/tasks/services.yml +13 -0
- package/ansible/ubuntu/tasks/snapshot.yml +50 -0
- package/ansible/ubuntu/tasks/unattended-upgrades.yml +24 -0
- package/bootstrap/doctor.ts +32 -6
- package/bootstrap/fleet-remote-bootstrap.sh +49 -45
- package/bootstrap/setup.ts +23 -8
- package/bootstrap/worker-tools.ts +118 -0
- package/cliproxy/README.md +1 -1
- package/cliproxy/compose.yaml +4 -0
- package/cliproxy/scripts/checkpoint.py +1 -1
- package/docs/authentication.md +90 -0
- package/docs/consumer-setup.md +32 -30
- package/docs/declared.md +6 -5
- package/docs/machine-setup.md +22 -12
- package/docs/operations.md +87 -31
- package/docs/runbooks.md +377 -0
- package/docs/state.md +13 -7
- package/docs/support-matrix.md +36 -0
- package/docs/ubuntu-sync.md +16 -13
- package/docs/worker-contract.md +28 -1
- package/examples/fleet.json +19 -1
- package/llms.txt +2 -0
- package/mise.toml +7 -0
- package/package.json +2 -2
- package/schema/check-ids.json +526 -0
- package/schema/fleet.schema.json +185 -4
- package/schema/output/check.schema.json +23 -0
- package/schema/output/error.schema.json +14 -0
- package/schema/output/fleet-report.schema.json +79 -0
- package/schema/output/fleet-sync.schema.json +112 -0
- package/schema/output/report.schema.json +38 -0
- package/src/auth-checks.ts +3 -0
- package/src/cli/agent-guide.ts +5 -3
- package/src/cli/fleet-report.ts +126 -0
- package/src/cli/fleet-sync-manager.ts +11 -0
- package/src/cli/fleet-sync-plan.ts +200 -0
- package/src/cli/fleet-sync-remote.ts +4 -2
- package/src/cli/fleet-sync-schedule.ts +142 -35
- package/src/cli/fleet-sync.ts +90 -8
- package/src/cli/fleet-watch.ts +198 -0
- package/src/cli/fleet.ts +4 -0
- package/src/cli/register-fleet.ts +51 -0
- package/src/cli/registry.ts +13 -0
- package/src/cli/schedule-logs.ts +36 -0
- package/src/clock.ts +63 -0
- package/src/definition.ts +160 -12
- package/src/git-identity.ts +160 -0
- package/src/machine/platform.ts +48 -28
- package/src/machine/support-matrix.ts +76 -0
- package/src/machine-health.ts +51 -4
- package/src/manager-health.ts +205 -0
- package/src/manager-sync.ts +27 -9
- package/src/monitoring/hub-backup.ts +63 -17
- package/src/monitoring/manager-hub.ts +44 -2
- package/src/network-definition.ts +1 -1
- package/src/node-converge-linux.ts +16 -5
- package/src/node-converge.ts +1 -1
- package/src/notify.ts +239 -0
- package/src/platform/debian-version.ts +62 -0
- package/src/platform/ubuntu-audit.ts +11 -4
- package/src/platform/ubuntu-release.ts +41 -0
- package/src/remote-bootstrap.ts +89 -25
- package/src/remote-operation.ts +213 -0
- package/src/schedule-check.ts +40 -0
- package/src/secrets.ts +26 -8
- package/src/ssh.ts +10 -2
- package/src/state/history.ts +172 -0
- package/src/state/store.ts +11 -4
- package/src/tailscale.ts +51 -0
- package/src/toolchain-checks.ts +8 -3
- package/src/worker-capability-checks.ts +167 -0
- package/src/worker-capacity.ts +19 -7
- package/src/worker-host-checks.ts +68 -10
- package/src/worker-policy.ts +33 -0
- package/src/worker-readiness.ts +10 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
# A package database left inconsistent by an interrupted run is repaired
|
|
3
|
+
# before any package task runs, or stops the play with APT_STATE_BROKEN.
|
|
4
|
+
- name: Wait for legitimate package transactions before inspecting dpkg
|
|
5
|
+
ansible.builtin.command:
|
|
6
|
+
argv:
|
|
7
|
+
- /usr/bin/python3
|
|
8
|
+
- '-c'
|
|
9
|
+
- |
|
|
10
|
+
import fcntl
|
|
11
|
+
files = [open(path, 'a') for path in ['/var/lib/dpkg/lock-frontend', '/var/lib/dpkg/lock']]
|
|
12
|
+
for handle in files:
|
|
13
|
+
fcntl.lockf(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
14
|
+
register: apt_idle
|
|
15
|
+
changed_when: false
|
|
16
|
+
retries: 24
|
|
17
|
+
delay: 5
|
|
18
|
+
until: apt_idle.rc == 0
|
|
19
|
+
- name: Inspect package database consistency
|
|
20
|
+
ansible.builtin.command: dpkg --audit
|
|
21
|
+
changed_when: false
|
|
22
|
+
register: dpkg_audit
|
|
23
|
+
# Power lost during a package run leaves dpkg half-configured. Finishing
|
|
24
|
+
# that run is the standard safe repair: it is tried once, bounded, after
|
|
25
|
+
# the lock wait above, and only a database still inconsistent afterwards
|
|
26
|
+
# needs a person.
|
|
27
|
+
- name: Repair an interrupted package run once
|
|
28
|
+
when: dpkg_audit.stdout | trim != ''
|
|
29
|
+
block:
|
|
30
|
+
- name: Finish configuring every unpacked package, bounded
|
|
31
|
+
ansible.builtin.command:
|
|
32
|
+
argv: [timeout, '600', dpkg, --configure, -a]
|
|
33
|
+
environment:
|
|
34
|
+
DEBIAN_FRONTEND: noninteractive
|
|
35
|
+
- name: Inspect package database consistency after the repair
|
|
36
|
+
ansible.builtin.command: dpkg --audit
|
|
37
|
+
changed_when: false
|
|
38
|
+
register: dpkg_reaudit
|
|
39
|
+
rescue:
|
|
40
|
+
- name: Report a package run the repair could not finish
|
|
41
|
+
ansible.builtin.fail:
|
|
42
|
+
msg: APT_STATE_BROKEN; dpkg --configure -a did not finish. Inspect dpkg --audit and use reviewed package recovery. Never remove package lock files.
|
|
43
|
+
- name: Require a consistent package database before modifying packages
|
|
44
|
+
ansible.builtin.assert:
|
|
45
|
+
that: (dpkg_reaudit.stdout | default(dpkg_audit.stdout)) | trim == ''
|
|
46
|
+
fail_msg: APT_STATE_BROKEN; dpkg --configure -a did not clear dpkg --audit. Inspect it and use reviewed package recovery. Never remove package lock files.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The one way a pinned Ubuntu package is installed: from the snapshot,
|
|
3
|
+
# waiting for the APT lock, and never downgrading. A pin is a minimum: a
|
|
4
|
+
# package Ubuntu's security updates moved past its pin is left as it is and
|
|
5
|
+
# reported as a warning (docs/worker-contract.md). `install_pins` maps each
|
|
6
|
+
# package to its pinned version.
|
|
7
|
+
- ansible.builtin.import_tasks: snapshot.yml
|
|
8
|
+
- name: Read the installed package versions
|
|
9
|
+
ansible.builtin.package_facts:
|
|
10
|
+
manager: apt
|
|
11
|
+
- name: Compare each installed pinned package with its pin
|
|
12
|
+
ansible.builtin.command:
|
|
13
|
+
argv:
|
|
14
|
+
- dpkg
|
|
15
|
+
- --compare-versions
|
|
16
|
+
- '{{ ansible_facts.packages[item][0].version }}'
|
|
17
|
+
- ge
|
|
18
|
+
- '{{ install_pins[item] }}'
|
|
19
|
+
loop: "{{ install_pins.keys() | select('in', ansible_facts.packages) | list }}"
|
|
20
|
+
register: pin_comparison
|
|
21
|
+
changed_when: false
|
|
22
|
+
failed_when: pin_comparison.rc not in [0, 1]
|
|
23
|
+
check_mode: false
|
|
24
|
+
- name: Select the pinned packages that are absent or older than their pin
|
|
25
|
+
ansible.builtin.set_fact:
|
|
26
|
+
below_pin: "{{ (install_pins.keys() | reject('in', ansible_facts.packages) | list) + (pin_comparison.results | selectattr('rc', 'equalto', 1) | map(attribute='item') | list) }}"
|
|
27
|
+
- name: Install the pinned version of each package below its pin, without restarting services
|
|
28
|
+
ansible.builtin.apt:
|
|
29
|
+
name: '{{ item }}={{ install_pins[item] }}'
|
|
30
|
+
state: present
|
|
31
|
+
lock_timeout: '{{ aptLockTimeoutSeconds }}'
|
|
32
|
+
allow_downgrade: false
|
|
33
|
+
allow_unauthenticated: false
|
|
34
|
+
fail_on_autoremove: true
|
|
35
|
+
policy_rc_d: 101
|
|
36
|
+
environment:
|
|
37
|
+
APT_CONFIG: /etc/personal-fleet/apt.conf
|
|
38
|
+
loop: '{{ below_pin }}'
|
|
@@ -1,56 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
owner: root
|
|
7
|
-
group: root
|
|
8
|
-
mode: '0755'
|
|
9
|
-
- name: Declare snapshot sources used only for managed package transactions
|
|
10
|
-
ansible.builtin.copy:
|
|
11
|
-
dest: /etc/personal-fleet/ubuntu.sources
|
|
12
|
-
owner: root
|
|
13
|
-
group: root
|
|
14
|
-
mode: '0644'
|
|
15
|
-
backup: true
|
|
16
|
-
content: |
|
|
17
|
-
Types: deb
|
|
18
|
-
URIs: https://snapshot.ubuntu.com/ubuntu/{{ snapshot }}/
|
|
19
|
-
Suites: resolute resolute-updates resolute-security
|
|
20
|
-
Components: main universe restricted multiverse
|
|
21
|
-
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
|
|
22
|
-
register: snapshot_sources
|
|
23
|
-
- name: Isolate snapshot selection from unrelated APT operations
|
|
24
|
-
ansible.builtin.copy:
|
|
25
|
-
dest: /etc/personal-fleet/apt.conf
|
|
26
|
-
owner: root
|
|
27
|
-
group: root
|
|
28
|
-
mode: '0644'
|
|
29
|
-
content: |
|
|
30
|
-
Dir::Etc::sourcelist "/etc/personal-fleet/ubuntu.sources";
|
|
31
|
-
Dir::Etc::sourceparts "-";
|
|
32
|
-
DPkg::Lock::Timeout "{{ aptLockTimeoutSeconds }}";
|
|
33
|
-
- name: Inspect immutable snapshot package index
|
|
34
|
-
ansible.builtin.stat:
|
|
35
|
-
path: '/var/lib/apt/lists/snapshot.ubuntu.com_ubuntu_{{ snapshot }}_dists_resolute_InRelease'
|
|
36
|
-
register: snapshot_index
|
|
37
|
-
- name: Fetch signed snapshot indexes after source change or interrupted setup
|
|
38
|
-
ansible.builtin.apt:
|
|
39
|
-
update_cache: true
|
|
40
|
-
cache_valid_time: 0
|
|
41
|
-
lock_timeout: '{{ aptLockTimeoutSeconds }}'
|
|
42
|
-
environment:
|
|
43
|
-
APT_CONFIG: /etc/personal-fleet/apt.conf
|
|
44
|
-
when: snapshot_sources.changed or not snapshot_index.stat.exists
|
|
45
|
-
- name: Install exact managed packages without downgrading or restarting services
|
|
46
|
-
ansible.builtin.apt:
|
|
47
|
-
name: '{{ item.key }}={{ item.value }}'
|
|
48
|
-
state: present
|
|
49
|
-
lock_timeout: '{{ aptLockTimeoutSeconds }}'
|
|
50
|
-
allow_downgrade: false
|
|
51
|
-
allow_unauthenticated: false
|
|
52
|
-
fail_on_autoremove: true
|
|
53
|
-
policy_rc_d: 101
|
|
54
|
-
environment:
|
|
55
|
-
APT_CONFIG: /etc/personal-fleet/apt.conf
|
|
56
|
-
loop: '{{ packages | dict2items }}'
|
|
2
|
+
- ansible.builtin.include_tasks: install.yml
|
|
3
|
+
vars:
|
|
4
|
+
install_pins: '{{ packages }}'
|
|
5
|
+
- ansible.builtin.import_tasks: unattended-upgrades.yml
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The accepted releases are named once, in policy.json. This selects the
|
|
3
|
+
# entry of the running release and refuses everything else.
|
|
4
|
+
- name: Select the accepted release this machine runs
|
|
5
|
+
ansible.builtin.set_fact:
|
|
6
|
+
ubuntu_release: "{{ releases | selectattr('release', 'equalto', ansible_facts.distribution_version) | first | default({}) }}"
|
|
7
|
+
- name: Require an accepted release, its codename and one of its architectures
|
|
8
|
+
ansible.builtin.assert:
|
|
9
|
+
that:
|
|
10
|
+
- ansible_facts.distribution == 'Ubuntu'
|
|
11
|
+
- ubuntu_release.release is defined
|
|
12
|
+
- ansible_facts.distribution_release == ubuntu_release.codename
|
|
13
|
+
- "(ansible_facts.architecture | replace('x86_64', 'amd64') | replace('aarch64', 'arm64')) in ubuntu_release.architectures"
|
|
14
|
+
fail_msg: UBUNTU_BASELINE_MISMATCH; workers run a release docs/support-matrix.md lists. No distribution upgrade is permitted.
|
|
15
|
+
- name: Pin the packages of this release, its own pins over the common ones
|
|
16
|
+
ansible.builtin.set_fact:
|
|
17
|
+
packages: '{{ packages | combine(ubuntu_release.packages | default({})) }}'
|
|
18
|
+
- name: Read the kernel release to tell WSL2 apart from a machine of its own
|
|
19
|
+
ansible.builtin.slurp:
|
|
20
|
+
src: /proc/sys/kernel/osrelease
|
|
21
|
+
register: kernel_release
|
|
22
|
+
- name: Refuse WSL2 by name
|
|
23
|
+
ansible.builtin.assert:
|
|
24
|
+
that: not (kernel_release.content | b64decode | lower is search('microsoft|wsl'))
|
|
25
|
+
fail_msg: WORKER_ENVIRONMENT_UNSUPPORTED; WSL2 is not a worker. Run the worker on a physical or virtual machine of its own (docs/support-matrix.md).
|
|
@@ -49,10 +49,11 @@
|
|
|
49
49
|
line: /swap.fleet none swap sw 0 0
|
|
50
50
|
when: active_swap.stdout | trim == ''
|
|
51
51
|
|
|
52
|
-
- name: Install the systemd userspace OOM killer
|
|
53
|
-
ansible.builtin.
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
- name: Install the systemd userspace OOM killer at its pin
|
|
53
|
+
ansible.builtin.include_tasks: install.yml
|
|
54
|
+
vars:
|
|
55
|
+
install_pins:
|
|
56
|
+
systemd-oomd: "{{ packages['systemd-oomd'] }}"
|
|
56
57
|
- name: Create memory and disk policy directories
|
|
57
58
|
ansible.builtin.file:
|
|
58
59
|
path: '{{ item }}'
|
|
@@ -224,7 +225,7 @@
|
|
|
224
225
|
StartLimitIntervalSec=0
|
|
225
226
|
[Service]
|
|
226
227
|
Type=simple
|
|
227
|
-
ExecStart=/usr/bin/python3 -I -B {{ disk_guard_source.stat.path }} --path / --path {{ development_home }} --floor-mib {{ resourceSafety.diskGuardFloorMiB }} --write-mib {{ resourceSafety.diskGuardWriteMiB }} --interval {{ resourceSafety.diskGuardIntervalSeconds }}
|
|
228
|
+
ExecStart=/usr/bin/python3 -I -B {{ disk_guard_source.stat.path }} --path / --path {{ development_home }} --floor-mib {{ resourceSafety.diskGuardFloorMiB }} --inode-floor-percent {{ resourceSafety.inodeFailFreePercent }} --write-mib {{ resourceSafety.diskGuardWriteMiB }} --interval {{ resourceSafety.diskGuardIntervalSeconds }}
|
|
228
229
|
Restart=always
|
|
229
230
|
RestartSec=5
|
|
230
231
|
OOMScoreAdjust=-1000
|
|
@@ -276,7 +277,7 @@
|
|
|
276
277
|
After=docker.service
|
|
277
278
|
[Service]
|
|
278
279
|
Type=oneshot
|
|
279
|
-
ExecStart=/usr/bin/python3 -I -B {{ disk_gc_source.stat.path }} --home {{ development_home }} --path / --path {{ development_home }} --idle-days {{ resourceSafety.diskGcIdleDays }} --pressure-mib {{ resourceSafety.diskGcPressureMiB }} --pressure-idle-hours {{ resourceSafety.diskGcPressureIdleHours }} --docker-age-hours {{ resourceSafety.diskGcDockerAgeHours }} --pressure-docker-age-hours {{ resourceSafety.diskGcPressureDockerAgeHours }}{% for cache in resourceSafety.diskGcPressureCaches %} --pressure-cache {{ cache }}{% endfor %}
|
|
280
|
+
ExecStart=/usr/bin/python3 -I -B {{ disk_gc_source.stat.path }} --home {{ development_home }} --path / --path {{ development_home }} --idle-days {{ resourceSafety.diskGcIdleDays }} --pressure-mib {{ resourceSafety.diskGcPressureMiB }} --pressure-inode-percent {{ resourceSafety.inodeWarnFreePercent }} --pressure-idle-hours {{ resourceSafety.diskGcPressureIdleHours }} --docker-age-hours {{ resourceSafety.diskGcDockerAgeHours }} --pressure-docker-age-hours {{ resourceSafety.diskGcPressureDockerAgeHours }}{% for cache in resourceSafety.diskGcPressureCaches %} --pressure-cache {{ cache }}{% endfor %}{% for root in resourceSafety.diskGcWorktreeRoots %} --worktree-root {{ root }}{% endfor %} --worktree-idle-days {{ resourceSafety.diskGcWorktreeIdleDays }}{% for cache, mib in resourceSafety.diskGcCacheCapsMiB.items() %} --cache-cap {{ cache }}={{ mib }}{% endfor %} --keep-newest /var/cache/personal-fleet={{ resourceSafety.diskGcPackagesKept }} --mise {{ development_home }}/.local/bin/mise --user {{ development_user }}
|
|
280
281
|
|
|
281
282
|
TimeoutStartSec=2h
|
|
282
283
|
Nice=19
|
|
@@ -29,3 +29,16 @@
|
|
|
29
29
|
ansible.builtin.systemd_service:
|
|
30
30
|
daemon_reload: true
|
|
31
31
|
when: recovery_dropins.changed
|
|
32
|
+
# A skewed clock breaks TLS, tokens, apt signatures and Vault logins with
|
|
33
|
+
# errors that never mention time. Ubuntu ships one time service (chrony on
|
|
34
|
+
# newer releases, systemd-timesyncd before); whichever is installed stays
|
|
35
|
+
# enabled. A container takes its clock from its host. Measured as worker:clock.
|
|
36
|
+
- name: Inspect the time services this worker has
|
|
37
|
+
ansible.builtin.service_facts:
|
|
38
|
+
- name: Keep the clock synchronised with the time service Ubuntu ships
|
|
39
|
+
ansible.builtin.systemd_service:
|
|
40
|
+
name: '{{ item }}'
|
|
41
|
+
enabled: true
|
|
42
|
+
state: started
|
|
43
|
+
loop: "{{ (['chrony.service', 'systemd-timesyncd.service'] | select('in', ansible_facts.services) | list)[:1] }}"
|
|
44
|
+
when: ansible_facts.virtualization_type not in ['docker', 'container', 'podman', 'lxc', 'systemd-nspawn']
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The snapshot every managed package comes from, separate from the system's
|
|
3
|
+
# own sources. Included before any pinned package is installed; a second
|
|
4
|
+
# inclusion in one run changes nothing.
|
|
5
|
+
- name: Create owned APT configuration directory
|
|
6
|
+
ansible.builtin.file:
|
|
7
|
+
path: /etc/personal-fleet
|
|
8
|
+
state: directory
|
|
9
|
+
owner: root
|
|
10
|
+
group: root
|
|
11
|
+
mode: '0755'
|
|
12
|
+
- name: Declare snapshot sources used only for managed package transactions
|
|
13
|
+
ansible.builtin.copy:
|
|
14
|
+
dest: /etc/personal-fleet/ubuntu.sources
|
|
15
|
+
owner: root
|
|
16
|
+
group: root
|
|
17
|
+
mode: '0644'
|
|
18
|
+
backup: true
|
|
19
|
+
content: |
|
|
20
|
+
Types: deb
|
|
21
|
+
URIs: https://snapshot.ubuntu.com/ubuntu/{{ snapshot }}/
|
|
22
|
+
Suites: {{ ubuntu_release.codename }} {{ ubuntu_release.codename }}-updates {{ ubuntu_release.codename }}-security
|
|
23
|
+
Components: main universe restricted multiverse
|
|
24
|
+
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
|
|
25
|
+
register: snapshot_sources
|
|
26
|
+
# Fetching the snapshot indexes must leave the indexes of the system's own
|
|
27
|
+
# sources in place: the system's APT and unattended upgrades still use them.
|
|
28
|
+
- name: Isolate snapshot selection from unrelated APT operations
|
|
29
|
+
ansible.builtin.copy:
|
|
30
|
+
dest: /etc/personal-fleet/apt.conf
|
|
31
|
+
owner: root
|
|
32
|
+
group: root
|
|
33
|
+
mode: '0644'
|
|
34
|
+
content: |
|
|
35
|
+
Dir::Etc::sourcelist "/etc/personal-fleet/ubuntu.sources";
|
|
36
|
+
Dir::Etc::sourceparts "-";
|
|
37
|
+
APT::Get::List-Cleanup "false";
|
|
38
|
+
DPkg::Lock::Timeout "{{ aptLockTimeoutSeconds }}";
|
|
39
|
+
- name: Inspect immutable snapshot package index
|
|
40
|
+
ansible.builtin.stat:
|
|
41
|
+
path: '/var/lib/apt/lists/snapshot.ubuntu.com_ubuntu_{{ snapshot }}_dists_{{ ubuntu_release.codename }}_InRelease'
|
|
42
|
+
register: snapshot_index
|
|
43
|
+
- name: Fetch signed snapshot indexes after source change or interrupted setup
|
|
44
|
+
ansible.builtin.apt:
|
|
45
|
+
update_cache: true
|
|
46
|
+
cache_valid_time: 0
|
|
47
|
+
lock_timeout: '{{ aptLockTimeoutSeconds }}'
|
|
48
|
+
environment:
|
|
49
|
+
APT_CONFIG: /etc/personal-fleet/apt.conf
|
|
50
|
+
when: snapshot_sources.changed or not snapshot_index.stat.exists
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Ubuntu installs security updates by itself, and they keep flowing: a pin is
|
|
3
|
+
# a minimum, never a hold. What runs unattended is declared here so it is
|
|
4
|
+
# known: security updates only, never a reboot, never a removal. The fleet's
|
|
5
|
+
# own file sorts after the package's 50unattended-upgrades and replaces its
|
|
6
|
+
# origins.
|
|
7
|
+
- name: Declare which updates Ubuntu installs unattended
|
|
8
|
+
ansible.builtin.copy:
|
|
9
|
+
dest: /etc/apt/apt.conf.d/52personal-fleet-unattended-upgrades
|
|
10
|
+
owner: root
|
|
11
|
+
group: root
|
|
12
|
+
mode: '0644'
|
|
13
|
+
content: |
|
|
14
|
+
// Managed by personal Fleet: unattended upgrades
|
|
15
|
+
APT::Periodic::Update-Package-Lists "1";
|
|
16
|
+
APT::Periodic::Unattended-Upgrade "1";
|
|
17
|
+
#clear Unattended-Upgrade::Allowed-Origins;
|
|
18
|
+
#clear Unattended-Upgrade::Origins-Pattern;
|
|
19
|
+
Unattended-Upgrade::Allowed-Origins {
|
|
20
|
+
"${distro_id}:${distro_codename}-security";
|
|
21
|
+
};
|
|
22
|
+
Unattended-Upgrade::Automatic-Reboot "false";
|
|
23
|
+
Unattended-Upgrade::Remove-Unused-Dependencies "false";
|
|
24
|
+
Unattended-Upgrade::Remove-New-Unused-Dependencies "false";
|
package/bootstrap/doctor.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
NO_SELECTION,
|
|
3
|
+
parseSelectionArguments,
|
|
4
|
+
workerToolset,
|
|
5
|
+
type ToolSelection,
|
|
6
|
+
} from './worker-tools';
|
|
2
7
|
/** Read-only toolchain checks; no logins, installations or changes to Docker. */
|
|
3
8
|
import { spawnSync } from 'node:child_process';
|
|
4
9
|
import { readFileSync } from 'node:fs';
|
|
@@ -15,7 +20,7 @@ import {
|
|
|
15
20
|
type Tool = string | { version: string; lazy?: boolean; os?: string[] };
|
|
16
21
|
type Config = {
|
|
17
22
|
tools: Record<string, Tool>;
|
|
18
|
-
vars?:
|
|
23
|
+
vars?: Record<string, unknown>;
|
|
19
24
|
};
|
|
20
25
|
export type Check = {
|
|
21
26
|
id: string;
|
|
@@ -42,6 +47,11 @@ const VERSION_ARGS: Record<string, string[]> = {
|
|
|
42
47
|
kubectl: ['version', '--client=true', '--output=json'],
|
|
43
48
|
helm: ['version', '--short'],
|
|
44
49
|
};
|
|
50
|
+
/** The version a tool prints, where it differs from its mise pin. */
|
|
51
|
+
const PRINTED: Record<string, (pin: string) => string> = {
|
|
52
|
+
// temurin-25.0.5+8.0.LTS prints as 25.0.5.
|
|
53
|
+
java: (pin) => pin.replace(/^[a-z]+-/, '').replace(/\+.*$/, ''),
|
|
54
|
+
};
|
|
45
55
|
const run: Runner = (command) => {
|
|
46
56
|
const result = spawnSync(command[0]!, command.slice(1), {
|
|
47
57
|
encoding: 'utf8',
|
|
@@ -80,7 +90,8 @@ export function toolChecks(
|
|
|
80
90
|
if (typeof tool !== 'string' && tool.lazy) continue;
|
|
81
91
|
// A tool pinned only for other platforms has no artifact to verify here.
|
|
82
92
|
if (unavailable(id, tool, platform)) continue;
|
|
83
|
-
const
|
|
93
|
+
const pin = typeof tool === 'string' ? tool : tool.version;
|
|
94
|
+
const version = PRINTED[id]?.(pin) ?? pin;
|
|
84
95
|
const command = COMMANDS[id] ?? id;
|
|
85
96
|
const result = execute([command, ...(VERSION_ARGS[id] ?? ['--version'])]);
|
|
86
97
|
const exact = new RegExp(
|
|
@@ -195,7 +206,21 @@ export function globalConfigCheck(
|
|
|
195
206
|
};
|
|
196
207
|
}
|
|
197
208
|
|
|
198
|
-
|
|
209
|
+
/** A worker installs every tool it selects, lazy or not, so each is checked. */
|
|
210
|
+
function installed(tools: Record<string, Tool>): Record<string, Tool> {
|
|
211
|
+
return Object.fromEntries(
|
|
212
|
+
Object.entries(tools).map(([id, tool]) => [
|
|
213
|
+
id,
|
|
214
|
+
typeof tool === 'string' ? tool : { ...tool, lazy: false },
|
|
215
|
+
])
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function checkMachine(
|
|
220
|
+
requireDocker = false,
|
|
221
|
+
worker = false,
|
|
222
|
+
selection: ToolSelection = NO_SELECTION
|
|
223
|
+
): Check[] {
|
|
199
224
|
const config = Bun.TOML.parse(
|
|
200
225
|
readFileSync(join(workspaceRoot, 'mise.toml'), 'utf8')
|
|
201
226
|
) as Config;
|
|
@@ -203,7 +228,7 @@ export function checkMachine(requireDocker = false, worker = false): Check[] {
|
|
|
203
228
|
return [
|
|
204
229
|
...toolChecks(
|
|
205
230
|
worker
|
|
206
|
-
?
|
|
231
|
+
? installed(workerToolset(config.tools, config.vars ?? {}, selection))
|
|
207
232
|
: config.tools
|
|
208
233
|
),
|
|
209
234
|
...dockerChecks(requireDocker),
|
|
@@ -222,7 +247,8 @@ export function checkMachine(requireDocker = false, worker = false): Check[] {
|
|
|
222
247
|
if (import.meta.main) {
|
|
223
248
|
const checks = checkMachine(
|
|
224
249
|
process.argv.includes('--docker'),
|
|
225
|
-
process.argv.includes('--worker')
|
|
250
|
+
process.argv.includes('--worker'),
|
|
251
|
+
parseSelectionArguments(process.argv)
|
|
226
252
|
);
|
|
227
253
|
const ok = checks.every((check) => check.status !== 'fail');
|
|
228
254
|
if (process.argv.includes('--json'))
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
# Executed over strict SSH from the selected verified Git revision. stdin is
|
|
3
3
|
# one line holding the base64 fleet configuration (empty when there is none),
|
|
4
4
|
# then the base64 Git bundle (nothing in cached mode). All executable content
|
|
5
|
-
# remains in this tracked source.
|
|
5
|
+
# remains in this tracked source. The Ubuntu releases workers may run are
|
|
6
|
+
# named once, in ansible/ubuntu/policy.json; the manager passes them in.
|
|
6
7
|
set -eu
|
|
7
8
|
umask 077
|
|
8
9
|
revision=$1
|
|
9
10
|
bundle_sha=$2
|
|
10
11
|
node=$3
|
|
11
12
|
source_mode=${4:-transfer}
|
|
13
|
+
releases=${5:-}
|
|
12
14
|
case "$source_mode" in cached|transfer) ;; *) exit 2;; esac
|
|
15
|
+
case "$releases" in *[!0-9.\ ]*|'') exit 2;; esac
|
|
13
16
|
case "$revision" in *[!a-f0-9]*|'') exit 2;; esac
|
|
14
17
|
[ "${#revision}" = 40 ] || exit 2
|
|
15
18
|
case "$bundle_sha" in *[!a-f0-9]*|'') exit 2;; esac
|
|
@@ -26,7 +29,8 @@ remedy() {
|
|
|
26
29
|
case "$1" in
|
|
27
30
|
FLEET_REMOTE_LOCKED) echo 'Another fleet operation holds this machine. Wait for it to finish; rerun fleet-sync afterwards. Never remove the lock while its holder runs.';;
|
|
28
31
|
ROOT_DEVELOPMENT_USER_REJECTED) echo 'Connect as the non-root developer account the fleet configuration names.';;
|
|
29
|
-
UBUNTU_BASELINE_MISMATCH|UBUNTU_ARCHITECTURE_UNSUPPORTED|
|
|
32
|
+
UBUNTU_BASELINE_MISMATCH|UBUNTU_ARCHITECTURE_UNSUPPORTED|PLATFORM_UNSUPPORTED) echo 'Workers run an Ubuntu release docs/support-matrix.md lists, on x86_64 or aarch64. Reinstall the machine; fleet-sync never upgrades an operating system.';;
|
|
33
|
+
WORKER_ENVIRONMENT_UNSUPPORTED) echo 'WSL2 is not a worker. Run the worker on a physical or virtual machine of its own (docs/support-matrix.md).';;
|
|
30
34
|
FLEET_STATE_PATH_UNSAFE) echo 'A fleet state directory under ~/.local/share/personal is a symbolic link. Replace it with a real directory.';;
|
|
31
35
|
BOOTSTRAP_CAPACITY_UNVERIFIED|BOOTSTRAP_DISK_CAPACITY_REQUIRED) echo 'Free at least 2 GiB and 50000 inodes on the home file system, then rerun fleet-sync.';;
|
|
32
36
|
SOURCE_DIGEST_MISMATCH) echo 'The bundle changed in transit. Rerun fleet-sync; if it repeats, check the SSH route.';;
|
|
@@ -37,11 +41,9 @@ remedy() {
|
|
|
37
41
|
APT_DISK_FULL) echo 'Package installation ran out of disk space. Free space under /var and /usr, then rerun fleet-sync.';;
|
|
38
42
|
DPKG_INTERRUPTED) echo 'An earlier package installation that fleet-sync did not start was interrupted. Run sudo dpkg --configure -a once no package process is running, then rerun fleet-sync.';;
|
|
39
43
|
APT_UPDATE_FAILED|APT_INSTALL_FAILED) echo 'Run sudo apt-get update and install the bootstrap packages by hand to see the error, then rerun fleet-sync.';;
|
|
40
|
-
XCODE_COMMAND_LINE_TOOLS_REQUIRED) echo 'Install the Xcode Command Line Tools (xcode-select --install), then rerun fleet-sync.';;
|
|
41
|
-
BOOTSTRAP_LOCK_TOOL_REQUIRED) echo 'Install python3 so the operation lock can be taken, then rerun fleet-sync.';;
|
|
42
44
|
SOURCE_CACHE_CHANGED|SOURCE_STATUS_FAILED|SOURCE_CHECKOUT_CONFLICT) echo 'The fleet-owned checkout of this revision holds changes fleet-sync did not make. Inspect and remove that revisions directory, then rerun fleet-sync.';;
|
|
43
45
|
CONFIGURATION_INSTALL_FAILED) echo 'The fleet configuration could not be written under ~/.config/dev-fleet. Check that directory, then rerun fleet-sync.';;
|
|
44
|
-
TOOLCHAIN_*) echo 'The pinned toolchain did not converge. Rerun fleet-sync; it resumes where it stopped. If it repeats, run devfleet
|
|
46
|
+
TOOLCHAIN_*) echo 'The pinned toolchain did not converge. Rerun fleet-sync; it resumes where it stopped. If it repeats, run devfleet fleet-status --node for this worker.';;
|
|
45
47
|
*) echo 'Inspect the remote operation lock and rerun the approved revision. No success receipt advanced.';;
|
|
46
48
|
esac
|
|
47
49
|
}
|
|
@@ -64,22 +66,14 @@ trap '' HUP
|
|
|
64
66
|
trap 'exit 1' INT TERM
|
|
65
67
|
# No shell startup assumptions, and no implicit root development user.
|
|
66
68
|
[ "$(id -u)" != 0 ] || die ROOT_DEVELOPMENT_USER_REJECTED
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
case "$(uname -m)" in x86_64|aarch64) ;; *) die UBUNTU_ARCHITECTURE_UNSUPPORTED;; esac ;;
|
|
76
|
-
Darwin)
|
|
77
|
-
major=$(sw_vers -productVersion | cut -d. -f1)
|
|
78
|
-
case "$major" in ''|*[!0-9]*) die MACOS_BASELINE_MISMATCH;; esac
|
|
79
|
-
[ "$major" -ge 14 ] || die MACOS_BASELINE_MISMATCH
|
|
80
|
-
case "$(uname -m)" in arm64|x86_64) ;; *) die MACOS_ARCHITECTURE_UNSUPPORTED;; esac ;;
|
|
81
|
-
*) die PLATFORM_UNSUPPORTED;;
|
|
82
|
-
esac
|
|
69
|
+
# Workers are the Ubuntu releases the policy accepts (docs/support-matrix.md).
|
|
70
|
+
[ "$(uname -s)" = Linux ] || die PLATFORM_UNSUPPORTED
|
|
71
|
+
# shellcheck source=/dev/null
|
|
72
|
+
. /etc/os-release
|
|
73
|
+
[ "$ID" = ubuntu ] || die UBUNTU_BASELINE_MISMATCH
|
|
74
|
+
case " $releases " in *" $VERSION_ID "*) ;; *) die UBUNTU_BASELINE_MISMATCH;; esac
|
|
75
|
+
case "$(uname -m)" in x86_64|aarch64) ;; *) die UBUNTU_ARCHITECTURE_UNSUPPORTED;; esac
|
|
76
|
+
! grep -qiE 'microsoft|wsl' /proc/sys/kernel/osrelease 2>/dev/null || die WORKER_ENVIRONMENT_UNSUPPORTED
|
|
83
77
|
root="$HOME/.local/share/personal/fleet"
|
|
84
78
|
# Refuse symlinked state roots rather than allowing writes through them.
|
|
85
79
|
for path in "$HOME/.local" "$HOME/.local/share" "$HOME/.local/share/personal" "$root"; do
|
|
@@ -89,15 +83,20 @@ mkdir -p "$root"
|
|
|
89
83
|
phase=lock
|
|
90
84
|
[ ! -L "$root/remote-operation.lock" ] || exit 1
|
|
91
85
|
exec 9>"$root/remote-operation.lock"
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
# The lock follows this shell's open descriptor and is released when it exits.
|
|
87
|
+
flock -n 9 || die FLEET_REMOTE_LOCKED
|
|
88
|
+
# The operation record: a manager that lost its session reads it to follow
|
|
89
|
+
# this operation to its end instead of starting another (see
|
|
90
|
+
# src/remote-operation.ts). The result of the last operation is beside it.
|
|
91
|
+
started=$(date +%s)
|
|
92
|
+
result="$root/remote-operation-result.json"
|
|
93
|
+
[ ! -L "$root/remote-operation.json" ] && [ ! -L "$result" ] || die FLEET_STATE_PATH_UNSAFE
|
|
94
|
+
rm -f -- "$result"
|
|
95
|
+
operation() {
|
|
96
|
+
printf '{"version":1,"pid":%s,"revision":"%s","node":"%s","started":%s,"phase":"%s"%s}\n' "$$" "$revision" "$node" "$started" "$1" "${2:-}" >"$root/remote-operation.json.new" &&
|
|
97
|
+
mv -f -- "$root/remote-operation.json.new" "$root/remote-operation.json"
|
|
98
|
+
}
|
|
99
|
+
operation lock
|
|
101
100
|
# The fleet configuration is installed under the lock, so two overlapping
|
|
102
101
|
# runs never write it at once. It holds no secret.
|
|
103
102
|
phase='configuration-install'
|
|
@@ -109,6 +108,7 @@ if [ -n "$configuration" ]; then
|
|
|
109
108
|
mv "$config_dir/fleet.json.new" "$config_dir/fleet.json" || die CONFIGURATION_INSTALL_FAILED
|
|
110
109
|
fi
|
|
111
110
|
phase=resources
|
|
111
|
+
operation resources
|
|
112
112
|
# A bootstrap floor protects source transfer and minimal prerequisites. The
|
|
113
113
|
# Workstation baseline subsequently enforces its stricter declared capacity.
|
|
114
114
|
free_kib=$(df -Pk "$root" | awk 'END {print $4}')
|
|
@@ -120,14 +120,9 @@ if [ "$source_mode" = transfer ]; then
|
|
|
120
120
|
bundle=$(mktemp "$root/source.XXXXXX")
|
|
121
121
|
# Leave no executable payload copies; this temporary file is only Git data.
|
|
122
122
|
base64 -d >"$bundle"
|
|
123
|
-
|
|
124
|
-
printf '%s %s\n' "$bundle_sha" "$bundle" | sha256sum -c - >/dev/null 2>&1 || die SOURCE_DIGEST_MISMATCH
|
|
125
|
-
else
|
|
126
|
-
[ "$(shasum -a 256 "$bundle" | cut -d' ' -f1)" = "$bundle_sha" ] || die SOURCE_DIGEST_MISMATCH
|
|
127
|
-
fi
|
|
123
|
+
printf '%s %s\n' "$bundle_sha" "$bundle" | sha256sum -c - >/dev/null 2>&1 || die SOURCE_DIGEST_MISMATCH
|
|
128
124
|
fi
|
|
129
125
|
phase=prerequisites
|
|
130
|
-
if [ "$platform" = Linux ]; then
|
|
131
126
|
missing=''
|
|
132
127
|
for package in git curl ca-certificates unzip xz-utils build-essential python3 ansible-core; do
|
|
133
128
|
dpkg-query -W -f='${db:Status-Status}' "$package" 2>/dev/null | grep -qx installed || missing="$missing $package"
|
|
@@ -159,12 +154,6 @@ if [ -n "$missing" ]; then
|
|
|
159
154
|
rm -f -- "$pending" "$apt_log"
|
|
160
155
|
apt_log=''
|
|
161
156
|
fi
|
|
162
|
-
else
|
|
163
|
-
# Apple Command Line Tools provide git and python3; Ansible runs through the
|
|
164
|
-
# pinned uv tool runner installed by the shared mise toolchain below.
|
|
165
|
-
xcode-select -p >/dev/null 2>&1 || die XCODE_COMMAND_LINE_TOOLS_REQUIRED
|
|
166
|
-
command -v git >/dev/null 2>&1 || die XCODE_COMMAND_LINE_TOOLS_REQUIRED
|
|
167
|
-
fi
|
|
168
157
|
phase=source-verification
|
|
169
158
|
deployment="$root/revisions/$revision"
|
|
170
159
|
[ ! -L "$root/revisions" ] && [ ! -L "$deployment" ] || exit 1
|
|
@@ -200,6 +189,7 @@ bundle=''
|
|
|
200
189
|
clean=$(git -C "$deployment" status --porcelain) || die SOURCE_STATUS_FAILED
|
|
201
190
|
[ -z "$clean" ] || die SOURCE_CHECKOUT_CONFLICT
|
|
202
191
|
phase=toolchain
|
|
192
|
+
operation toolchain
|
|
203
193
|
cd "$deployment"
|
|
204
194
|
export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:/usr/local/bin:/usr/bin:/bin"
|
|
205
195
|
status_file="$root/toolchain-status.json"
|
|
@@ -211,15 +201,20 @@ ready_file="$root/toolchain-ready"
|
|
|
211
201
|
# are still checked (and reinstalled on drift) by node convergence.
|
|
212
202
|
toolchain_key=$(git rev-parse HEAD:mise.toml HEAD:mise.lock HEAD:.mise HEAD:bun.lock HEAD:package.json HEAD:bootstrap 2>/dev/null | tr '\n' ' ')
|
|
213
203
|
[ -n "$toolchain_key" ] || toolchain_key=$revision
|
|
204
|
+
# The tool profiles of the fleet configuration choose what is installed, so a
|
|
205
|
+
# changed configuration runs the setup again.
|
|
206
|
+
if [ -f "$HOME/.config/dev-fleet/fleet.json" ]; then
|
|
207
|
+
toolchain_key="$toolchain_key$(cksum <"$HOME/.config/dev-fleet/fleet.json" | cut -d' ' -f1)"
|
|
208
|
+
fi
|
|
214
209
|
if [ "$(cat "$ready_file" 2>/dev/null)" = "$toolchain_key" ] && [ -n "$(find "$ready_file" -mmin -1440 2>/dev/null)" ]; then
|
|
215
210
|
toolchain_warm=1
|
|
216
211
|
else
|
|
217
212
|
toolchain_warm=''
|
|
218
213
|
rm -f -- "$status_file" "$ready_file"
|
|
219
214
|
fi
|
|
220
|
-
|
|
215
|
+
bounded='timeout 1800'
|
|
221
216
|
# shellcheck disable=SC2086
|
|
222
|
-
if [ -z "$toolchain_warm" ] && ! $bounded bash bootstrap/bootstrap.sh --worker --non-interactive --status-file "$status_file" >/dev/null 2>&1; then
|
|
217
|
+
if [ -z "$toolchain_warm" ] && ! $bounded bash bootstrap/bootstrap.sh --worker --node "$node" --non-interactive --status-file "$status_file" >/dev/null 2>&1; then
|
|
223
218
|
tool_phase=$(sed -n 's/.*"phase":"\([a-z0-9-]*\)".*/\1/p' "$status_file" 2>/dev/null) || tool_phase=''
|
|
224
219
|
case "$tool_phase" in
|
|
225
220
|
managed-config) die TOOLCHAIN_CONFIG_CONFLICT;;
|
|
@@ -254,5 +249,14 @@ for old in $(ls -1t "$root/revisions"); do
|
|
|
254
249
|
rm -rf -- "${root:?}/revisions/$old" || true
|
|
255
250
|
done
|
|
256
251
|
phase=configuration
|
|
252
|
+
operation configuration
|
|
257
253
|
trap - EXIT INT TERM
|
|
258
|
-
|
|
254
|
+
# Node convergence writes its evidence to the result file, not to the SSH
|
|
255
|
+
# session: the session may be gone by the time it ends. The evidence is then
|
|
256
|
+
# printed for the manager that is still connected, under the same lock.
|
|
257
|
+
code=0
|
|
258
|
+
mise exec --locked bun -- bun src/node-converge.ts "$node" "$revision" >"$result.partial" 2>"$root/remote-operation.log" || code=$?
|
|
259
|
+
mv -f -- "$result.partial" "$result"
|
|
260
|
+
operation finished ",\"finished\":$(date +%s),\"exit\":$code"
|
|
261
|
+
cat -- "$result" || true
|
|
262
|
+
exit "$code"
|
package/bootstrap/setup.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
installedSelection,
|
|
3
|
+
NO_SELECTION,
|
|
4
|
+
workerToolset,
|
|
5
|
+
type ToolSelection,
|
|
6
|
+
} from './worker-tools';
|
|
2
7
|
import { readFileSync } from 'node:fs';
|
|
3
8
|
import { join } from 'node:path';
|
|
4
9
|
/** Used by both OS bootstraps, dev-fleet tools sync and dev-fleet update. No workspace dependencies. */
|
|
@@ -26,7 +31,8 @@ function run(args: string[]): void {
|
|
|
26
31
|
|
|
27
32
|
export async function syncTools(
|
|
28
33
|
observe: (phase: SetupPhase) => void = () => {},
|
|
29
|
-
worker = false
|
|
34
|
+
worker = false,
|
|
35
|
+
selection: ToolSelection = NO_SELECTION
|
|
30
36
|
): Promise<void> {
|
|
31
37
|
observe('managed-config');
|
|
32
38
|
const dir = globalConfigDir();
|
|
@@ -34,9 +40,9 @@ export async function syncTools(
|
|
|
34
40
|
observe('mise');
|
|
35
41
|
const config = Bun.TOML.parse(
|
|
36
42
|
readFileSync(join(workspaceRoot, 'mise.toml'), 'utf8')
|
|
37
|
-
) as { tools: Record<string, unknown>; vars:
|
|
43
|
+
) as { tools: Record<string, unknown>; vars: Record<string, unknown> };
|
|
38
44
|
const selected = worker
|
|
39
|
-
? Object.keys(
|
|
45
|
+
? Object.keys(workerToolset(config.tools, config.vars, selection))
|
|
40
46
|
: [];
|
|
41
47
|
run(['mise', 'install', '--locked', ...selected]);
|
|
42
48
|
observe('aws');
|
|
@@ -54,11 +60,20 @@ if (import.meta.main) {
|
|
|
54
60
|
const flag = process.argv.indexOf('--status-file');
|
|
55
61
|
const report = setupStatus(flag < 0 ? undefined : process.argv[flag + 1]);
|
|
56
62
|
let phase: SetupPhase = 'managed-config';
|
|
63
|
+
const worker = process.argv.includes('--worker');
|
|
64
|
+
const node = process.argv.indexOf('--node');
|
|
57
65
|
try {
|
|
58
|
-
await syncTools(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
await syncTools(
|
|
67
|
+
(current) => {
|
|
68
|
+
phase = current;
|
|
69
|
+
report(phase, 'running');
|
|
70
|
+
},
|
|
71
|
+
worker,
|
|
72
|
+
// The tool profiles of the fleet configuration fleet-sync installed.
|
|
73
|
+
worker && node >= 0
|
|
74
|
+
? installedSelection(process.argv[node + 1])
|
|
75
|
+
: NO_SELECTION
|
|
76
|
+
);
|
|
62
77
|
if (!process.argv.includes('--tools-only')) {
|
|
63
78
|
phase = 'dependencies';
|
|
64
79
|
report(phase, 'running');
|