Skip to content

Commit 28fcd5b

Browse files
committed
feat: update Dockerfile and Ansible configurations for improved deployment and security
- Enhanced Dockerfile to include TLS root store for outbound HTTPS and updated pnpm version to 10.17.1. - Modified .gitignore to exclude additional Ansible files for better repository cleanliness. - Improved Ansible playbook to validate required database variables and ensure derived URLs are not preset. - Updated inventory files to separate sensitive information and ensure proper host configuration. - Enhanced environment variable management in vars.example.yml for better clarity and security.
1 parent 541fb40 commit 28fcd5b

File tree

14 files changed

+583
-236
lines changed

14 files changed

+583
-236
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Tasks:
2+
1) Locate all files/modules affected by the issue. List paths and why each is implicated.
3+
2) Explain the root cause(s): what changed, how it propagates to the failure, and any environmental factors.
4+
3) Propose the minimal, safe fix. Include code-level steps, side effects, and tests to add/update.
5+
4) Flag any missing or outdated documentation/configs/schemas that should be updated or added (especially if code appears outdated vs. current behavior). Specify exact docs/sections to create or amend.
6+
7+
Output format:
8+
- Affected files:
9+
- <path>: <reason>
10+
- Root cause:
11+
- <concise explanation>
12+
- Proposed fix:
13+
- <steps/patch outline>
14+
- Tests:
15+
- Documentation gaps:
16+
- <doc_section_what_to_update_add>
17+
- Open questions/assumptions:
18+
- <items>
19+
20+
21+
DON'T CODE YET.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,5 @@ infra/ansible/group_vars/constructa/vars.yml
106106
*.tfstate
107107
*.tfstate.*
108108
*.tfstate.backup
109+
.ansible
110+
hosts.local.ini

Dockerfile

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# ---- Stage 1: Build ----------------------------------------------------------
22
FROM node:22-alpine AS builder
33

4-
# Slightly better compatibility on alpine
5-
RUN apk add --no-cache libc6-compat
4+
# Slightly better compatibility on alpine + TLS root store for outbound HTTPS
5+
RUN apk add --no-cache libc6-compat ca-certificates
66

77
# Allow Vite build to use more memory inside the builder container
88
ENV NODE_OPTIONS="--max-old-space-size=4096"
99

1010
# Use pnpm
11-
RUN corepack enable && corepack prepare pnpm@9.14.4 --activate
11+
RUN corepack enable && corepack prepare pnpm@10.17.1 --activate
1212

1313
WORKDIR /app
1414

@@ -24,16 +24,17 @@ RUN pnpm run build
2424
# ---- Stage 2: Runtime --------------------------------------------------------
2525
FROM node:22-alpine AS runner
2626

27-
RUN apk add --no-cache libc6-compat
28-
RUN corepack enable && corepack prepare pnpm@9.14.4 --activate
27+
RUN apk add --no-cache libc6-compat ca-certificates
28+
RUN npm install -g pnpm@10.17.1
2929

3030
WORKDIR /app
31-
ENV NODE_ENV=production
3231
ENV PORT=5000
3332

34-
# Install production dependencies only
33+
# Install runtime dependencies (keep dev tools for migrations/worker)
3534
COPY package.json pnpm-lock.yaml ./
36-
RUN pnpm install --prod --frozen-lockfile
35+
RUN pnpm install --frozen-lockfile
36+
37+
ENV NODE_ENV=production
3738

3839
# Copy build output and runtime assets
3940
COPY --from=builder /app/.output ./.output

cli/index.ts

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,46 @@ const checkDocker = () => {
7272
}
7373

7474
type Remote = { host: string; user: string }
75+
type InventoryEntry = { host?: string; user?: string }
76+
77+
const inventoryFiles = [
78+
'infra/ansible/inventory/hosts.ini',
79+
'infra/ansible/inventory/hosts.local.ini'
80+
]
81+
82+
const loadInventoryEntries = (): Record<string, InventoryEntry> => {
83+
const entries: Record<string, InventoryEntry> = {}
84+
for (const file of inventoryFiles) {
85+
if (!existsSync(file)) continue
86+
const content = readFileSync(file, 'utf8')
87+
for (const rawLine of content.split('\n')) {
88+
const line = rawLine.trim()
89+
if (!line || line.startsWith('#') || line.startsWith('[')) continue
90+
const [name, ...rest] = line.split(/\s+/)
91+
if (!name) continue
92+
const data = entries[name] ?? {}
93+
for (const kv of rest) {
94+
const [key, value] = kv.split('=')
95+
if (!key || typeof value === 'undefined') continue
96+
if (key === 'ansible_host') data.host = value
97+
if (key === 'ansible_user') data.user = value
98+
}
99+
entries[name] = data
100+
}
101+
}
102+
return entries
103+
}
104+
75105
const resolveRemote = (env: 'dev' | 'prod'): Remote => {
76-
const invPath = 'infra/ansible/inventory/hosts.ini'
77-
const inv = readFileSync(invPath, 'utf8')
78106
const name = `ex0-${env}`
79-
const line = inv
80-
.split('\n')
81-
.map((l) => l.trim())
82-
.find((l) => l.startsWith(name))
83-
if (!line) throw new Error(`No inventory entry for ${name} in ${invPath}`)
84-
const host = /ansible_host=([^\s]+)/.exec(line)?.[1]
85-
const user = /ansible_user=([^\s]+)/.exec(line)?.[1] || 'deploy'
86-
if (!host) throw new Error(`Missing ansible_host for ${name}`)
87-
return { host, user }
107+
const entries = loadInventoryEntries()
108+
const entry = entries[name]
109+
if (!entry?.host || entry.host === '<insert' || entry.host.includes(' ')) {
110+
throw new Error(
111+
`Missing ansible_host for ${name}. Fill hosts.local.ini with "${name} ansible_host=<ip> ansible_user=deploy".`
112+
)
113+
}
114+
return { host: entry.host, user: entry.user ?? 'deploy' }
88115
}
89116

