Compare commits

..

1 Commits

Author SHA1 Message Date
b55deb4cc3 fix(install): preserve user fleet data on re-seed + refresh active units (#631)
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
CRITICAL data-loss in the routine update path. `mosaic update` auto-runs
install.sh keep-mode sync (#610); the rsync --delete honored PRESERVE_PATHS but
fleet/ was not listed, so the sync WIPED ~/.config/mosaic/fleet/roster.yaml (and
fleet/run, fleet/agents). Any user running `mosaic update` lost their fleet.

PRIMARY (data-loss):
- install.sh PRESERVE_PATHS += fleet/*.yaml, fleet/agents, fleet/run. The
  framework still SEEDS fleet/examples + fleet/roles + fleet/roster.schema.json
  (synced); the operator's roster, custom rosters, per-agent env, and heartbeat
  run dir are preserved.
- Made the cp (no-rsync) fallback GLOB-AWARE so fleet/*.yaml is preserved there
  too; fixed the restore to re-glob per pattern (restores only the user file,
  not the freshly-synced fleet/ dir).
- file-adapter.ts (TS installer): mirrored the preserve list for dual-installer
  parity. (syncDirectory is copy-only — never --delete — so it never had the
  bug; this is parity + belt-and-suspenders.)

SECONDARY (stale active units):
- refreshActiveFleetUnits(): the re-seed updates ~/.config/mosaic/systemd/user
  but systemd runs ~/.config/systemd/user, so shipped unit fixes (#627) did not
  take effect after update. `mosaic update` now copies the fresh mosaic-*.service
  → the active dir + daemon-reload (best-effort, only when a fleet is installed).

Verified: bash F6 fixture (roster/custom-yaml/agents/run survive + examples
refreshed + schema seeded), 20/20 migration matrix; TS file-adapter keep-mode
test; 2 refreshActiveFleetUnits unit tests. tsc/eslint/prettier/sanitize clean.

Refs #631

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
2026-06-22 16:13:52 -05:00
2 changed files with 0 additions and 83 deletions

View File

@@ -1,40 +0,0 @@
# Build & push the pre-baked CI base image (Dockerfile.ci) to the Gitea
# registry CI already publishes to. Reuses the exact kaniko + auth pattern
# from publish.yml (REGISTRY_USER/REGISTRY_PASS from_secret, /kaniko/.docker
# config.json). Other pipelines (ci.yml, publish.yml) pull `ci-base:latest`
# for their install step.
#
# Rebuild ONLY when the dependency set or the image recipe changes — a normal
# code push must not trigger a 25-min image build. `path` applies to push/PR
# events; `event: tag` (releases) rebuilds unconditionally so a tagged release
# always ships a fresh base.
when:
- event: tag
- event: [push, manual]
branch: main
path:
include:
- 'pnpm-lock.yaml'
- 'Dockerfile.ci'
steps:
build-ci-base:
image: gcr.io/kaniko-project/executor:debug
environment:
REGISTRY_USER:
from_secret: gitea_username
REGISTRY_PASS:
from_secret: gitea_password
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
commands:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
# Lockfile-hash tag: an immutable identity for the exact dep set baked
# into this image. `:latest` is the mutable pointer pipelines consume.
LOCK_HASH=$(sha256sum pnpm-lock.yaml | cut -c1-12)
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/ci-base:latest"
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/ci-base:lock-$LOCK_HASH"
/kaniko/executor --context . --dockerfile Dockerfile.ci $DESTINATIONS

View File

@@ -1,43 +0,0 @@
# Pre-baked CI base image for Woodpecker pipelines.
#
# Purpose: eliminate the cold `pnpm install` that dominates every pipeline
# (~731s median). This image ships the native toolchain (no per-run `apk add`)
# AND a warm, content-addressable pnpm store with the dependency-tree tarballs
# already fetched at build time. `pnpm fetch` only populates the store from the
# lockfile — it does NOT run the native node-gyp builds (better-sqlite3,
# node-pty, sqlite3, canvas, sharp); those still compile at `pnpm install`,
# which is exactly why the musl toolchain stays baked into this image. A
# pipeline `pnpm install --frozen-lockfile --prefer-offline` then resolves
# tarballs from local hard-links (no network) and compiles natives against the
# already-present toolchain, in tens of seconds instead of ~731s.
#
# Rebuilt only when `pnpm-lock.yaml` or this Dockerfile change
# (see .woodpecker/ci-image.yml).
#
# Node version is intentionally pinned to 22 (Active LTS at time of writing).
# The node:22 -> node:24 bump lands as a SEPARATE follow-up PR so the cache
# change carries zero runtime-version variables.
FROM node:22-alpine
# Native toolchain required to compile node-gyp deps on musl, plus the
# postgresql-client used by the test step's pg_isready readiness probe. `bash`
# is baked here too — the sanitization step in ci.yml otherwise does a per-run
# `apk add bash`.
RUN apk add --no-cache python3 make g++ postgresql-client bash
# Pin pnpm to the repo's packageManager version via corepack.
RUN corepack enable && corepack prepare pnpm@10.6.2 --activate
WORKDIR /app
# Pin the store location so the pipeline can point `store-dir` at the same path.
ENV PNPM_HOME=/root/.local/share/pnpm
RUN pnpm config set store-dir /root/.local/share/pnpm/store
# Warm the store. `pnpm fetch` populates the content-addressable store with the
# dependency tarballs directly from the lockfile (no package.json / workspace
# needed), so a baked store stays valid until the lockfile changes. Note:
# `fetch` does NOT compile native modules — that happens later at `pnpm install`
# in the pipeline, against the toolchain baked above.
COPY pnpm-lock.yaml ./
RUN pnpm fetch --frozen-lockfile