Skip to content

Commit 6470a88

Browse files
authored
Merge pull request #409 from konard/issue-408-5d575dc8d85e
fix(entrypoint): clone into the dev-owned app folder
2 parents c5d6c3a + 9239b9e commit 6470a88

3 files changed

Lines changed: 87 additions & 1 deletion

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@prover-coder-ai/docker-git": patch
3+
---
4+
5+
Fix the standalone base image cloning the repo outside the prepared `app` folder.
6+
7+
The Dockerfile prepares and chowns `/home/dev/app` to the unprivileged `dev`
8+
user, but `entrypoint.sh` defaulted `TARGET_DIR` to `/work/app`. Because the
9+
auto-clone runs as `su - dev`, cloning into the root-created `/work/app` failed
10+
with permission denied, so the repository never landed in the `app` folder.
11+
The default now points at `/home/dev/app`, and the resolved `TARGET_DIR` is
12+
chowned to `dev` so overrides outside `/home/dev` keep working too.

entrypoint.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ docker_git_repair_dns || true
4949

5050
REPO_URL="${REPO_URL:-}"
5151
REPO_REF="${REPO_REF:-}"
52-
TARGET_DIR="${TARGET_DIR:-/work/app}"
52+
TARGET_DIR="${TARGET_DIR:-/home/dev/app}"
5353

5454
# 1) Authorized keys are mounted from host at /authorized_keys
5555
mkdir -p /home/dev/.ssh
@@ -70,6 +70,11 @@ chown -R dev:dev /home/dev/.codex
7070
if [[ -n "$REPO_URL" && ! -d "$TARGET_DIR/.git" ]]; then
7171
mkdir -p "$TARGET_DIR"
7272
chown -R dev:dev /home/dev
73+
# git clone runs as `su - dev`, so the target must be writable by dev even when
74+
# TARGET_DIR is overridden to a path outside /home/dev (otherwise clone fails silently).
75+
if [[ "$TARGET_DIR" != "/" ]]; then
76+
chown -R dev:dev "$TARGET_DIR"
77+
fi
7378

7479
if [[ -n "$REPO_REF" ]]; then
7580
su - dev -c "git clone --branch '$REPO_REF' '$REPO_URL' '$TARGET_DIR'"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// CHANGE: pin the standalone base-image clone target to the dev-owned app folder
2+
// WHY: entrypoint runs `git clone` as `su - dev`; cloning into a root-owned dir (e.g. /work/app)
3+
// fails with permission denied, so the repo never lands in the prepared `app` folder
4+
// QUOTE(ТЗ): "Почему-то при docker-git clone не делается git clone в папку app"
5+
// REF: issue-408
6+
// SOURCE: n/a
7+
// FORMAT THEOREM: defaultTargetDir(entrypoint) = appDir(Dockerfile) ∧ appDir ⊂ chown(dev, /home/dev)
8+
// PURITY: SHELL (reads repository source files via @effect/platform FileSystem)
9+
// INVARIANT: clone target is owned by the unprivileged clone user
10+
// COMPLEXITY: O(|file|)
11+
import * as FileSystem from "@effect/platform/FileSystem"
12+
import * as Path from "@effect/platform/Path"
13+
import { NodeContext } from "@effect/platform-node"
14+
import { describe, expect, it } from "@effect/vitest"
15+
import { Effect } from "effect"
16+
17+
// CHANGE: derive the dev home that the Dockerfile recursively chowns to the clone user
18+
// WHY: `git clone` runs as `su - dev`, so the target must live under a dev-owned path
19+
// PURITY: CORE
20+
// INVARIANT: returns the `chown -R dev:dev <home>` argument, or "" when absent
21+
const matchChownedHome = (dockerfile: string): string =>
22+
dockerfile.match(/chown -R dev:dev (\/home\/dev)\b/)?.[1] ?? ""
23+
24+
// CHANGE: derive the default TARGET_DIR the entrypoint clones into
25+
// WHY: the bug is a wrong default that points outside the dev-owned home
26+
// PURITY: CORE
27+
// INVARIANT: returns the `TARGET_DIR="${TARGET_DIR:-<default>}"` value, or "" when absent
28+
const matchDefaultTargetDir = (entrypoint: string): string =>
29+
entrypoint.match(/TARGET_DIR="\$\{TARGET_DIR:-([^}]+)\}"/)?.[1] ?? ""
30+
31+
// CHANGE: read the repo-root entrypoint + Dockerfile through the Effect FileSystem service
32+
// WHY: lint forbids node:* imports; @effect/platform is the sanctioned IO boundary
33+
// PURITY: SHELL
34+
// EFFECT: Effect<{ entrypoint, dockerfile }, PlatformError | BadArgument, FileSystem | Path>
35+
const readRepoSources = Effect.gen(function*(_) {
36+
const fs = yield* _(FileSystem.FileSystem)
37+
const path = yield* _(Path.Path)
38+
const here = yield* _(path.fromFileUrl(new URL(import.meta.url)))
39+
// packages/lib/tests/shell -> repository root
40+
const repoRoot = path.resolve(path.dirname(here), "..", "..", "..", "..")
41+
const entrypoint = yield* _(fs.readFileString(path.join(repoRoot, "entrypoint.sh")))
42+
const dockerfile = yield* _(fs.readFileString(path.join(repoRoot, "Dockerfile")))
43+
return { dockerfile, entrypoint }
44+
})
45+
46+
describe("standalone base-image clone target", () => {
47+
it.effect("prepares an app folder under the dev home in the Dockerfile", () =>
48+
Effect.gen(function*(_) {
49+
const { dockerfile } = yield* _(readRepoSources)
50+
expect(dockerfile).toContain("mkdir -p /home/dev/app")
51+
expect(matchChownedHome(dockerfile)).toBe("/home/dev")
52+
}).pipe(Effect.provide(NodeContext.layer)))
53+
54+
it.effect("defaults the clone target to the prepared app folder", () =>
55+
Effect.gen(function*(_) {
56+
const { entrypoint } = yield* _(readRepoSources)
57+
expect(matchDefaultTargetDir(entrypoint)).toBe("/home/dev/app")
58+
}).pipe(Effect.provide(NodeContext.layer)))
59+
60+
it.effect("keeps the clone target under the dev-owned home so `su - dev` can write into it", () =>
61+
Effect.gen(function*(_) {
62+
const { dockerfile, entrypoint } = yield* _(readRepoSources)
63+
const chownedHome = matchChownedHome(dockerfile)
64+
const defaultTargetDir = matchDefaultTargetDir(entrypoint)
65+
expect(chownedHome).not.toBe("")
66+
expect(defaultTargetDir).not.toBe("")
67+
expect(`${defaultTargetDir}/`.startsWith(`${chownedHome}/`)).toBe(true)
68+
}).pipe(Effect.provide(NodeContext.layer)))
69+
})

0 commit comments

Comments
 (0)