Skip to content

Commit e9b33cf

Browse files
authored
Merge pull request #177 from konard/issue-176-f004daa766ec
feat(shell): auto-provision docker-git scripts inside generated containers
2 parents af29ba8 + e6a7161 commit e9b33cf

6 files changed

Lines changed: 114 additions & 1 deletion

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// CHANGE: define the set of docker-git scripts to embed in generated containers
2+
// WHY: scripts (session-backup, pre-commit guards, knowledge splitter) must be available
3+
// inside containers for git hooks and docker-git module usage
4+
// REF: issue-176
5+
// SOURCE: n/a
6+
// FORMAT THEOREM: ∀ name ∈ dockerGitScriptNames: name ∈ scripts/ ∧ referenced_by_hooks(name)
7+
// PURITY: CORE (pure constant definition)
8+
// INVARIANT: list is exhaustive for all scripts referenced by generated git hooks
9+
// COMPLEXITY: O(1)
10+
11+
/**
12+
* Names of docker-git scripts that must be available inside generated containers.
13+
*
14+
* These scripts are referenced by git hooks (pre-push, pre-commit) and session
15+
* backup workflows. They are copied into each project's build context under
16+
* `scripts/` and embedded into the Docker image at `/opt/docker-git/scripts/`.
17+
*
18+
* @pure true
19+
* @invariant ∀ name ∈ result: ∃ file(scripts/{name}) in docker-git workspace
20+
*/
21+
export const dockerGitScriptNames: ReadonlyArray<string> = [
22+
"session-backup-gist.js",
23+
"session-backup-repo.js",
24+
"session-list-gists.js",
25+
"pre-commit-secret-guard.sh",
26+
"pre-push-knowledge-guard.js",
27+
"split-knowledge-large-files.js",
28+
"repair-knowledge-history.js",
29+
"setup-pre-commit-hook.js"
30+
]

packages/lib/src/core/templates-entrypoint/git.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,20 @@ cat <<'EOF' >> "$PRE_PUSH_HOOK"
261261
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
262262
cd "$REPO_ROOT"
263263
264+
# CHANGE: resolve session-backup script from /opt/docker-git/scripts (embedded) or repo-local fallback
265+
# WHY: docker-git scripts are now embedded in the container image at /opt/docker-git/scripts
266+
# REF: issue-176
264267
if [ "${"${"}DOCKER_GIT_SKIP_SESSION_BACKUP:-}" != "1" ]; then
265268
if command -v gh >/dev/null 2>&1; then
266-
node scripts/session-backup-gist.js --verbose || echo "[session-backup] Warning: session backup failed (non-fatal)"
269+
BACKUP_SCRIPT=""
270+
if [ -f /opt/docker-git/scripts/session-backup-gist.js ]; then
271+
BACKUP_SCRIPT="/opt/docker-git/scripts/session-backup-gist.js"
272+
elif [ -f "$REPO_ROOT/scripts/session-backup-gist.js" ]; then
273+
BACKUP_SCRIPT="$REPO_ROOT/scripts/session-backup-gist.js"
274+
fi
275+
if [ -n "$BACKUP_SCRIPT" ]; then
276+
node "$BACKUP_SCRIPT" --verbose || echo "[session-backup] Warning: session backup failed (non-fatal)"
277+
fi
267278
fi
268279
fi
269280
EOF

packages/lib/src/core/templates-entrypoint/tasks.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,26 @@ const renderCloneBody = (config: TemplateConfig): string =>
186186
renderCloneCacheFinalize(config)
187187
].join("\n")
188188

189+
// CHANGE: provision docker-git scripts into workspace after successful clone
190+
// WHY: git hooks reference scripts/ relative to repo root (e.g. "node scripts/session-backup-gist.js");
191+
// symlinking embedded /opt/docker-git/scripts makes them available in any cloned repo
192+
// REF: issue-176
193+
// PURITY: SHELL
194+
// INVARIANT: symlink created only when /opt/docker-git/scripts exists ∧ TARGET_DIR/scripts absent
195+
// COMPLEXITY: O(1)
189196
const renderCloneFinalize = (): string =>
190197
`if [[ "$CLONE_OK" -eq 1 ]]; then
191198
echo "[clone] done"
192199
touch "$CLONE_DONE_PATH"
200+
201+
# Provision docker-git scripts into workspace (symlink if not already present)
202+
if [[ -d /opt/docker-git/scripts && -n "$TARGET_DIR" && "$TARGET_DIR" != "/" ]]; then
203+
if [[ ! -e "$TARGET_DIR/scripts" ]]; then
204+
ln -s /opt/docker-git/scripts "$TARGET_DIR/scripts" || true
205+
chown -h 1000:1000 "$TARGET_DIR/scripts" 2>/dev/null || true
206+
echo "[scripts] provisioned docker-git scripts into workspace"
207+
fi
208+
fi
193209
else
194210
echo "[clone] failed"
195211
touch "$CLONE_FAIL_PATH"

packages/lib/src/core/templates.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const renderGitignore = (): string =>
1414
# NOTE: this directory is intended to be committed to the docker-git state repository.
1515
# It intentionally does not ignore .orch/ or auth files; keep the state repo private.
1616
17+
# docker-git scripts (copied from workspace, rebuilt on each project update)
18+
scripts/
19+
1720
# Volatile Codex artifacts (do not commit)
1821
.orch/auth/codex/log/
1922
.orch/auth/codex/tmp/

packages/lib/src/core/templates/dockerfile.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,18 @@ RUN printf "%s\\n" \
220220
"AllowUsers ${config.sshUser}" \
221221
> /etc/ssh/sshd_config.d/${config.sshUser}.conf`
222222

