Skip to content

Commit 1f84779

Browse files
committed
Add Warning component and enhance self-hosting docs
1 parent 8210173 commit 1f84779

File tree

4 files changed

+195
-6
lines changed

4 files changed

+195
-6
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: "Test Self-Hosting"
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- "docker-compose.yml"
10+
- "docker-compose.coolify.yml"
11+
- "apps/media-server/**"
12+
- "apps/web/Dockerfile"
13+
- ".github/workflows/test-self-hosting.yml"
14+
pull_request:
15+
paths:
16+
- "docker-compose.yml"
17+
- "docker-compose.coolify.yml"
18+
- "apps/media-server/**"
19+
- "apps/web/Dockerfile"
20+
- ".github/workflows/test-self-hosting.yml"
21+
22+
jobs:
23+
test-docker-compose:
24+
name: Test Docker Compose Self-Hosting
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 15
27+
28+
steps:
29+
- name: Checkout Repository
30+
uses: actions/checkout@v4
31+
32+
- name: Start Cap with Docker Compose
33+
run: |
34+
echo "Starting Cap..."
35+
docker compose up -d
36+
echo "Waiting for services to initialize..."
37+
38+
- name: Wait for MySQL to be healthy
39+
run: |
40+
echo "Waiting for MySQL..."
41+
timeout 120 bash -c 'until docker inspect cap-mysql --format="{{.State.Health.Status}}" 2>/dev/null | grep -q "healthy"; do sleep 2; done'
42+
echo "MySQL is healthy"
43+
44+
- name: Wait for MinIO to be healthy
45+
run: |
46+
echo "Waiting for MinIO..."
47+
timeout 60 bash -c 'until docker inspect cap-minio --format="{{.State.Health.Status}}" 2>/dev/null | grep -q "healthy"; do sleep 2; done'
48+
echo "MinIO is healthy"
49+
50+
- name: Wait for Media Server to be healthy
51+
run: |
52+
echo "Waiting for Media Server..."
53+
timeout 60 bash -c 'until docker inspect cap-media-server --format="{{.State.Health.Status}}" 2>/dev/null | grep -q "healthy"; do sleep 2; done'
54+
echo "Media Server is healthy"
55+
56+
- name: Wait for Cap Web to be healthy
57+
run: |
58+
echo "Waiting for Cap Web..."
59+
timeout 180 bash -c 'until docker inspect cap-web --format="{{.State.Health.Status}}" 2>/dev/null | grep -q "healthy"; do sleep 2; done'
60+
echo "Cap Web is healthy"
61+
62+
- name: Show service status
63+
run: docker compose ps
64+
65+
- name: Test Cap Web responds
66+
run: |
67+
echo "Testing Cap Web..."
68+
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000)
69+
if [ "$response" = "307" ] || [ "$response" = "200" ]; then
70+
echo "✓ Cap Web responds (HTTP $response)"
71+
else
72+
echo "✗ Cap Web failed (HTTP $response)"
73+
exit 1
74+
fi
75+
76+
- name: Test login page renders
77+
run: |
78+
echo "Testing login page..."
79+
if curl -s http://localhost:3000/login | grep -q "Cap"; then
80+
echo "✓ Login page renders correctly"
81+
else
82+
echo "✗ Login page failed to render"
83+
exit 1
84+
fi
85+
86+
- name: Test Media Server health
87+
run: |
88+
echo "Testing Media Server..."
89+
health=$(docker exec cap-web wget -qO- http://media-server:3456/health)
90+
if echo "$health" | grep -q '"status":"ok"'; then
91+
echo "✓ Media Server is healthy"
92+
echo " Response: $health"
93+
else
94+
echo "✗ Media Server health check failed"
95+
exit 1
96+
fi
97+
98+
- name: Test database has tables
99+
run: |
100+
echo "Testing database..."
101+
tables=$(docker exec cap-mysql mysql -ucap -pcap-local-pwd-123 cap -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='cap';" -s -N 2>/dev/null)
102+
if [ "$tables" -gt 0 ]; then
103+
echo "✓ Database has $tables tables (migrations ran successfully)"
104+
else
105+
echo "✗ Database has no tables"
106+
exit 1
107+
fi
108+
109+
- name: Test MinIO bucket exists
110+
run: |
111+
echo "Testing MinIO bucket..."
112+
if docker compose logs minio-setup 2>&1 | grep -q "Bucket created successfully\|already exists"; then
113+
echo "✓ MinIO bucket is configured"
114+
else
115+
echo "✗ MinIO bucket setup failed"
116+
docker compose logs minio-setup
117+
exit 1
118+
fi
119+
120+
- name: Show Cap Web logs
121+
if: always()
122+
run: |
123+
echo "=== Cap Web Logs ==="
124+
docker compose logs cap-web | tail -50
125+
126+
- name: Show all logs on failure
127+
if: failure()
128+
run: |
129+
echo "=== All Service Logs ==="
130+
docker compose logs
131+
132+
- name: Cleanup
133+
if: always()
134+
run: |
135+
docker compose down -v
136+
echo "Cleanup complete"

apps/web/app/(site)/docs/[...slug]/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Metadata } from "next";
33
import Image from "next/image";
44
import Link from "next/link";
55
import { notFound } from "next/navigation";
6-
import { MDXRemote } from "next-mdx-remote/rsc";
6+
import { CustomMDX } from "@/components/mdx";
77
import type { DocMetadata } from "@/utils/blog";
88
import { getDocs } from "@/utils/blog";
99

@@ -121,7 +121,7 @@ export default async function DocPage(props: DocProps) {
121121
{/* Show root category content if it exists */}
122122
{rootDoc && (
123123
<div className="mb-8">
124-
<MDXRemote source={rootDoc.content} />
124+
<CustomMDX source={rootDoc.content} />
125125
<hr className="my-8" />
126126
</div>
127127
)}
@@ -194,7 +194,7 @@ export default async function DocPage(props: DocProps) {
194194
<h1 className="mb-2">{doc.metadata.title}</h1>
195195
</header>
196196
<hr className="my-6" />
197-
<MDXRemote source={doc.content} />
197+
<CustomMDX source={doc.content} />
198198
</div>
199199
</article>
200200
);

apps/web/components/mdx.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,36 @@ function Callout(props: CalloutProps) {
7575
);
7676
}
7777

78+
interface WarningProps {
79+
title?: string;
80+
children: ReactNode;
81+
}
82+
83+
function Warning(props: WarningProps) {
84+
return (
85+
<div className="px-4 py-3 border-2 border-red-300 bg-red-50 rounded-lg text-sm mb-8 dark:bg-red-950 dark:border-red-800">
86+
<div className="flex items-center gap-2 font-semibold text-red-700 dark:text-red-400 mb-2">
87+
<svg
88+
xmlns="http://www.w3.org/2000/svg"
89+
viewBox="0 0 20 20"
90+
fill="currentColor"
91+
className="w-5 h-5"
92+
>
93+
<path
94+
fillRule="evenodd"
95+
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
96+
clipRule="evenodd"
97+
/>
98+
</svg>
99+
{props.title || "Warning"}
100+
</div>
101+
<div className="text-red-700 dark:text-red-300 [&>p]:m-0 [&>ul]:m-0 [&>ul]:mt-2">
102+
{props.children}
103+
</div>
104+
</div>
105+
);
106+
}
107+
78108
function slugify(str: string) {
79109
return str
80110
.toString()
@@ -114,6 +144,7 @@ const components = {
114144
Image: RoundedImage,
115145
a: CustomLink,
116146
Callout,
147+
Warning,
117148
Table,
118149
};
119150

apps/web/content/docs/self-hosting.mdx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ docker compose up -d
3636

3737
That's it! Cap will be available at `http://localhost:3000`.
3838

39+
<Warning title="Security Notice for Production Deployments">
40+
The default `docker-compose.yml` includes placeholder secrets for quick local testing. **If you're deploying to a public server or production environment**, you must change these values. See the [Production Checklist](#production-checklist) below.
41+
</Warning>
42+
3943
Login links will appear in the logs since email isn't configured:
4044
```bash
4145
docker compose logs cap-web
@@ -133,12 +137,30 @@ GOOGLE_CLIENT_SECRET=your-secret
133137

134138
## Production Checklist
135139

136-
> **Important:** The default configuration uses placeholder passwords suitable for local testing only. For production, you must set secure values via environment variables.
140+
<Warning title="Critical: Change Default Secrets Before Going Public">
141+
The default `docker-compose.yml` contains **hardcoded placeholder secrets** that are visible in the public repository. Anyone who knows you're using Cap with defaults could potentially:
142+
143+
- **Forge authentication sessions** (via `NEXTAUTH_SECRET`)
144+
- **Decrypt sensitive database fields** (via `DATABASE_ENCRYPTION_KEY`)
145+
- **Spoof webhook requests** (via `MEDIA_SERVER_WEBHOOK_SECRET`)
146+
147+
This is fine for local development or testing on a private network, but **you must generate unique secrets before exposing Cap to the internet**.
148+
</Warning>
149+
150+
**Generate secure secrets:**
151+
```bash
152+
openssl rand -hex 32
153+
```
154+
155+
Run this command three times to generate values for:
156+
- `NEXTAUTH_SECRET`
157+
- `DATABASE_ENCRYPTION_KEY`
158+
- `MEDIA_SERVER_WEBHOOK_SECRET`
137159

138-
For production deployments:
160+
**Full production checklist:**
139161

140162
- [ ] Set secure passwords: `MYSQL_PASSWORD`, `MINIO_ROOT_PASSWORD`
141-
- [ ] Set secure secrets: `DATABASE_ENCRYPTION_KEY`, `NEXTAUTH_SECRET`, `MEDIA_SERVER_WEBHOOK_SECRET` (use `openssl rand -hex 32`)
163+
- [ ] Set secure secrets: `DATABASE_ENCRYPTION_KEY`, `NEXTAUTH_SECRET`, `MEDIA_SERVER_WEBHOOK_SECRET`
142164
- [ ] Set `CAP_URL` to your public URL
143165
- [ ] Set `S3_PUBLIC_URL` to your MinIO/S3 public URL
144166
- [ ] Configure a reverse proxy (nginx, Caddy, Traefik) with SSL

0 commit comments

Comments
 (0)