container-superposition 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -1014
- package/dist/scripts/init.js +512 -238
- package/dist/scripts/init.js.map +1 -1
- package/dist/tool/commands/adopt.d.ts +62 -0
- package/dist/tool/commands/adopt.d.ts.map +1 -0
- package/dist/tool/commands/adopt.js +767 -0
- package/dist/tool/commands/adopt.js.map +1 -0
- package/dist/tool/commands/doctor.js +2 -2
- package/dist/tool/commands/explain.d.ts.map +1 -1
- package/dist/tool/commands/explain.js +88 -0
- package/dist/tool/commands/explain.js.map +1 -1
- package/dist/tool/commands/hash.d.ts +36 -0
- package/dist/tool/commands/hash.d.ts.map +1 -0
- package/dist/tool/commands/hash.js +242 -0
- package/dist/tool/commands/hash.js.map +1 -0
- package/dist/tool/commands/plan.d.ts +53 -0
- package/dist/tool/commands/plan.d.ts.map +1 -1
- package/dist/tool/commands/plan.js +784 -42
- package/dist/tool/commands/plan.js.map +1 -1
- package/dist/tool/questionnaire/composer.d.ts +12 -3
- package/dist/tool/questionnaire/composer.d.ts.map +1 -1
- package/dist/tool/questionnaire/composer.js +133 -20
- package/dist/tool/questionnaire/composer.js.map +1 -1
- package/dist/tool/schema/project-config.d.ts +15 -0
- package/dist/tool/schema/project-config.d.ts.map +1 -0
- package/dist/tool/schema/project-config.js +359 -0
- package/dist/tool/schema/project-config.js.map +1 -0
- package/dist/tool/schema/types.d.ts +57 -1
- package/dist/tool/schema/types.d.ts.map +1 -1
- package/dist/tool/utils/backup.d.ts +23 -0
- package/dist/tool/utils/backup.d.ts.map +1 -0
- package/dist/tool/utils/backup.js +123 -0
- package/dist/tool/utils/backup.js.map +1 -0
- package/dist/tool/utils/gitignore.d.ts +15 -0
- package/dist/tool/utils/gitignore.d.ts.map +1 -0
- package/dist/tool/utils/gitignore.js +41 -0
- package/dist/tool/utils/gitignore.js.map +1 -0
- package/dist/tool/utils/services-export.d.ts +14 -0
- package/dist/tool/utils/services-export.d.ts.map +1 -0
- package/dist/tool/utils/services-export.js +478 -0
- package/dist/tool/utils/services-export.js.map +1 -0
- package/dist/tool/utils/summary.d.ts +69 -0
- package/dist/tool/utils/summary.d.ts.map +1 -0
- package/dist/tool/utils/summary.js +260 -0
- package/dist/tool/utils/summary.js.map +1 -0
- package/docs/README.md +12 -2
- package/docs/adopt.md +196 -0
- package/docs/custom-patches.md +1 -1
- package/docs/discovery-commands.md +55 -3
- package/docs/examples.md +40 -6
- package/docs/filesystem-contract.md +58 -0
- package/docs/hash.md +183 -0
- package/docs/minimal-and-editor.md +1 -1
- package/docs/overlays.md +108 -5
- package/docs/presets-architecture.md +1 -1
- package/docs/presets.md +1 -1
- package/docs/publishing.md +36 -23
- package/docs/security.md +43 -0
- package/docs/specs/001-verbose-plan-graph/checklists/requirements.md +36 -0
- package/docs/specs/001-verbose-plan-graph/contracts/plan-verbose-output.md +96 -0
- package/docs/specs/001-verbose-plan-graph/data-model.md +111 -0
- package/docs/specs/001-verbose-plan-graph/plan.md +127 -0
- package/docs/specs/001-verbose-plan-graph/quickstart.md +106 -0
- package/docs/specs/001-verbose-plan-graph/research.md +100 -0
- package/docs/specs/001-verbose-plan-graph/spec.md +128 -0
- package/docs/specs/001-verbose-plan-graph/tasks.md +223 -0
- package/docs/specs/002-superposition-config-file/checklists/requirements.md +36 -0
- package/docs/specs/002-superposition-config-file/contracts/init-project-config.md +98 -0
- package/docs/specs/002-superposition-config-file/data-model.md +126 -0
- package/docs/specs/002-superposition-config-file/plan.md +208 -0
- package/docs/specs/002-superposition-config-file/quickstart.md +140 -0
- package/docs/specs/002-superposition-config-file/research.md +144 -0
- package/docs/specs/002-superposition-config-file/spec.md +130 -0
- package/docs/specs/002-superposition-config-file/tasks.md +213 -0
- package/docs/team-workflow.md +27 -1
- package/docs/workflows.md +136 -0
- package/overlays/.presets/microservice.yml +32 -6
- package/overlays/.presets/sdd.yml +84 -0
- package/overlays/.presets/web-api.yml +76 -56
- package/overlays/README.md +7 -1
- package/overlays/amp/README.md +70 -0
- package/overlays/amp/devcontainer.patch.json +3 -0
- package/overlays/amp/overlay.yml +15 -0
- package/overlays/amp/setup.sh +21 -0
- package/overlays/amp/verify.sh +21 -0
- package/overlays/claude-code/README.md +83 -0
- package/overlays/claude-code/devcontainer.patch.json +3 -0
- package/overlays/claude-code/overlay.yml +15 -0
- package/overlays/claude-code/setup.sh +21 -0
- package/overlays/claude-code/verify.sh +21 -0
- package/overlays/cloudflared/README.md +190 -0
- package/overlays/cloudflared/devcontainer.patch.json +3 -0
- package/overlays/cloudflared/overlay.yml +15 -0
- package/overlays/cloudflared/setup.sh +49 -0
- package/overlays/cloudflared/verify.sh +21 -0
- package/overlays/direnv/README.md +6 -4
- package/overlays/direnv/setup.sh +0 -12
- package/overlays/gemini-cli/README.md +77 -0
- package/overlays/gemini-cli/devcontainer.patch.json +3 -0
- package/overlays/gemini-cli/overlay.yml +15 -0
- package/overlays/gemini-cli/setup.sh +21 -0
- package/overlays/gemini-cli/verify.sh +21 -0
- package/overlays/grpc-tools/README.md +242 -0
- package/overlays/grpc-tools/devcontainer.patch.json +14 -0
- package/overlays/grpc-tools/overlay.yml +14 -0
- package/overlays/grpc-tools/setup.sh +57 -0
- package/overlays/grpc-tools/verify.sh +47 -0
- package/overlays/keycloak/.env.example +5 -0
- package/overlays/keycloak/README.md +238 -0
- package/overlays/keycloak/devcontainer.patch.json +17 -0
- package/overlays/keycloak/docker-compose.yml +32 -0
- package/overlays/keycloak/overlay.yml +23 -0
- package/overlays/keycloak/verify.sh +54 -0
- package/overlays/mailpit/.env.example +4 -0
- package/overlays/mailpit/README.md +191 -0
- package/overlays/mailpit/devcontainer.patch.json +20 -0
- package/overlays/mailpit/docker-compose.yml +17 -0
- package/overlays/mailpit/overlay.yml +26 -0
- package/overlays/mailpit/verify.sh +52 -0
- package/overlays/ngrok/overlay.yml +2 -1
- package/overlays/opencode/README.md +76 -0
- package/overlays/opencode/devcontainer.patch.json +3 -0
- package/overlays/opencode/overlay.yml +14 -0
- package/overlays/opencode/setup.sh +21 -0
- package/overlays/opencode/verify.sh +21 -0
- package/overlays/python/README.md +51 -35
- package/overlays/python/devcontainer.patch.json +7 -4
- package/overlays/python/setup.sh +50 -23
- package/overlays/python/verify.sh +29 -1
- package/overlays/spec-kit/README.md +181 -0
- package/overlays/spec-kit/devcontainer.patch.json +6 -0
- package/overlays/spec-kit/overlay.yml +19 -0
- package/overlays/spec-kit/setup.sh +45 -0
- package/overlays/spec-kit/verify.sh +33 -0
- package/overlays/windsurf-cli/README.md +69 -0
- package/overlays/windsurf-cli/devcontainer.patch.json +3 -0
- package/overlays/windsurf-cli/overlay.yml +15 -0
- package/overlays/windsurf-cli/setup.sh +21 -0
- package/overlays/windsurf-cli/verify.sh +21 -0
- package/package.json +1 -1
- package/tool/schema/config.schema.json +138 -9
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Keycloak Overlay
|
|
2
|
+
|
|
3
|
+
Open-source identity and access management for developing apps with OAuth2/OIDC authentication.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Keycloak 26** - Latest stable version with OIDC/OAuth2 support
|
|
8
|
+
- **Admin console** - Web UI for managing realms, clients, and users (port 8180)
|
|
9
|
+
- **OIDC/OAuth2** - Full OpenID Connect and OAuth 2.0 support
|
|
10
|
+
- **PostgreSQL backend** - Uses the PostgreSQL overlay as database
|
|
11
|
+
- **Docker Compose service** - Runs as separate container
|
|
12
|
+
- **Development mode** - Pre-configured for local development (no TLS required)
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
This overlay adds Keycloak as a Docker Compose service alongside your development container. It requires the `postgres` overlay to provide the database backend.
|
|
17
|
+
|
|
18
|
+
**Architecture:**
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Development Container
|
|
22
|
+
└─ Your application code
|
|
23
|
+
└─ Connects to keycloak:8180 for auth
|
|
24
|
+
|
|
25
|
+
Keycloak Container (port 8180)
|
|
26
|
+
└─ Admin console
|
|
27
|
+
└─ OIDC/OAuth2 endpoints
|
|
28
|
+
└─ Connects to postgres:5432
|
|
29
|
+
|
|
30
|
+
PostgreSQL Container (port 5432)
|
|
31
|
+
└─ Keycloak database storage
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The Keycloak service is accessible from the dev container using the hostname `keycloak`.
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
### Environment Variables
|
|
39
|
+
|
|
40
|
+
The overlay includes a `.env.example` file. Copy it to `.env` and customize:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd .devcontainer
|
|
44
|
+
cp .env.example .env
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Default values (.env.example):**
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Keycloak Configuration
|
|
51
|
+
KEYCLOAK_VERSION=26.0
|
|
52
|
+
KEYCLOAK_PORT=8180
|
|
53
|
+
KEYCLOAK_ADMIN=admin
|
|
54
|
+
KEYCLOAK_ADMIN_PASSWORD=admin
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
⚠️ **Security Note:** Default credentials (`admin`/`admin`) are for development only. Never use these in production.
|
|
58
|
+
|
|
59
|
+
### PostgreSQL Integration
|
|
60
|
+
|
|
61
|
+
Keycloak uses the PostgreSQL overlay's environment variables:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
POSTGRES_DB=devdb # Database name
|
|
65
|
+
POSTGRES_USER=postgres # Database user
|
|
66
|
+
POSTGRES_PASSWORD=postgres # Database password
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
These are configured in the `postgres` overlay's `.env.example`.
|
|
70
|
+
|
|
71
|
+
## Common Commands
|
|
72
|
+
|
|
73
|
+
### Access Admin Console
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Open in browser
|
|
77
|
+
open http://localhost:8180
|
|
78
|
+
|
|
79
|
+
# Admin credentials
|
|
80
|
+
# Username: admin (or KEYCLOAK_ADMIN value)
|
|
81
|
+
# Password: admin (or KEYCLOAK_ADMIN_PASSWORD value)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Realm Management
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# List realms via Admin REST API
|
|
88
|
+
curl -s \
|
|
89
|
+
-u admin:admin \
|
|
90
|
+
http://localhost:8180/admin/realms | jq '.[].realm'
|
|
91
|
+
|
|
92
|
+
# Create a new realm
|
|
93
|
+
curl -s -X POST \
|
|
94
|
+
-H "Content-Type: application/json" \
|
|
95
|
+
-u admin:admin \
|
|
96
|
+
http://localhost:8180/admin/realms \
|
|
97
|
+
-d '{"realm": "myrealm", "enabled": true}'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### OIDC Discovery
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Discover OIDC endpoints for the master realm
|
|
104
|
+
curl -s http://localhost:8180/realms/master/.well-known/openid-configuration | jq .
|
|
105
|
+
|
|
106
|
+
# Get discovery for a custom realm
|
|
107
|
+
curl -s http://localhost:8180/realms/myrealm/.well-known/openid-configuration | jq .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Client Credentials Flow
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Get access token using client credentials
|
|
114
|
+
curl -s -X POST \
|
|
115
|
+
http://localhost:8180/realms/master/protocol/openid-connect/token \
|
|
116
|
+
-d "client_id=admin-cli" \
|
|
117
|
+
-d "username=admin" \
|
|
118
|
+
-d "password=admin" \
|
|
119
|
+
-d "grant_type=password" | jq .access_token
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Token Introspection
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
TOKEN=$(curl -s -X POST \
|
|
126
|
+
http://localhost:8180/realms/master/protocol/openid-connect/token \
|
|
127
|
+
-d "client_id=admin-cli" \
|
|
128
|
+
-d "username=admin" \
|
|
129
|
+
-d "password=admin" \
|
|
130
|
+
-d "grant_type=password" | jq -r .access_token)
|
|
131
|
+
|
|
132
|
+
# Decode token (base64)
|
|
133
|
+
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Application Integration
|
|
137
|
+
|
|
138
|
+
### Node.js (using openid-client)
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
import { Issuer } from 'openid-client';
|
|
142
|
+
|
|
143
|
+
const keycloakIssuer = await Issuer.discover('http://keycloak:8180/realms/myrealm');
|
|
144
|
+
|
|
145
|
+
const client = new keycloakIssuer.Client({
|
|
146
|
+
client_id: 'my-app',
|
|
147
|
+
client_secret: 'my-secret',
|
|
148
|
+
redirect_uris: ['http://localhost:3000/callback'],
|
|
149
|
+
response_types: ['code'],
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Python (using requests-oauthlib)
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from requests_oauthlib import OAuth2Session
|
|
157
|
+
|
|
158
|
+
oauth = OAuth2Session(
|
|
159
|
+
client_id="my-app",
|
|
160
|
+
redirect_uri="http://localhost:5000/callback",
|
|
161
|
+
scope=["openid", "profile", "email"]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
authorization_url, state = oauth.authorization_url(
|
|
165
|
+
"http://keycloak:8180/realms/myrealm/protocol/openid-connect/auth"
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### .NET (using Microsoft.AspNetCore.Authentication.OpenIdConnect)
|
|
170
|
+
|
|
171
|
+
```csharp
|
|
172
|
+
builder.Services.AddAuthentication(options => {
|
|
173
|
+
options.DefaultScheme = "Cookies";
|
|
174
|
+
options.DefaultChallengeScheme = "oidc";
|
|
175
|
+
})
|
|
176
|
+
.AddCookie("Cookies")
|
|
177
|
+
.AddOpenIdConnect("oidc", options => {
|
|
178
|
+
options.Authority = "http://keycloak:8180/realms/myrealm";
|
|
179
|
+
options.ClientId = "my-app";
|
|
180
|
+
options.ClientSecret = "my-secret";
|
|
181
|
+
options.ResponseType = "code";
|
|
182
|
+
options.RequireHttpsMetadata = false; // Development only
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Use Cases
|
|
187
|
+
|
|
188
|
+
- **OAuth2/OIDC integration testing** - Test authentication flows end-to-end
|
|
189
|
+
- **Multi-tenant applications** - Create separate realms per tenant
|
|
190
|
+
- **SSO development** - Single Sign-On across multiple local services
|
|
191
|
+
- **Identity federation** - Test social login, LDAP, SAML integration
|
|
192
|
+
- **Role-based access control** - Define and test permissions locally
|
|
193
|
+
|
|
194
|
+
## Troubleshooting
|
|
195
|
+
|
|
196
|
+
### Keycloak takes too long to start
|
|
197
|
+
|
|
198
|
+
Keycloak can take 60–90 seconds on first startup (database schema creation). Wait for the health check to pass:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
docker-compose logs -f keycloak
|
|
202
|
+
# Look for: "Keycloak X.X started"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Database connection errors
|
|
206
|
+
|
|
207
|
+
Ensure PostgreSQL is running and healthy before Keycloak starts:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
docker-compose ps postgres
|
|
211
|
+
# Should show "healthy"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Cannot connect from application
|
|
215
|
+
|
|
216
|
+
Use `keycloak` (not `localhost`) as the hostname when connecting from inside the container:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Correct (from inside dev container)
|
|
220
|
+
http://keycloak:8180/realms/master
|
|
221
|
+
|
|
222
|
+
# Correct (from host machine browser)
|
|
223
|
+
http://localhost:8180/realms/master
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## References
|
|
227
|
+
|
|
228
|
+
- [Keycloak Documentation](https://www.keycloak.org/documentation)
|
|
229
|
+
- [Keycloak Admin REST API](https://www.keycloak.org/docs-api/latest/rest-api/)
|
|
230
|
+
- [OpenID Connect Specification](https://openid.net/connect/)
|
|
231
|
+
- [Keycloak Docker Image](https://quay.io/repository/keycloak/keycloak)
|
|
232
|
+
|
|
233
|
+
**Related Overlays:**
|
|
234
|
+
|
|
235
|
+
- `postgres` - Required database backend
|
|
236
|
+
- `nodejs` - Node.js application development
|
|
237
|
+
- `python` - Python application development
|
|
238
|
+
- `dotnet` - .NET application development
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.base.schema.json",
|
|
3
|
+
"runServices": ["keycloak"],
|
|
4
|
+
"_serviceOrder": 10,
|
|
5
|
+
"forwardPorts": [8180],
|
|
6
|
+
"portsAttributes": {
|
|
7
|
+
"8180": {
|
|
8
|
+
"label": "Keycloak Admin Console",
|
|
9
|
+
"onAutoForward": "openBrowser"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"remoteEnv": {
|
|
13
|
+
"KEYCLOAK_HOST": "keycloak",
|
|
14
|
+
"KEYCLOAK_PORT": "8180",
|
|
15
|
+
"KEYCLOAK_ISSUER": "http://keycloak:8180/realms/master"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
keycloak:
|
|
5
|
+
image: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION:-26.0}
|
|
6
|
+
command: start-dev
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
environment:
|
|
9
|
+
KC_DB: postgres
|
|
10
|
+
KC_DB_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-devdb}
|
|
11
|
+
KC_DB_USERNAME: ${POSTGRES_USER:-postgres}
|
|
12
|
+
KC_DB_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
|
13
|
+
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-admin}
|
|
14
|
+
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
|
|
15
|
+
KC_HTTP_PORT: 8180
|
|
16
|
+
KC_HOSTNAME_STRICT: 'false'
|
|
17
|
+
KC_HTTP_ENABLED: 'true'
|
|
18
|
+
ports:
|
|
19
|
+
- '${KEYCLOAK_PORT:-8180}:8180'
|
|
20
|
+
depends_on:
|
|
21
|
+
- postgres
|
|
22
|
+
networks:
|
|
23
|
+
- devnet
|
|
24
|
+
healthcheck:
|
|
25
|
+
test: ['CMD-SHELL', 'curl -sf http://localhost:8180/health/ready || exit 1']
|
|
26
|
+
interval: 15s
|
|
27
|
+
timeout: 10s
|
|
28
|
+
retries: 10
|
|
29
|
+
start_period: 60s
|
|
30
|
+
|
|
31
|
+
networks:
|
|
32
|
+
devnet:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
id: keycloak
|
|
2
|
+
name: Keycloak
|
|
3
|
+
description: Open-source identity and access management (OIDC/OAuth2)
|
|
4
|
+
category: dev
|
|
5
|
+
supports:
|
|
6
|
+
- compose
|
|
7
|
+
requires:
|
|
8
|
+
- postgres
|
|
9
|
+
suggests: []
|
|
10
|
+
conflicts: []
|
|
11
|
+
tags:
|
|
12
|
+
- dev
|
|
13
|
+
- auth
|
|
14
|
+
- oidc
|
|
15
|
+
- oauth2
|
|
16
|
+
- identity
|
|
17
|
+
ports:
|
|
18
|
+
- port: 8180
|
|
19
|
+
service: keycloak
|
|
20
|
+
protocol: http
|
|
21
|
+
description: Keycloak admin console and auth endpoints
|
|
22
|
+
path: /
|
|
23
|
+
onAutoForward: openBrowser
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Verification script for Keycloak overlay
|
|
3
|
+
# Confirms Keycloak is running and accessible
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
echo "🔍 Verifying Keycloak overlay..."
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
# Check if curl is available
|
|
11
|
+
echo "1️⃣ Checking curl availability..."
|
|
12
|
+
if ! command -v curl &> /dev/null; then
|
|
13
|
+
echo " ❌ curl not found"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
echo " ✅ curl found"
|
|
17
|
+
|
|
18
|
+
# Check Keycloak health endpoint
|
|
19
|
+
echo ""
|
|
20
|
+
echo "2️⃣ Checking Keycloak service..."
|
|
21
|
+
KEYCLOAK_HOST="${KEYCLOAK_HOST:-keycloak}"
|
|
22
|
+
KEYCLOAK_PORT="${KEYCLOAK_PORT:-8180}"
|
|
23
|
+
KEYCLOAK_READY=false
|
|
24
|
+
|
|
25
|
+
for i in {1..40}; do
|
|
26
|
+
if curl -sf "http://${KEYCLOAK_HOST}:${KEYCLOAK_PORT}/health/ready" &> /dev/null; then
|
|
27
|
+
echo " ✅ Keycloak service is ready"
|
|
28
|
+
KEYCLOAK_READY=true
|
|
29
|
+
break
|
|
30
|
+
fi
|
|
31
|
+
sleep 3
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
if [ "$KEYCLOAK_READY" = false ]; then
|
|
35
|
+
echo " ❌ Keycloak service not ready after 2 minutes"
|
|
36
|
+
echo " ℹ️ Keycloak can take a while to start on first run"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Check OIDC discovery endpoint
|
|
41
|
+
echo ""
|
|
42
|
+
echo "3️⃣ Checking OIDC discovery endpoint..."
|
|
43
|
+
if curl -sf "http://${KEYCLOAK_HOST}:${KEYCLOAK_PORT}/realms/master/.well-known/openid-configuration" &> /dev/null; then
|
|
44
|
+
echo " ✅ OIDC discovery endpoint is accessible"
|
|
45
|
+
else
|
|
46
|
+
echo " ❌ OIDC discovery endpoint not accessible"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
echo ""
|
|
51
|
+
echo "✅ Keycloak overlay verification complete"
|
|
52
|
+
echo " Admin console: http://localhost:${KEYCLOAK_PORT}"
|
|
53
|
+
echo " Admin credentials: admin / admin (default)"
|
|
54
|
+
echo " OIDC discovery: http://localhost:${KEYCLOAK_PORT}/realms/master/.well-known/openid-configuration"
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Mailpit Overlay
|
|
2
|
+
|
|
3
|
+
Email testing tool that captures all outbound emails, with a web UI for browsing and an API for automated testing.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Mailpit** - Fast, lightweight email testing tool
|
|
8
|
+
- **Web UI** - Beautiful interface for viewing captured emails (port 8025)
|
|
9
|
+
- **SMTP server** - Accepts all email without authentication (port 1025)
|
|
10
|
+
- **REST API** - Programmatic access to captured messages
|
|
11
|
+
- **Search and filter** - Find emails by recipient, subject, or content
|
|
12
|
+
- **No accidental sends** - All emails are captured locally, never delivered
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
This overlay adds Mailpit as a Docker Compose service. Configure your application to send emails via SMTP to `mailpit:1025` (from inside the container) and all emails will be captured and visible in the web UI.
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
### Environment Variables
|
|
21
|
+
|
|
22
|
+
The overlay includes a `.env.example` file. Copy it to `.env` and customize:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd .devcontainer
|
|
26
|
+
cp .env.example .env
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Default values (.env.example):**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Mailpit Configuration
|
|
33
|
+
MAILPIT_VERSION=latest
|
|
34
|
+
MAILPIT_UI_PORT=8025
|
|
35
|
+
MAILPIT_SMTP_PORT=1025
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### SMTP Settings for Your Application
|
|
39
|
+
|
|
40
|
+
Configure your application to use these settings:
|
|
41
|
+
|
|
42
|
+
| Setting | Value |
|
|
43
|
+
| -------------- | ------------------------------------------------------ |
|
|
44
|
+
| SMTP host | `mailpit` (inside container) / `localhost` (from host) |
|
|
45
|
+
| SMTP port | `1025` |
|
|
46
|
+
| Authentication | None required |
|
|
47
|
+
| TLS/SSL | Not required |
|
|
48
|
+
|
|
49
|
+
## Common Commands
|
|
50
|
+
|
|
51
|
+
### View Captured Emails
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Open web UI in browser
|
|
55
|
+
open http://localhost:8025
|
|
56
|
+
|
|
57
|
+
# List messages via API
|
|
58
|
+
curl -s http://localhost:8025/api/v1/messages | jq .
|
|
59
|
+
|
|
60
|
+
# Get message count
|
|
61
|
+
curl -s http://localhost:8025/api/v1/info | jq .Messages
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Search Emails
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Search by query
|
|
68
|
+
curl -s "http://localhost:8025/api/v1/messages?query=password+reset" | jq .
|
|
69
|
+
|
|
70
|
+
# Filter by recipient
|
|
71
|
+
curl -s "http://localhost:8025/api/v1/messages?query=to:user@example.com" | jq .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Clear All Emails
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Delete all messages via API
|
|
78
|
+
curl -s -X DELETE http://localhost:8025/api/v1/messages
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Application Configuration Examples
|
|
82
|
+
|
|
83
|
+
### Node.js (using nodemailer)
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
import nodemailer from 'nodemailer';
|
|
87
|
+
|
|
88
|
+
const transporter = nodemailer.createTransport({
|
|
89
|
+
host: process.env.SMTP_HOST || 'mailpit',
|
|
90
|
+
port: parseInt(process.env.SMTP_PORT || '1025'),
|
|
91
|
+
secure: false,
|
|
92
|
+
auth: null, // No authentication needed
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await transporter.sendMail({
|
|
96
|
+
from: 'noreply@example.com',
|
|
97
|
+
to: 'user@example.com',
|
|
98
|
+
subject: 'Welcome!',
|
|
99
|
+
text: 'Welcome to our app.',
|
|
100
|
+
html: '<b>Welcome to our app.</b>',
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Python (using smtplib)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import smtplib
|
|
108
|
+
from email.mime.text import MIMEText
|
|
109
|
+
import os
|
|
110
|
+
|
|
111
|
+
def send_email(to, subject, body):
|
|
112
|
+
msg = MIMEText(body, 'html')
|
|
113
|
+
msg['Subject'] = subject
|
|
114
|
+
msg['From'] = 'noreply@example.com'
|
|
115
|
+
msg['To'] = to
|
|
116
|
+
|
|
117
|
+
smtp_host = os.getenv('SMTP_HOST', 'mailpit')
|
|
118
|
+
smtp_port = int(os.getenv('SMTP_PORT', '1025'))
|
|
119
|
+
|
|
120
|
+
with smtplib.SMTP(smtp_host, smtp_port) as server:
|
|
121
|
+
server.sendmail(msg['From'], [to], msg.as_string())
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### .NET (using MailKit)
|
|
125
|
+
|
|
126
|
+
```csharp
|
|
127
|
+
using MailKit.Net.Smtp;
|
|
128
|
+
using MimeKit;
|
|
129
|
+
|
|
130
|
+
var message = new MimeMessage();
|
|
131
|
+
message.From.Add(new MailboxAddress("App", "noreply@example.com"));
|
|
132
|
+
message.To.Add(new MailboxAddress("User", "user@example.com"));
|
|
133
|
+
message.Subject = "Welcome!";
|
|
134
|
+
message.Body = new TextPart("html") { Text = "<b>Welcome!</b>" };
|
|
135
|
+
|
|
136
|
+
using var client = new SmtpClient();
|
|
137
|
+
await client.ConnectAsync(
|
|
138
|
+
Environment.GetEnvironmentVariable("SMTP_HOST") ?? "mailpit",
|
|
139
|
+
int.Parse(Environment.GetEnvironmentVariable("SMTP_PORT") ?? "1025"),
|
|
140
|
+
false
|
|
141
|
+
);
|
|
142
|
+
await client.SendAsync(message);
|
|
143
|
+
await client.DisconnectAsync(true);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Automated Testing
|
|
147
|
+
|
|
148
|
+
### Check Email was Sent
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Send test email, then verify it was received
|
|
152
|
+
curl -s http://localhost:8025/api/v1/messages | jq '.messages | length'
|
|
153
|
+
# Should be > 0
|
|
154
|
+
|
|
155
|
+
# Get latest email subject
|
|
156
|
+
curl -s http://localhost:8025/api/v1/messages | jq '.messages[0].Subject'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Integration Test Pattern
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
// After triggering an action that sends email:
|
|
163
|
+
const response = await fetch('http://localhost:8025/api/v1/messages');
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
|
|
166
|
+
const latestEmail = data.messages[0];
|
|
167
|
+
expect(latestEmail.To[0].Address).toBe('user@example.com');
|
|
168
|
+
expect(latestEmail.Subject).toBe('Password Reset');
|
|
169
|
+
|
|
170
|
+
// Clean up for next test
|
|
171
|
+
await fetch('http://localhost:8025/api/v1/messages', { method: 'DELETE' });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Use Cases
|
|
175
|
+
|
|
176
|
+
- **Email flow testing** - Verify registration, password reset, and notification emails
|
|
177
|
+
- **Template development** - Preview rendered HTML email templates
|
|
178
|
+
- **Integration testing** - Assert emails are sent with correct content
|
|
179
|
+
- **No accidental sends** - Safely test in development without real email delivery
|
|
180
|
+
|
|
181
|
+
## References
|
|
182
|
+
|
|
183
|
+
- [Mailpit Documentation](https://mailpit.axllent.org/docs/)
|
|
184
|
+
- [Mailpit API Reference](https://mailpit.axllent.org/docs/api-v1/)
|
|
185
|
+
- [Mailpit GitHub](https://github.com/axllent/mailpit)
|
|
186
|
+
|
|
187
|
+
**Related Overlays:**
|
|
188
|
+
|
|
189
|
+
- `nodejs` - Node.js with nodemailer
|
|
190
|
+
- `python` - Python email development
|
|
191
|
+
- `dotnet` - .NET with MailKit
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.base.schema.json",
|
|
3
|
+
"runServices": ["mailpit"],
|
|
4
|
+
"forwardPorts": [8025, 1025],
|
|
5
|
+
"portsAttributes": {
|
|
6
|
+
"8025": {
|
|
7
|
+
"label": "Mailpit Web UI",
|
|
8
|
+
"onAutoForward": "openBrowser"
|
|
9
|
+
},
|
|
10
|
+
"1025": {
|
|
11
|
+
"label": "Mailpit SMTP",
|
|
12
|
+
"onAutoForward": "ignore"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"remoteEnv": {
|
|
16
|
+
"SMTP_HOST": "mailpit",
|
|
17
|
+
"SMTP_PORT": "1025",
|
|
18
|
+
"MAILPIT_URL": "http://mailpit:8025"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
mailpit:
|
|
5
|
+
image: axllent/mailpit:${MAILPIT_VERSION:-latest}
|
|
6
|
+
restart: unless-stopped
|
|
7
|
+
environment:
|
|
8
|
+
MP_SMTP_AUTH_ACCEPT_ANY: '1'
|
|
9
|
+
MP_SMTP_AUTH_ALLOW_INSECURE: '1'
|
|
10
|
+
ports:
|
|
11
|
+
- '${MAILPIT_UI_PORT:-8025}:8025'
|
|
12
|
+
- '${MAILPIT_SMTP_PORT:-1025}:1025'
|
|
13
|
+
networks:
|
|
14
|
+
- devnet
|
|
15
|
+
|
|
16
|
+
networks:
|
|
17
|
+
devnet:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
id: mailpit
|
|
2
|
+
name: Mailpit
|
|
3
|
+
description: Email testing tool with web UI and SMTP server
|
|
4
|
+
category: dev
|
|
5
|
+
supports:
|
|
6
|
+
- compose
|
|
7
|
+
requires: []
|
|
8
|
+
suggests: []
|
|
9
|
+
conflicts: []
|
|
10
|
+
tags:
|
|
11
|
+
- dev
|
|
12
|
+
- email
|
|
13
|
+
- smtp
|
|
14
|
+
- testing
|
|
15
|
+
ports:
|
|
16
|
+
- port: 8025
|
|
17
|
+
service: mailpit
|
|
18
|
+
protocol: http
|
|
19
|
+
description: Mailpit web UI
|
|
20
|
+
path: /
|
|
21
|
+
onAutoForward: openBrowser
|
|
22
|
+
- port: 1025
|
|
23
|
+
service: mailpit
|
|
24
|
+
protocol: tcp
|
|
25
|
+
description: Mailpit SMTP server
|
|
26
|
+
onAutoForward: ignore
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Verification script for Mailpit overlay
|
|
3
|
+
# Confirms Mailpit is running and accessible
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
echo "🔍 Verifying Mailpit overlay..."
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
# Check if curl is available
|
|
11
|
+
echo "1️⃣ Checking curl availability..."
|
|
12
|
+
if ! command -v curl &> /dev/null; then
|
|
13
|
+
echo " ❌ curl not found"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
echo " ✅ curl found"
|
|
17
|
+
|
|
18
|
+
# Check Mailpit web UI
|
|
19
|
+
echo ""
|
|
20
|
+
echo "2️⃣ Checking Mailpit service..."
|
|
21
|
+
MAILPIT_HOST="${MAILPIT_HOST:-mailpit}"
|
|
22
|
+
MAILPIT_UI_PORT="${MAILPIT_UI_PORT:-8025}"
|
|
23
|
+
MAILPIT_READY=false
|
|
24
|
+
|
|
25
|
+
for i in {1..20}; do
|
|
26
|
+
if curl -sf "http://${MAILPIT_HOST}:${MAILPIT_UI_PORT}/api/v1/info" &> /dev/null; then
|
|
27
|
+
echo " ✅ Mailpit service is ready"
|
|
28
|
+
MAILPIT_READY=true
|
|
29
|
+
break
|
|
30
|
+
fi
|
|
31
|
+
sleep 2
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
if [ "$MAILPIT_READY" = false ]; then
|
|
35
|
+
echo " ❌ Mailpit service not ready after 40 seconds"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Check Mailpit API
|
|
40
|
+
echo ""
|
|
41
|
+
echo "3️⃣ Checking Mailpit API..."
|
|
42
|
+
if curl -sf "http://${MAILPIT_HOST}:${MAILPIT_UI_PORT}/api/v1/messages" &> /dev/null; then
|
|
43
|
+
echo " ✅ Mailpit API is accessible"
|
|
44
|
+
else
|
|
45
|
+
echo " ❌ Mailpit API not accessible"
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
echo "✅ Mailpit overlay verification complete"
|
|
51
|
+
echo " Web UI: http://localhost:${MAILPIT_UI_PORT}"
|
|
52
|
+
echo " SMTP server: mailpit:1025 (from inside container)"
|