Skip to content

Commit 8ef1d2a

Browse files
author
Chris Funderburg
committed
feat(docker): include default templates for first-run seeding
1 parent b879316 commit 8ef1d2a

4 files changed

Lines changed: 73 additions & 0 deletions

File tree

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dist/
1212
.github/
1313
*.md
1414
!README.md
15+
!data/templates/**
1516
.env*
1617
.DS_Store
1718
*.log

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ RUN npm ci --omit=dev
4343
COPY --from=builder /app/server/dist ./server/dist
4444
COPY --from=builder /app/client/dist ./client/dist
4545

46+
# Include default templates in the image for first-run seeding into the mounted data volume
47+
COPY data/templates ./seed/templates
48+
4649
# Create data directory and set permissions
4750
RUN mkdir -p /app/data && chown -R codex:codex /app
4851

@@ -55,6 +58,11 @@ EXPOSE 3001
5558
# Set environment variables
5659
ENV NODE_ENV=production
5760
ENV PORT=3001
61+
ENV DATA_DIR=/app/data
62+
63+
# Seed templates into the data volume on first run (only if data/templates is missing or empty)
64+
ENV CODEX_SEED_TEMPLATES=true
65+
ENV CODEX_SEED_TEMPLATES_DIR=/app/seed/templates
5866

5967
# Health check
6068
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,9 @@ docker stop codex && docker rm codex
550550
**Volume Mounting**:
551551
- `./data:/app/data` - Persists your wiki data outside the container
552552

553+
**Templates on first run**:
554+
- The Docker image includes built-in starter templates; on first startup, if `data/templates/` is missing or empty, Codex will copy the defaults into it.
555+
553556
### Authentication in Docker
554557

555558
**Important**: Due to secure cookie requirements, there are two ways to run Codex with authentication:

server/src/services/fileSystem.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,67 @@ export class FileSystemService {
120120
} catch {
121121
await fs.mkdir(this.dataDir, { recursive: true });
122122
}
123+
124+
await this.seedTemplatesIfConfigured();
125+
}
126+
127+
private async seedTemplatesIfConfigured(): Promise<void> {
128+
if (process.env.CODEX_SEED_TEMPLATES !== "true") {
129+
return;
130+
}
131+
132+
const seedDir = process.env.CODEX_SEED_TEMPLATES_DIR;
133+
if (!seedDir) {
134+
return;
135+
}
136+
137+
const templatesDir = this.validatePath("templates");
138+
139+
let shouldSeed = false;
140+
try {
141+
const entries = await fs.readdir(templatesDir, { withFileTypes: true });
142+
// Seed only when the directory is empty (ignore dotfiles like .DS_Store)
143+
const visibleEntries = entries.filter((entry) => !entry.name.startsWith("."));
144+
shouldSeed = visibleEntries.length === 0;
145+
} catch (error: any) {
146+
if (error?.code === "ENOENT") {
147+
shouldSeed = true;
148+
} else {
149+
return;
150+
}
151+
}
152+
153+
if (!shouldSeed) {
154+
return;
155+
}
156+
157+
try {
158+
await fs.mkdir(templatesDir, { recursive: true });
159+
const seedEntries = await fs.readdir(seedDir, { withFileTypes: true });
160+
const templateFiles = seedEntries
161+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"))
162+
.map((entry) => entry.name);
163+
164+
for (const filename of templateFiles) {
165+
const sourcePath = path.join(seedDir, filename);
166+
const destPath = path.join(templatesDir, filename);
167+
try {
168+
await fs.access(destPath);
169+
// Don't overwrite existing files
170+
continue;
171+
} catch {
172+
// ok
173+
}
174+
175+
await fs.copyFile(sourcePath, destPath);
176+
}
177+
178+
// Invalidate caches that might be affected
179+
this.cache.invalidate("folder-tree:");
180+
this.cache.invalidate("pages:templates");
181+
} catch {
182+
// Best-effort seeding; ignore failures to avoid blocking startup
183+
}
123184
}
124185

125186
async getFolderTree(relativePath: string = ""): Promise<FolderNode> {

0 commit comments

Comments
 (0)