223+
// CHANGE: add docker-git scripts to Docker image at /opt/docker-git/scripts
224+
// WHY: scripts (session-backup, pre-commit guards, knowledge splitter) must be available
225+
// inside the container for git hooks and docker-git module usage
226+
// REF: issue-176
227+
// PURITY: CORE (pure template renderer)
228+
// INVARIANT: ∀ script ∈ scripts/: accessible(/opt/docker-git/scripts/{script})
229+
const renderDockerfileScripts = (): string =>
230+
`# docker-git scripts (hooks, session backup, knowledge guards)
231+
COPY scripts/ /opt/docker-git/scripts/
232+
RUN find /opt/docker-git/scripts -type f -name '*.sh' -exec chmod +x {} + \
233+
&& find /opt/docker-git/scripts -type f -name '*.js' -exec chmod +x {} +`
234+
223235
const renderDockerfileWorkspace = (config: TemplateConfig): string =>
224236
`# Workspace path (supports root-level dirs like /repo)
225237
RUN mkdir -p ${config.targetDir} \
@@ -241,5 +253,6 @@ export const renderDockerfile = (config: TemplateConfig): string =>
241253
renderDockerfileOpenCode(),
242254
renderDockerfileGitleaks(),
243255
renderDockerfileUsers(config),
256+
renderDockerfileScripts(),
244257
renderDockerfileWorkspace(config)
245258
].join("\n\n")

packages/lib/src/shell/files.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type * as Path from "@effect/platform/Path"
44
import { Effect, Match } from "effect"
55

66
import { type TemplateConfig } from "../core/domain.js"
7+
import { dockerGitScriptNames } from "../core/docker-git-scripts.js"
78
import { resolveComposeResourceLimits, withDefaultResourceLimitIntent } from "../core/resource-limits.js"
89
import { type FileSpec, planFiles } from "../core/templates.js"
910
import { FileExistsError } from "./errors.js"
@@ -102,6 +103,40 @@ const failOnExistingFiles = (
102103
return Effect.fail(new FileExistsError({ path: firstPath }))
103104
}
104105

106+
// CHANGE: discover and copy docker-git scripts into the project build context
107+
// WHY: scripts must be part of the Docker build context for COPY into the image
108+
// REF: issue-176
109+
// PURITY: SHELL
110+
// EFFECT: Effect<void, PlatformError, FileSystem | Path>
111+
// INVARIANT: only copies scripts that exist in the workspace; missing scripts are skipped
112+
// COMPLEXITY: O(|dockerGitScriptNames|)
113+
const provisionDockerGitScripts = (
114+
fs: FileSystem.FileSystem,
115+
path: Path.Path,
116+
baseDir: string
117+
): Effect.Effect<void, PlatformError> =>
118+
Effect.gen(function*(_) {
119+
const workspaceRoot = process.cwd()
120+
const sourceScriptsDir = path.join(workspaceRoot, "scripts")
121+
const targetScriptsDir = path.join(baseDir, "scripts")
122+
123+
const sourceExists = yield* _(fs.exists(sourceScriptsDir))
124+
if (!sourceExists) {
125+
return
126+
}
127+
128+
yield* _(fs.makeDirectory(targetScriptsDir, { recursive: true }))
129+
130+
for (const scriptName of dockerGitScriptNames) {
131+
const sourcePath = path.join(sourceScriptsDir, scriptName)
132+
const targetPath = path.join(targetScriptsDir, scriptName)
133+
const exists = yield* _(fs.exists(sourcePath))
134+
if (exists) {
135+
yield* _(fs.copyFile(sourcePath, targetPath))
136+
}
137+
}
138+
})
139+
105140
// CHANGE: write generated docker-git files to disk
106141
// WHY: isolate all filesystem effects in a thin shell
107142
// QUOTE(ТЗ): "создавать докер образы"
@@ -150,5 +185,10 @@ export const writeProjectFiles = (
150185
}
151186
}
152187

188+
// CHANGE: provision docker-git scripts into project build context
189+
// WHY: Dockerfile COPY scripts/ requires scripts to be in the build context
190+
// REF: issue-176
191+
yield* _(provisionDockerGitScripts(fs, path, baseDir))
192+
153193
return created
154194
})

0 commit comments

Comments
 (0)