@cairnkeep/cli 2.2.0 → 2.2.1
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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable user-facing changes are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [2.2.1] - 2026-07-20
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- Make default sandbox-volume names path-specific so same-named repositories
|
|
11
|
+
cannot share a workspace accidentally.
|
|
12
|
+
- Refuse to merge repository content into a non-empty unmarked workspace
|
|
13
|
+
volume.
|
|
14
|
+
- Document that sandbox copies include untracked files and must be treated as
|
|
15
|
+
sensitive persistent data.
|
|
16
|
+
|
|
6
17
|
## [2.2.0] - 2026-07-20
|
|
7
18
|
|
|
8
19
|
### Added
|
|
@@ -11,6 +11,10 @@ case "$mode" in
|
|
|
11
11
|
exit 66
|
|
12
12
|
fi
|
|
13
13
|
if [ ! -e /workspace/.cairnkeep-sandbox-ready ]; then
|
|
14
|
+
if find /workspace -mindepth 1 -maxdepth 1 -print -quit | grep -q .; then
|
|
15
|
+
echo "cairnkeep: refusing to initialize a non-empty unmarked workspace volume" >&2
|
|
16
|
+
exit 65
|
|
17
|
+
fi
|
|
14
18
|
cp -a /source/. /workspace/
|
|
15
19
|
: > /workspace/.cairnkeep-sandbox-ready
|
|
16
20
|
fi
|
package/docs/containers.md
CHANGED
|
@@ -154,8 +154,14 @@ cairn-container workspace --repo /path/to/repository
|
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
Rerunning the command with the same workspace volume resumes the copy. Use
|
|
157
|
-
`--workspace-volume` when
|
|
158
|
-
|
|
157
|
+
`--workspace-volume` when an explicit lifecycle is preferable. Default volume
|
|
158
|
+
names include both the repository basename and a checksum of its absolute path,
|
|
159
|
+
so same-named repositories do not share a sandbox accidentally.
|
|
160
|
+
|
|
161
|
+
The initial copy includes tracked and untracked files. Treat the workspace
|
|
162
|
+
volume as sensitive local data and remove it when it is no longer needed.
|
|
163
|
+
Cairnkeep refuses to initialize a non-empty volume that lacks its sandbox
|
|
164
|
+
marker rather than merging unrelated content.
|
|
159
165
|
|
|
160
166
|
`shared` mode is opt-in and bind-mounts the checkout read/write:
|
|
161
167
|
|
package/package.json
CHANGED
package/scripts/cairn-container
CHANGED
|
@@ -171,7 +171,8 @@ run_workspace() {
|
|
|
171
171
|
repo=$(absolute_dir "$repo")
|
|
172
172
|
case "$mode" in sandbox | shared) ;; *) fail "--mode must be sandbox or shared" ;; esac
|
|
173
173
|
if [ -z "$workspace_volume" ]; then
|
|
174
|
-
|
|
174
|
+
repo_id=$(printf '%s' "$repo" | cksum | awk '{print $1}')
|
|
175
|
+
workspace_volume="cairnkeep-workspace-$(safe_name "$(basename "$repo")")-$repo_id"
|
|
175
176
|
fi
|
|
176
177
|
|
|
177
178
|
require_engine
|
|
@@ -80,4 +80,8 @@ has_arg CAIRN_WORKSPACE_MODE=shared
|
|
|
80
80
|
lacks_arg "$repo:/source:ro,Z"
|
|
81
81
|
has_arg bash
|
|
82
82
|
|
|
83
|
+
repo_id=$(printf '%s' "$repo" | cksum | awk '{print $1}')
|
|
84
|
+
run_launcher workspace --repo "$repo" --image example/workspace:1
|
|
85
|
+
has_arg "cairnkeep-workspace-repository-$repo_id:/workspace:Z,U"
|
|
86
|
+
|
|
83
87
|
echo "PASS: container launcher security and workspace contracts"
|
|
@@ -21,12 +21,13 @@ server_image="localhost/cairnkeep-test-server:$suffix"
|
|
|
21
21
|
workspace_image="localhost/cairnkeep-test-workspace:$suffix"
|
|
22
22
|
volume="cairnkeep-test-data-$suffix"
|
|
23
23
|
sandbox_volume="cairnkeep-test-workspace-$suffix"
|
|
24
|
+
conflict_volume="cairnkeep-test-conflict-$suffix"
|
|
24
25
|
name="cairnkeep-test-$suffix"
|
|
25
26
|
tmp=$(mktemp -d)
|
|
26
27
|
|
|
27
28
|
cleanup() {
|
|
28
29
|
"$ENGINE" rm -f "$name" >/dev/null 2>&1 || true
|
|
29
|
-
"$ENGINE" volume rm -f "$volume" "$sandbox_volume" >/dev/null 2>&1 || true
|
|
30
|
+
"$ENGINE" volume rm -f "$volume" "$sandbox_volume" "$conflict_volume" >/dev/null 2>&1 || true
|
|
30
31
|
"$ENGINE" image rm -f "$server_image" "$workspace_image" >/dev/null 2>&1 || true
|
|
31
32
|
rm -rf "$tmp"
|
|
32
33
|
}
|
|
@@ -180,4 +181,34 @@ else
|
|
|
180
181
|
"$workspace_image" sh -c 'test "$(cat sandbox.txt)" = sandbox'
|
|
181
182
|
fi
|
|
182
183
|
|
|
184
|
+
# A pre-populated, unmarked volume must not be merged with repository content.
|
|
185
|
+
"$ENGINE" volume create "$conflict_volume" >/dev/null
|
|
186
|
+
if [[ "$ENGINE" == podman ]]; then
|
|
187
|
+
"$ENGINE" run --rm --userns=keep-id:uid=10001,gid=10001 \
|
|
188
|
+
--volume "$conflict_volume:/workspace:Z,U" \
|
|
189
|
+
--env CAIRN_WORKSPACE_MODE=shared \
|
|
190
|
+
"$workspace_image" sh -c 'printf unrelated > unrelated.txt'
|
|
191
|
+
else
|
|
192
|
+
"$ENGINE" run --rm --user root --entrypoint sh \
|
|
193
|
+
--volume "$conflict_volume:/workspace" "$workspace_image" \
|
|
194
|
+
-c 'printf unrelated > /workspace/unrelated.txt; chown -R 10001:10001 /workspace'
|
|
195
|
+
fi
|
|
196
|
+
if [[ "$ENGINE" == podman ]]; then
|
|
197
|
+
conflict_mounts=(
|
|
198
|
+
--userns=keep-id:uid=10001,gid=10001
|
|
199
|
+
--volume "$tmp/repo:/source:ro,Z"
|
|
200
|
+
--volume "$conflict_volume:/workspace:Z,U"
|
|
201
|
+
)
|
|
202
|
+
else
|
|
203
|
+
conflict_mounts=(
|
|
204
|
+
--volume "$tmp/repo:/source:ro"
|
|
205
|
+
--volume "$conflict_volume:/workspace"
|
|
206
|
+
)
|
|
207
|
+
fi
|
|
208
|
+
if "$ENGINE" run --rm "${conflict_mounts[@]}" \
|
|
209
|
+
--env CAIRN_WORKSPACE_MODE=sandbox "$workspace_image" true >/dev/null 2>&1; then
|
|
210
|
+
echo "FAIL: sandbox merged into a non-empty unmarked volume" >&2
|
|
211
|
+
exit 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
183
214
|
echo "PASS: container stdio, guarded HTTP, persistence, shared, and sandbox modes"
|