90117
const getDataDir = () => {
@@ -416,6 +443,8 @@ const deployBranchCommand = defineCommand({
416443
})
417444

418445
// ----- Logs & Restart on remote (defaults to dev) -----
446+
const composeFilePath = '/opt/constructa/compose.yml'
447+
419448
const logsCommand = defineCommand({
420449
meta: { name: 'logs', description: 'Stream remote compose logs' },
421450
args: {
@@ -428,7 +457,8 @@ const logsCommand = defineCommand({
428457
const password = await getSudoPassword()
429458
const safeTail = Number.isFinite(args.tail) ? args.tail : 100
430459
const safeService = escapeSingleQuotes(args.service)
431-
const command = `cd '/opt/constructa' && sudo -SE docker compose logs -f --tail=${safeTail} '${safeService}'`
460+
const command =
461+
`cd '/opt/constructa' && sudo -SE docker compose -f '${composeFilePath}' logs -f --tail=${safeTail} '${safeService}'`
432462
runRemote(remote, command, `Logs (${args.service})`, { sudo: true, password })
433463
}
434464
})
@@ -440,7 +470,8 @@ const restartCommand = defineCommand({
440470
const remote = resolveRemote(args.env as 'dev' | 'prod')
441471
const password = await getSudoPassword()
442472
const safeService = escapeSingleQuotes(args.service)
443-
const command = `cd '/opt/constructa' && sudo -SE docker compose restart '${safeService}'`
473+
const command =
474+
`cd '/opt/constructa' && sudo -SE docker compose -f '${composeFilePath}' restart '${safeService}'`
444475
runRemote(remote, command, `Restart ${args.service}`, { sudo: true, password })
445476
}
446477
})

infra/ansible/group_vars/constructa/vars.example.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@
88

99
constructa_deploy_dir: /opt/constructa
1010

11+
# Optional Docker daemon DNS overrides (helps containers reach the Internet)
12+
# Leave empty to skip writing /etc/docker/daemon.json
13+
constructa_docker_dns_servers:
14+
- '1.1.1.1'
15+
- '8.8.8.8'
16+
1117
# Sudo password for the deploy user (store encrypted via Vault)
1218
constructa_deploy_sudo_passwort: 'change-me-deploy-password'
1319

1420
# Make Ansible escalate with the password above
1521
ansible_become: true
1622
ansible_become_method: sudo
17-
ansible_become_password: "{{ constructa_deploy_sudo_passwort }}"
18-
ansible_become_flags: "-SE"
23+
ansible_become_password: '{{ constructa_deploy_sudo_passwort }}'
24+
ansible_become_flags: '-SE'
1925

2026
# Optional: login to image registry before pulling
2127
constructa_enable_registry_login: false
@@ -47,7 +53,8 @@ constructa_env:
4753
POSTGRES_USER: 'user'
4854
POSTGRES_PASSWORD: 'password'
4955
POSTGRES_DB: 'ex0'
50-
DATABASE_URL: 'postgresql://user:password@db:5432/ex0'
56+
POSTGRES_HOST: 'db'
57+
POSTGRES_PORT: '5432'
5158

5259
# MinIO/S3
5360
MINIO_ROOT_USER: 'minioadmin'
@@ -58,11 +65,10 @@ constructa_env:
5865
S3_SECRET_ACCESS_KEY: '${MINIO_ROOT_PASSWORD}'
5966
S3_BUCKET: '${MINIO_BUCKET}'
6067
S3_ENABLE_PATH_STYLE: '1'
61-
S3_PREVIEW_URL_EXPIRE_IN: '7200'
68+
S3_PREVIEW_URL_EXPIRE_IN: '900'
6269

6370
# Redis / BullMQ
6471
REDIS_PASSWORD: 'change-me-redis-password'
65-
REDIS_URL: 'redis://:${REDIS_PASSWORD}@redis:6379'
6672
BULLMQ_PREFIX: 'constructa'
6773
DAILY_CREDIT_REFILL_CRON: '0 3 * * *'
6874
JOB_DAILY_CREDIT_REFILL_URL: 'http://app:3000/api/jobs/daily-credit-refill'
@@ -91,3 +97,6 @@ constructa_env:
9197
POLAR_PRODUCT_BUSINESS_MONTHLY: ''
9298
POLAR_PRODUCT_CREDITS_50: ''
9399
POLAR_PRODUCT_CREDITS_100: ''
100+
101+
# Frontend logging helpers
102+
VITE_BROWSER_ECHO_ENABLED: 'false'

infra/ansible/inventory/hosts.ini

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[constructa]
2-
# Replace with the reachable hostname or IP of your Compose host
3-
# example: ex0-dev ansible_user=deploy
4-
ex0-dev ansible_host=5.75.251.186 ansible_user=deploy
2+
# Replace with the reachable hostname or IP of your Compose host.
3+
# Keep secrets/real IP addresses in hosts.local.ini (gitignored).
4+
ex0-dev ansible_host=<insert server ip> ansible_user=deploy

0 commit comments

Comments
 (0)