diff --git a/index.html b/index.html index b2c0742..5336cb3 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,7 @@ Hagicode Docker Compose Builder - Visual Docker Compose Generator - + diff --git a/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/proposal.md b/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/proposal.md new file mode 100644 index 0000000..04f30ba --- /dev/null +++ b/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/proposal.md @@ -0,0 +1,296 @@ +# Change: Update Image Tag and Add SQLite Database Support + +## Why + +The Hagicode project has updated its versioning strategy and deployment defaults. The `latest` image tag is no longer used for the latest version, and new deployments now support SQLite as a lightweight database option for quick-start scenarios. The current generator defaults need to be updated to align with these changes. + +**Research Notes from pcode repository**: +- SQLite is the default database provider in Hagicode with automatic path generation +- Default SQLite connection string: `Data Source=./data/hagicode.db` (relative to app root) +- Container data directory: `/app/data` (for volume mounts) +- Image tags use semantic versioning (e.g., `0.1.0-beta.1`) instead of `latest` +- Reference: `/home/newbe36524/repos/newbe36524/pcode` + +## What Changes + +- **BREAKING**: Update default image tag from `latest` to `0` (as placeholder for latest stable version) +- **NEW**: Add `sqlite` as a new database type option +- **MODIFIED**: Quick-start profile now defaults to SQLite instead of internal PostgreSQL +- **MODIFIED**: Full-custom profile allows selection between SQLite, internal PostgreSQL, and external PostgreSQL +- **NEW**: Add SQLite volume mount configuration for data persistence (`/app/data` mount point) +- **MODIFIED**: Docker Compose generator to conditionally include/exclude PostgreSQL service based on database type + +**Implementation Details** (based on pcode repository analysis and design decisions): +- **CRITICAL**: `Database__Provider` environment variable MUST be set based on database type + - SQLite: `Database__Provider=sqlite` + - PostgreSQL: `Database__Provider=postgresql` (or `PostgreSQL` - both work) +- SQLite connection string format: `Data Source=/app/data/hagicode.db` (fixed container path) +- PostgreSQL connection string format: `Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres` +- **`hagicode_data:/app/data` named volume is ALWAYS included** (regardless of database type) + - Contains SQLite database file when using SQLite + - Contains other application data (Orleans grain storage, logs, etc.) for all database types +- Container path `/app/data` is **NOT user-configurable** (fixed by design) +- Users **DO NOT** configure SQLite data directory path - it's always a named volume +- SQLite requires no PostgreSQL service, no `depends_on` database dependency +- Image tag `0` represents the latest stable version (aligning with pcode deployment practices) + +## UI Design Changes + +### Quick-Start Mode +``` +Docker Compose Generator - Quick Start +======================================== + +Required Fields: +- [Work Directory Path: _________________ ] +- [HTTP Port: 45000 ] +- [API Token: __________________________ ] +- [Image Registry: (x) Aliyun ACR ( ) Docker Hub ( ) Azure ACR] +- [API Provider: (x) ZAI ( ) Anthropic ( ) Custom] + +[Hidden: Database defaults to SQLite] +[Hidden: All other fields use sensible defaults] + +[Generate Configuration] [Copy] [Download] +``` + +### Full-Custom Mode - Database Selection +``` +Database Configuration: +====================== +(x) SQLite - Lightweight, file-based database (recommended for quick start) +( ) Internal PostgreSQL - Full PostgreSQL service running in container +( ) External PostgreSQL - Connect to existing PostgreSQL instance + +[If SQLite selected:] +- Data stored in named volume (hagicode_data) mounted to /app/data (not user-configurable) + +[If Internal PostgreSQL selected:] +- Database Name: [hagicode_________ ] +- Username: [postgres_________ ] +- Password: [****************] +- Volume Type: ( ) Named ( ) Bind Mount +- Volume Path/Name: [postgres-data__________________________ ] + +[If External PostgreSQL selected:] +- Host: [____________________ ] +- Port: [5432_______________ ] +- Database: [hagicode_________ ] +- Username: [postgres_________ ] +- Password: [****************] +``` + +### User Interaction Flow +```mermaid +sequenceDiagram + participant User + participant UI + participant Generator + + User->>UI: Selects Quick-Start mode + UI->>UI: Set databaseType='sqlite' (hidden) + User->>UI: Fills required fields + User->>UI: Clicks Generate + UI->>Generator: generateYAML(config) + Generator->>Generator: buildAppService() with SQLite connection string + Generator->>Generator: buildSqliteVolumes() + Generator-->>UI: Returns YAML without PostgreSQL service + UI-->>User: Displays docker-compose.yml + + User->>UI: Switches to Full-Custom mode + UI->>UI: Shows database selection + User->>UI: Selects Internal PostgreSQL + User->>UI: Clicks Generate + UI->>Generator: generateYAML(config) + Generator->>Generator: buildAppService() with PostgreSQL connection string + Generator->>Generator: buildPostgresService() + Generator-->>UI: Returns YAML with PostgreSQL service + UI-->>User: Displays docker-compose.yml +``` + +## Code Flow Changes + +### Database Configuration Flow +```mermaid +flowchart TD + A[generateYAML] --> B{databaseType?} + B -->|sqlite| C[buildAppService with SQLite] + B -->|internal| D[buildAppService with PostgreSQL] + B -->|external| E[buildAppService with PostgreSQL] + + C --> F[buildSqliteVolumes] + D --> G[buildPostgresService] + E --> H[No PostgreSQL service] + + F --> I[buildVolumesSection - check SQLite named volumes] + G --> I + H --> I + + I --> J[Return YAML] +``` + +### Component Relationship Changes +```mermaid +graph TD + A[DockerComposeConfig] -->|NEW| B[DatabaseType: 'sqlite' option] + A --> C[ConfigProfile: 'quick-start'] + C -->|MODIFIED| D[Defaults to SQLite] + + E[buildAppService] -->|NEW| F{Check SQLite} + F -->|Yes| G[Set SQLite connection string] + F -->|No| H[Set PostgreSQL connection string] + + I[buildServicesSection] -->|NEW| J{Include PostgreSQL?} + J -->|databaseType = 'internal'| K[buildPostgresService] + J -->|databaseType = 'sqlite'| L[Skip PostgreSQL service] + + M[NEW: buildSqliteVolumes] --> N[Configure named volume hagicode_data → /app/data] +``` + +## Impact + +- Affected specs: `docker-compose-generator` +- Affected code: + - `src/lib/docker-compose/types.ts:6` - Add `'sqlite'` to `DatabaseType` + - `src/lib/docker-compose/defaultConfig.ts:7` - Change `imageTag` from `'latest'` to `'0'` + - `src/lib/docker-compose/defaultConfig.ts:12` - Change quick-start `databaseType` from `'internal'` to `'sqlite'` + - `src/lib/docker-compose/generator.ts:57-152` - Modify `buildAppService()` to handle SQLite connection strings + - `src/lib/docker-compose/generator.ts:207-223` - Modify `buildServicesSection()` to conditionally include PostgreSQL + - `src/lib/docker-compose/generator.ts:230-241` - Modify `buildVolumesSection()` to handle SQLite named volume + - New function: `buildSqliteVolumes()` in `src/lib/docker-compose/generator.ts` (generates named volume mount) + +**Design Decision: Application data volume** +- Container path `/app/data` is **NOT user-configurable** +- Named volume `hagicode_data` is **always created** for ALL database types + - SQLite: stores database file + other app data + - PostgreSQL: stores Orleans grain storage, logs, and other non-database data +- Users **DO NOT** specify data directory path (simplified UX) +- PostgreSQL database data uses separate volume (`postgres-data`) + +## Docker Compose Sample Comparison + +This section shows the expected YAML output changes for verification. + +### Before (current - PostgreSQL internal) + +```yaml +services: + hagicode: + image: registry.cn-hangzhou.aliyuncs.com/hagicode/hagicode:latest + container_name: hagicode-app + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ASPNETCORE_URLS=http://+:45000 + - TZ=Asia/Shanghai + - Database__Provider=PostgreSQL + - ConnectionStrings__Default=Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres + # ... other env vars + ports: + - "45000:45000" + volumes: + - /home/user/repos:/app/workdir + - hagicode_data:/app/data + depends_on: + postgres: + condition: service_healthy + networks: + - pcode-network + restart: unless-stopped + + postgres: + image: bitnami/postgresql:latest + container_name: hagicode-postgres + environment: + - POSTGRES_DATABASE=hagicode + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_HOST_AUTH_METHOD=trust + - TZ=Asia/Shanghai + volumes: + - postgres-data:/bitnami/postgresql + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 10s + timeout: 3s + retries: 3 + networks: + - pcode-network + restart: unless-stopped + +volumes: + hagicode_data: + postgres-data: + +networks: + pcode-network: + driver: bridge +``` + +### After (new - SQLite default) + +```yaml +services: + hagicode: + image: registry.cn-hangzhou.aliyuncs.com/hagicode/hagicode:0 + container_name: hagicode-app + environment: + - ASPNETCORE_ENVIRONMENT=Production + - ASPNETCORE_URLS=http://+:45000 + - TZ=Asia/Shanghai + - Database__Provider=sqlite + - ConnectionStrings__Default=Data Source=/app/data/hagicode.db + # ... other env vars + ports: + - "45000:45000" + volumes: + - /home/user/repos:/app/workdir + - hagicode_data:/app/data + # NO depends_on for SQLite + networks: + - pcode-network + restart: unless-stopped + +# NO postgres service when using SQLite + +volumes: + hagicode_data: + +networks: + pcode-network: + driver: bridge +``` + +### Key Changes Summary + +| Section | Before (PostgreSQL) | After (SQLite) | +|---------|-------------------|---------------| +| **Image tag** | `:latest` | `:0` | +| **Database__Provider** | `Database__Provider=PostgreSQL` | `Database__Provider=sqlite` | +| **Connection string** | `Host=postgres;Port=5432;...` | `Data Source=/app/data/hagicode.db` | +| **hagicode_data volume** | Present (for Orleans/logs) | Present (for SQLite + Orleans/logs) | +| **postgres-data volume** | Present (for PostgreSQL DB) | Removed | +| **depends_on** | `postgres: condition: service_healthy` | (removed) | +| **postgres service** | Included | Excluded | +| **volumes section** | `hagicode_data:` + `postgres-data:` | `hagicode_data:` only | + +### Verification Checklist + +#### All Configurations (Common) +- [ ] Image tag changed from `latest` to `0` +- [ ] `hagicode_data:/app/data` volume is **always present** (for app data: Orleans, logs, etc.) +- [ ] `volumes:` section **always defines** `hagicode_data:` +- [ ] `networks:` section remains unchanged + +#### SQLite-Specific (Quick-Start / SQLite selected) +- [ ] `Database__Provider=sqlite` environment variable set +- [ ] Connection string uses SQLite format: `Data Source=/app/data/hagicode.db` +- [ ] No `depends_on` for database +- [ ] No `postgres` service in output +- [ ] No `postgres-data` volume + +#### PostgreSQL-Specific (Internal PostgreSQL) +- [ ] `Database__Provider=postgresql` (or `PostgreSQL`) environment variable set +- [ ] Connection string uses PostgreSQL format: `Host=postgres;...` +- [ ] `depends_on: postgres:` with healthcheck condition present +- [ ] `postgres` service included with correct configuration +- [ ] `volumes:` section includes `postgres-data:` (in addition to `hagicode_data:`) diff --git a/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/specs/docker-compose-generator/spec.md b/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/specs/docker-compose-generator/spec.md new file mode 100644 index 0000000..9cacfa5 --- /dev/null +++ b/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/specs/docker-compose-generator/spec.md @@ -0,0 +1,109 @@ +# docker-compose-generator Specification Delta + +## ADDED Requirements + +### Requirement: SQLite Database Support + +系统 SHALL 支持 SQLite 作为数据库选项,为用户提供轻量级的快速启动部署方式。 + +#### Scenario: 快速启动模式使用 SQLite + +- **WHEN** 用户选择"快速体验"模式 +- **THEN** 系统默认使用 SQLite 数据库 +- **AND** 生成的 Docker Compose 配置 SHALL NOT 包含 PostgreSQL 服务 +- **AND** 应用服务环境变量 SHALL 设置: + - `Database__Provider=sqlite` + - SQLite 连接字符串: `ConnectionStrings__Default=Data Source=/app/data/hagicode.db` +- **AND** 系统配置 `/app/data` 目录的卷挂载以确保持久化 + +#### Scenario: 完整自定义模式选择 SQLite + +- **WHEN** 用户在"完整自定义"模式中选择 SQLite 数据库 +- **THEN** 系统生成不包含 PostgreSQL 服务的配置 +- **AND** 系统使用命名卷 `hagicode_data` 挂载到 `/app/data` +- **AND** 容器内路径 `/app/data` 是固定的,用户不可配置 +- **AND** 系统 SHALL NOT 显示 SQLite 数据目录配置选项(简化用户体验) + +#### Scenario: SQLite 数据持久化 + +- **WHEN** 用户使用 SQLite 数据库配置 +- **THEN** 系统创建命名卷 `hagicode_data` 并挂载到容器的 `/app/data` 路径 +- **AND** SQLite 数据库文件 `hagicode.db` 存储在挂载的目录中 +- **AND** 容器重启后数据得以保留 +- **AND** 挂载路径始终为命名卷模式: `hagicode_data:/app/data` +- **AND** 容器内路径 `/app/data` 是固定的,不由用户配置 +- **AND** `hagicode_data:/app/data` 卷在所有配置中始终存在(SQLite 存储 DB 文件,PostgreSQL 配置存储其他应用数据) + +### Requirement: 镜像标签策略更新 + +系统 SHALL 使用 `0` 作为默认镜像标签,替代过时的 `latest` 标签。 + +#### Scenario: 默认镜像标签 + +- **WHEN** 用户生成新的 Docker Compose 配置 +- **THEN** 系统使用 `0` 作为镜像标签 +- **AND** 镜像引用格式为 `[registry]/[image]:0` +- **AND** 标签 `0` 代表最新稳定版本(与 pcode 部署实践对齐) + +#### Scenario: 所有配置模式使用新标签 + +- **WHEN** 用户使用快速启动或完整自定义模式 +- **THEN** 生成的配置均使用 `0` 标签 +- **AND** 所有镜像注册表 (Docker Hub, ACR, Aliyun ACR) 均使用相同标签 + +## MODIFIED Requirements + +### Requirement: 配置模式选择 (Configuration Profile Selection) + +系统 SHALL 提供配置模式选择功能,允许用户在"快速体验"和"完整自定义"两种模式之间切换,以适应不同用户的需求。 + +#### Scenario: 快速体验模式 + +- **WHEN** 用户选择"快速体验"模式 +- **THEN** 系统显示以下配置项: + - 工作目录路径 (必填) + - HTTP 端口 (必填) + - API Token (必填) + - 镜像注册表 (必填) + - API 提供商 (新增,必填) +- **AND** 当用户选择"自定义 API 端点"作为 API 提供商时: + - 系统额外显示 API 端点 URL 输入框 (必填) +- **AND** 系统隐藏以下高级配置项: + - 容器名称、镜像标签、主机操作系统 + - 数据库配置(**MODIFIED: 默认使用 SQLite 而非内部 PostgreSQL**) + - 许可证密钥类型 + - 卷类型、卷名称/路径 + - Linux 用户权限 (PUID/PGID) +- **AND** 所有标签和描述文本 SHALL 支持多语言 + +### Requirement: Docker Compose 配置生成 + +系统 SHALL 提供 Docker Compose 配置的可视化生成功能,允许用户通过表单界面配置各种参数,并自动生成相应的 YAML 配置文件。 + +#### Scenario: SQLite 配置生成 + +- **WHEN** 用户选择 SQLite 作为数据库类型并生成配置 +- **THEN** 系统生成包含以下内容的 Docker Compose 配置: + - hagicode 应用服务环境变量设置: + - `Database__Provider=sqlite` + - `ConnectionStrings__Default=Data Source=/app/data/hagicode.db` + - 命名卷 `hagicode_data` 挂载到 `/app/data` 的配置 + - volumes 部分定义 `hagicode_data` 命名卷 + - 网络配置 +- **AND** 配置 SHALL NOT 包含 PostgreSQL 服务 +- **AND** 应用服务的 `depends_on` 配置 SHALL 不包含数据库依赖 +- **AND** 容器内挂载路径 `/app/data` 是固定的,不由用户配置 + +#### Scenario: PostgreSQL 配置生成(保留原有功能) + +- **WHEN** 用户选择内部或外部 PostgreSQL 并生成配置 +- **THEN** 系统生成包含以下内容的 Docker Compose 配置: + - hagicode 应用服务环境变量设置: + - `Database__Provider=postgresql` (或 `PostgreSQL`) + - `ConnectionStrings__Default=Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres` + - **命名卷 `hagicode_data` 挂载到 `/app/data`**(用于存储 Orleans grain storage、日志等非数据库数据) + - PostgreSQL 服务(仅限内部数据库) + - PostgreSQL 数据卷 `postgres-data`(如使用命名卷) + - 网络、健康检查和依赖配置 +- **AND** `hagicode_data:/app/data` 卷 SHALL 始终存在(无论使用何种数据库类型) +- **AND** volumes 部分 SHALL 始终定义 `hagicode_data:` 命名卷 diff --git a/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/tasks.md b/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/tasks.md new file mode 100644 index 0000000..0793e51 --- /dev/null +++ b/openspec/changes/archive/2026-02-08-image-tag-and-sqlite-support/proposal-files/tasks.md @@ -0,0 +1,104 @@ +# Implementation Tasks + +## 1. Type System Updates + +- [ ] 1.1 Update `DatabaseType` in `src/lib/docker-compose/types.ts` to include `'sqlite'` +- [ ] 1.2 Verify type compatibility across all files using `DatabaseType` + +## 2. Default Configuration Updates + +- [ ] 2.1 Update `imageTag` from `'latest'` to `'0'` in `src/lib/docker-compose/defaultConfig.ts` +- [ ] 2.2 Update quick-start profile `databaseType` from `'internal'` to `'sqlite'` in `src/lib/docker-compose/defaultConfig.ts` + +## 3. Generator Logic - Application Service + +- [ ] 3.1 Add SQLite connection string handling in `buildAppService()` function +- [ ] 3.2 Implement conditional logic: when `databaseType === 'sqlite'`, set SQLite-specific environment variable +- [ ] 3.3 **CRITICAL**: Set `Database__Provider` environment variable based on database type: + - SQLite: `Database__Provider=sqlite` + - PostgreSQL (internal/external): `Database__Provider=postgresql` +- [ ] 3.4 Ensure SQLite connection string format: `Data Source=/app/data/hagicode.db` (container path) +- [ ] 3.5 Ensure PostgreSQL connection string format: `Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres` +- [ ] 3.6 Remove PostgreSQL `depends_on` dependency when SQLite is selected +- [ ] 3.7 Reference pcode implementation: appsettings.yml shows `Database: Provider: "sqlite"` default +- [ ] 3.8 **IMPORTANT**: `hagicode_data:/app/data` volume mount is ALWAYS added (for ALL database types) - contains Orleans grain storage, logs, etc. + +## 4. Generator Logic - SQLite Volumes + +- [ ] 4.1 **Note**: No separate `buildSqliteVolumes()` function needed +- [ ] 4.2 The `hagicode_data:/app/data` volume is part of the standard app service configuration (not database-specific) +- [ ] 4.3 Ensure app service ALWAYS includes `- hagicode_data:/app/data` volume mount +- [ ] 4.4 Reference pcode docker-compose.yml: `hagicode_data:/app/data` volume pattern +- [ ] 4.5 **IMPORTANT**: The `/app/data` volume is NOT user-configurable and is ALWAYS present + +## 5. Generator Logic - Services Section + +- [ ] 5.1 Modify `buildServicesSection()` to conditionally include PostgreSQL service +- [ ] 5.2 Skip `buildPostgresService()` call when `databaseType === 'sqlite'` +- [ ] 5.3 **Note**: The `hagicode_data:/app/data` volume is added in `buildAppService()`, not here + +## 6. Generator Logic - Volumes Section + +- [ ] 6.1 Modify `buildVolumesSection()` to **ALWAYS** include `hagicode_data:` volume definition +- [ ] 6.2 Add `postgres-data:` volume definition ONLY when `databaseType === 'internal'` +- [ ] 6.3 Ensure: SQLite config has only `hagicode_data:`, PostgreSQL config has both `hagicode_data:` + `postgres-data:` + +## 7. Testing + +- [ ] 7.1 Test quick-start mode generates correct YAML with SQLite and image tag `0` +- [ ] 7.2 Test full-custom mode with SQLite option generates correct YAML +- [ ] 7.3 Test full-custom mode with internal PostgreSQL still works correctly +- [ ] 7.4 Test full-custom mode with external PostgreSQL still works correctly +- [ ] 7.5 **CRITICAL**: Verify `hagicode_data:/app/data` is present in ALL configurations (SQLite, internal PostgreSQL, external PostgreSQL) +- [ ] 7.6 Verify SQLite uses `hagicode_data:/app/data` for database file (no user path configuration) +- [ ] 7.7 Verify no PostgreSQL service is generated when SQLite is selected +- [ ] 7.8 Validate image tag `0` appears in all generated configurations +- [ ] 7.9 Verify SQLite connection string format: `Data Source=/app/data/hagicode.db` +- [ ] 7.10 Verify PostgreSQL config has BOTH `hagicode_data:` (app data) and `postgres-data:` (database) +- [ ] 7.11 Verify SQLite config has ONLY `hagicode_data:` (contains both database and other app data) + +## 8. YAML Output Verification + +Use this checklist to verify the generated Docker Compose YAML matches the expected format in proposal.md: + +### SQLite Configuration (Quick-Start / SQLite selected) +- [ ] Image tag is `:0` not `:latest` +- [ ] `Database__Provider=sqlite` environment variable is set +- [ ] Connection string: `ConnectionStrings__Default=Data Source=/app/data/hagicode.db` +- [ ] Volume mount: `- hagicode_data:/app/data` present in hagicode service +- [ ] NO `depends_on` section for database +- [ ] NO `postgres` service in output +- [ ] `volumes:` section includes `hagicode_data:` ONLY (no `postgres-data:`) + +### PostgreSQL Configuration (Internal) +- [ ] Image tag is `:0` not `:latest` +- [ ] `Database__Provider=postgresql` (or `PostgreSQL`) environment variable is set +- [ ] Connection string: `ConnectionStrings__Default=Host=postgres;Port=5432;...` +- [ ] Volume mount: `- hagicode_data:/app/data` present in hagicode service (for Orleans, logs, etc.) +- [ ] `depends_on: postgres:` with healthcheck condition present +- [ ] `postgres` service included with correct configuration +- [ ] `volumes:` section includes BOTH `hagicode_data:` AND `postgres-data:` (or custom name) + +### External PostgreSQL Configuration +- [ ] Image tag is `:0` not `:latest` +- [ ] Connection string uses external host:port +- [ ] Volume mount: `- hagicode_data:/app/data` present in hagicode service (for Orleans, logs, etc.) +- [ ] NO `depends_on` section (no postgres service to wait for) +- [ ] NO `postgres` service in output +- [ ] `volumes:` section includes `hagicode_data:` ONLY + +### All Configurations (Common) +- [ ] `hagicode_data:/app/data` is ALWAYS present (contains app data regardless of database type) +- [ ] Image tag is `:0` not `:latest` +- [ ] Workdir mount is present +- [ ] Network and restart policies unchanged + +### Comparison with pcode Reference +- [ ] SQLite config matches pcode pattern: `hagicode_data:/app/data` +- [ ] `Database__Provider` environment variable follows pcode naming convention (`Database: Provider` in appsettings.yml) +- [ ] Environment variables use double underscore format: `Database__Provider` (Docker Compose standard) +- [ ] Network and restart policies unchanged + +## 9. Validation + +- [ ] 9.1 Run `openspec validate image-tag-and-sqlite-support --strict` and fix any issues diff --git a/src/components/docker-compose/ConfigForm.tsx b/src/components/docker-compose/ConfigForm.tsx index 7f1be46..6a2dd3a 100644 --- a/src/components/docker-compose/ConfigForm.tsx +++ b/src/components/docker-compose/ConfigForm.tsx @@ -113,14 +113,12 @@ export function ConfigForm() { {config.profile === 'full-custom' && (
- + updateConfig('imageTag', e.target.value)} + placeholder="0" + />
)} @@ -211,19 +209,32 @@ export function ConfigForm() { - {config.databaseType === 'internal' ? ( + {config.databaseType === 'sqlite' ? ( +
+

+ 🚀 {t('configForm.sqliteDatabase')} +

+

+ {t('configForm.sqliteDescription')} +

+

+ {t('configForm.sqliteDataLocation')}: /app/data/hagicode.db +

+
+ ) : config.databaseType === 'internal' ? (
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 350e3b4..4bcede5 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -27,7 +27,7 @@ "profile": "Profile", "profileLabel": "Configuration Mode", "quickStart": "Quick Start (Recommended for new users)", - "quickStartDescription": "Get started quickly with minimal configuration", + "quickStartDescription": "Get started quickly with SQLite database, no PostgreSQL configuration needed", "fullCustom": "Full Custom", "fullCustomDescription": "Complete control over all configuration options", "profileHelp": "Choose your configuration experience. You can switch modes at any time.", @@ -58,6 +58,9 @@ "databaseConfig": "Database Configuration", "databaseType": "Database Type", "selectDatabaseType": "Select database type", + "sqliteDatabase": "SQLite Database", + "sqliteDescription": "Lightweight embedded database, no additional container needed, ideal for quick start and development environments.", + "sqliteDataLocation": "Data storage location", "internalPostgresql": "Internal PostgreSQL", "externalDatabase": "External Database", "databaseName": "Database Name", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 6540ceb..ccb22a9 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -27,7 +27,7 @@ "profile": "配置模式", "profileLabel": "配置模式", "quickStart": "本地快速体验 (推荐新用户)", - "quickStartDescription": "使用最小配置快速开始", + "quickStartDescription": "使用 SQLite 数据库快速开始,无需额外配置 PostgreSQL", "fullCustom": "完整自定义", "fullCustomDescription": "完全控制所有配置选项", "profileHelp": "选择您的配置体验。您可以随时切换模式。", @@ -58,6 +58,9 @@ "databaseConfig": "数据库配置", "databaseType": "数据库类型", "selectDatabaseType": "选择数据库类型", + "sqliteDatabase": "SQLite 数据库", + "sqliteDescription": "轻量级嵌入式数据库,无需额外容器,适合快速体验和开发环境。", + "sqliteDataLocation": "数据存储位置", "internalPostgresql": "内部 PostgreSQL", "externalDatabase": "外部数据库", "databaseName": "数据库名称", diff --git a/src/lib/docker-compose/__tests__/__verify__/__snapshots__/api-provider-snapshots.test.ts.snap b/src/lib/docker-compose/__tests__/__verify__/__snapshots__/api-provider-snapshots.test.ts.snap index 1789d95..e3cdcd8 100644 --- a/src/lib/docker-compose/__tests__/__verify__/__snapshots__/api-provider-snapshots.test.ts.snap +++ b/src/lib/docker-compose/__tests__/__verify__/__snapshots__/api-provider-snapshots.test.ts.snap @@ -15,13 +15,14 @@ exports[`API Provider Profiles - Complete File Verification with YAML Parsing > services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -35,34 +36,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: @@ -84,13 +64,14 @@ exports[`API Provider Profiles - Complete File Verification with YAML Parsing > services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -105,34 +86,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: @@ -154,13 +114,14 @@ exports[`API Provider Profiles - Complete File Verification with YAML Parsing > services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -175,34 +136,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: diff --git a/src/lib/docker-compose/__tests__/__verify__/__snapshots__/full-custom-snapshots.test.ts.snap b/src/lib/docker-compose/__tests__/__verify__/__snapshots__/full-custom-snapshots.test.ts.snap index 40aea29..0056119 100644 --- a/src/lib/docker-compose/__tests__/__verify__/__snapshots__/full-custom-snapshots.test.ts.snap +++ b/src/lib/docker-compose/__tests__/__verify__/__snapshots__/full-custom-snapshots.test.ts.snap @@ -15,12 +15,13 @@ exports[`Full Custom Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai + Database__Provider: postgresql ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" License__Activation__LicenseKey: "public-license-key" PUID: 1000 @@ -37,6 +38,7 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir + - hagicode_data:/app/data depends_on: postgres: condition: service_healthy @@ -64,6 +66,7 @@ services: restart: unless-stopped volumes: + hagicode_data: postgres-data: networks: @@ -86,12 +89,13 @@ exports[`Full Custom Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai + Database__Provider: postgresql ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" License__Activation__LicenseKey: "public-license-key" # ================================================== @@ -106,6 +110,7 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir + - hagicode_data:/app/data depends_on: postgres: condition: service_healthy @@ -133,6 +138,7 @@ services: restart: unless-stopped volumes: + hagicode_data: postgres-data: networks: @@ -155,12 +161,13 @@ exports[`Full Custom Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai + Database__Provider: postgresql ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" License__Activation__LicenseKey: "public-license-key" # ================================================== @@ -175,6 +182,7 @@ services: - "8080:45000" volumes: - C:\\\\repos:/app/workdir + - hagicode_data:/app/data depends_on: postgres: condition: service_healthy @@ -202,6 +210,7 @@ services: restart: unless-stopped volumes: + hagicode_data: postgres-data: networks: @@ -224,12 +233,13 @@ exports[`Full Custom Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai + Database__Provider: postgresql ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" License__Activation__LicenseKey: "public-license-key" PUID: 1000 @@ -246,6 +256,7 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir + - hagicode_data:/app/data depends_on: postgres: condition: service_healthy @@ -272,6 +283,9 @@ services: - pcode-network restart: unless-stopped +volumes: + hagicode_data: + networks: pcode-network: driver: bridge" @@ -292,12 +306,13 @@ exports[`Full Custom Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai + Database__Provider: postgresql ConnectionStrings__Default: "Host=external-postgres.example.com;Port=5432;Database=hagicode;Username=postgres;Password=postgres" License__Activation__LicenseKey: "public-license-key" # ================================================== @@ -312,10 +327,14 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped +volumes: + hagicode_data: + networks: pcode-network: driver: bridge" diff --git a/src/lib/docker-compose/__tests__/__verify__/__snapshots__/quick-start-snapshots.test.ts.snap b/src/lib/docker-compose/__tests__/__verify__/__snapshots__/quick-start-snapshots.test.ts.snap index 2a74fd2..5d3e1a6 100644 --- a/src/lib/docker-compose/__tests__/__verify__/__snapshots__/quick-start-snapshots.test.ts.snap +++ b/src/lib/docker-compose/__tests__/__verify__/__snapshots__/quick-start-snapshots.test.ts.snap @@ -15,13 +15,14 @@ exports[`Quick Start Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -35,34 +36,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: @@ -84,13 +64,14 @@ exports[`Quick Start Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -105,34 +86,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: @@ -154,13 +114,14 @@ exports[`Quick Start Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -175,34 +136,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: @@ -224,13 +164,14 @@ exports[`Quick Start Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -244,34 +185,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: @@ -293,13 +213,14 @@ exports[`Quick Start Profiles - Complete File Verification with YAML Parsing > s services: hagicode: - image: newbe36524/hagicode:latest + image: newbe36524/hagicode:0 container_name: hagicode environment: ASPNETCORE_ENVIRONMENT: Production ASPNETCORE_URLS: http://+:45000 TZ: Asia/Shanghai - ConnectionStrings__Default: "Host=postgres;Port=5432;Database=hagicode;Username=postgres;Password=postgres" + Database__Provider: sqlite + ConnectionStrings__Default: "Data Source=/app/data/hagicode.db" License__Activation__LicenseKey: "public-license-key" # ================================================== # Claude Code Configuration @@ -313,34 +234,13 @@ services: - "8080:45000" volumes: - /home/user/repos:/app/workdir - depends_on: - postgres: - condition: service_healthy - networks: - - pcode-network - restart: unless-stopped - - postgres: - image: bitnami/postgresql:latest - environment: - POSTGRES_DATABASE: hagicode - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_HOST_AUTH_METHOD: trust - TZ: Asia/Shanghai - volumes: - - postgres-data:/bitnami/postgresql - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 10s - timeout: 3s - retries: 3 + - hagicode_data:/app/data networks: - pcode-network restart: unless-stopped volumes: - postgres-data: + hagicode_data: networks: pcode-network: diff --git a/src/lib/docker-compose/__tests__/__verify__/api-provider-snapshots.test.ts b/src/lib/docker-compose/__tests__/__verify__/api-provider-snapshots.test.ts index bad3924..2d0d6b8 100644 --- a/src/lib/docker-compose/__tests__/__verify__/api-provider-snapshots.test.ts +++ b/src/lib/docker-compose/__tests__/__verify__/api-provider-snapshots.test.ts @@ -174,7 +174,9 @@ describe('API Provider Profiles - Complete File Verification with YAML Parsing', }); it('should validate postgres service healthcheck structure', async () => { - const config = createZaiProviderConfig(); + const config = createZaiProviderConfig({ + databaseType: 'internal' // 明确设置内部 PostgreSQL 以测试 postgres healthcheck + }); const yaml = generateYAML(config, 'zh-CN', FIXED_DATE); const parsed = parseDockerComposeYAML(yaml); diff --git a/src/lib/docker-compose/__tests__/__verify__/full-custom-snapshots.test.ts b/src/lib/docker-compose/__tests__/__verify__/full-custom-snapshots.test.ts index 1b4d6cd..14e475d 100644 --- a/src/lib/docker-compose/__tests__/__verify__/full-custom-snapshots.test.ts +++ b/src/lib/docker-compose/__tests__/__verify__/full-custom-snapshots.test.ts @@ -123,8 +123,10 @@ describe('Full Custom Profiles - Complete File Verification with YAML Parsing', expect(hasService(yaml, 'hagicode')).toBe(true); expect(hasService(yaml, 'postgres')).toBe(false); - // 验证没有顶层卷配置 - expect(validation.parsed.volumes).toBeUndefined(); + // 验证只有 hagicode_data 卷(没有 postgres-data) + expect(validation.parsed.volumes).toBeDefined(); + expect(validation.parsed.volumes?.hagicode_data).toBeDefined(); + expect(validation.parsed.volumes?.postgres_data).toBeUndefined(); // 验证外部数据库连接字符串 const connectionString = getServiceEnvVar(yaml, 'hagicode', 'ConnectionStrings__Default'); @@ -158,8 +160,10 @@ describe('Full Custom Profiles - Complete File Verification with YAML Parsing', const postgresVolumes = getServiceVolumes(yaml, 'postgres'); expect(postgresVolumes).toContain('/data/postgres:/bitnami/postgresql'); - // 验证没有顶层卷配置(绑定挂载不需要) - expect(validation.parsed.volumes).toBeUndefined(); + // 验证只有 hagicode_data 卷(没有 postgres-data,因为使用绑定挂载) + expect(validation.parsed.volumes).toBeDefined(); + expect(validation.parsed.volumes?.hagicode_data).toBeDefined(); + expect(validation.parsed.volumes?.postgres_data).toBeUndefined(); // 验证 PUID/PGID expect(hasEnvVar(yaml, 'hagicode', 'PUID')).toBe(true); diff --git a/src/lib/docker-compose/__tests__/__verify__/quick-start-snapshots.test.ts b/src/lib/docker-compose/__tests__/__verify__/quick-start-snapshots.test.ts index 0601b55..3c3c795 100644 --- a/src/lib/docker-compose/__tests__/__verify__/quick-start-snapshots.test.ts +++ b/src/lib/docker-compose/__tests__/__verify__/quick-start-snapshots.test.ts @@ -29,12 +29,13 @@ describe('Quick Start Profiles - Complete File Verification with YAML Parsing', expect(validation.errors).toEqual([]); expect(validation.valid).toBe(true); - // 验证服务存在 + // 验证服务存在(快速启动现在使用 SQLite,没有 postgres 服务) expect(hasService(yaml, 'hagicode')).toBe(true); - expect(hasService(yaml, 'postgres')).toBe(true); + expect(hasService(yaml, 'postgres')).toBe(false); - // 验证卷存在(内部数据库使用命名卷) - expect(hasVolume(yaml, 'postgres-data')).toBe(true); + // 验证卷存在(SQLite 只使用 hagicode_data) + expect(hasVolume(yaml, 'hagicode_data')).toBe(true); + expect(hasVolume(yaml, 'postgres-data')).toBe(false); // 验证网络存在 expect(hasNetwork(yaml, 'pcode-network')).toBe(true); @@ -44,9 +45,8 @@ describe('Quick Start Profiles - Complete File Verification with YAML Parsing', expect(hasEnvVar(yaml, 'hagicode', 'ANTHROPIC_AUTH_TOKEN')).toBe(true); expect(hasEnvVar(yaml, 'hagicode', 'ConnectionStrings__Default')).toBe(true); - // 验证镜像 - expect(getServiceImage(yaml, 'hagicode')).toBe('newbe36524/hagicode:latest'); - expect(getServiceImage(yaml, 'postgres')).toContain('postgresql'); + // 验证镜像(快速启动现在使用镜像标签 0) + expect(getServiceImage(yaml, 'hagicode')).toBe('newbe36524/hagicode:0'); // 验证端口映射 const ports = getServicePorts(yaml, 'hagicode'); @@ -69,9 +69,9 @@ describe('Quick Start Profiles - Complete File Verification with YAML Parsing', expect(validation.errors).toEqual([]); expect(validation.valid).toBe(true); - // 验证服务 + // 验证服务(快速启动现在使用 SQLite,没有 postgres 服务) expect(hasService(yaml, 'hagicode')).toBe(true); - expect(hasService(yaml, 'postgres')).toBe(true); + expect(hasService(yaml, 'postgres')).toBe(false); // 验证网络 expect(hasNetwork(yaml, 'pcode-network')).toBe(true); @@ -156,19 +156,12 @@ describe('Quick Start Profiles - Complete File Verification with YAML Parsing', const parsed = validateDockerComposeStructure(yaml); expect(parsed.valid).toBe(true); - // 验证 hagicode 服务依赖 postgres + // 快速启动使用 SQLite,没有 postgres 服务,所以没有 depends_on const hagicodeService = parsed.parsed.services.hagicode; - expect(hagicodeService.depends_on).toBeDefined(); - expect(hagicodeService.depends_on.postgres).toBeDefined(); - expect(hagicodeService.depends_on.postgres.condition).toBe('service_healthy'); - - // 验证 postgres 的 healthcheck - const postgresService = parsed.parsed.services.postgres; - expect(postgresService.healthcheck).toBeDefined(); - expect(postgresService.healthcheck.test).toContain('pg_isready'); - expect(postgresService.healthcheck.interval).toBe('10s'); - expect(postgresService.healthcheck.timeout).toBe('3s'); - expect(postgresService.healthcheck.retries).toBe(3); + expect(hagicodeService.depends_on).toBeUndefined(); + + // 验证没有 postgres 服务 + expect(parsed.parsed.services.postgres).toBeUndefined(); }); it('should validate network configuration', async () => { @@ -183,9 +176,11 @@ describe('Quick Start Profiles - Complete File Verification with YAML Parsing', expect(network).toBeDefined(); expect(network.driver).toBe('bridge'); - // 验证服务连接到网络 + // 验证 hagicode 服务连接到网络 expect(parsed.parsed.services.hagicode.networks).toContain('pcode-network'); - expect(parsed.parsed.services.postgres.networks).toContain('pcode-network'); + + // 快速启动使用 SQLite,没有 postgres 服务 + expect(parsed.parsed.services.postgres).toBeUndefined(); }); it('should validate restart policy', async () => { @@ -197,6 +192,8 @@ describe('Quick Start Profiles - Complete File Verification with YAML Parsing', // 验证重启策略 expect(parsed.parsed.services.hagicode.restart).toBe('unless-stopped'); - expect(parsed.parsed.services.postgres.restart).toBe('unless-stopped'); + + // 快速启动使用 SQLite,没有 postgres 服务 + expect(parsed.parsed.services.postgres).toBeUndefined(); }); }); diff --git a/src/lib/docker-compose/__tests__/bdd/edge-cases.test.ts b/src/lib/docker-compose/__tests__/bdd/edge-cases.test.ts index 72bfd77..a5d3c3a 100644 --- a/src/lib/docker-compose/__tests__/bdd/edge-cases.test.ts +++ b/src/lib/docker-compose/__tests__/bdd/edge-cases.test.ts @@ -233,7 +233,8 @@ describe('Docker Compose Generation: Edge Cases', () => { // Given const config = createMockConfig({ imageRegistry: 'docker-hub', - imageTag: 'v1.2.3' + imageTag: 'v1.2.3', + databaseType: 'internal' // Explicitly set to get postgres service }); // When @@ -248,7 +249,8 @@ describe('Docker Compose Generation: Edge Cases', () => { // Given const config = createMockConfig({ imageRegistry: 'aliyun-acr', - imageTag: 'v2.0.0' + imageTag: 'v2.0.0', + databaseType: 'internal' // Explicitly set to get postgres service }); // When @@ -263,7 +265,8 @@ describe('Docker Compose Generation: Edge Cases', () => { // Given const config = createMockConfig({ imageRegistry: 'azure-acr', - imageTag: 'latest' + imageTag: 'latest', + databaseType: 'internal' // Explicitly set to get postgres service }); // When diff --git a/src/lib/docker-compose/__tests__/bdd/full-custom-scenarios.test.ts b/src/lib/docker-compose/__tests__/bdd/full-custom-scenarios.test.ts index e4b74d8..ceab09a 100644 --- a/src/lib/docker-compose/__tests__/bdd/full-custom-scenarios.test.ts +++ b/src/lib/docker-compose/__tests__/bdd/full-custom-scenarios.test.ts @@ -129,7 +129,7 @@ describe('Docker Compose Generation: Full Custom Profile', () => { }); describe('Scenario 5: Internal Database with Bind Mount', () => { - it('Given an internal database with bind mount, When generating YAML, Then volume section should not be included', () => { + it('Given an internal database with bind mount, When generating YAML, Then volumes section should include hagicode_data but not postgres-data', () => { // Given const config = createMockConfig({ databaseType: 'internal', @@ -141,10 +141,10 @@ describe('Docker Compose Generation: Full Custom Profile', () => { // When const result = generateYAML(config, 'zh-CN', FIXED_DATE); - // Then - check that there's no top-level volumes section - const networksIndex = result.indexOf('\nnetworks:'); - const beforeNetworks = result.substring(0, networksIndex); - expect(beforeNetworks).not.toContain('\nvolumes:'); + // Then - hagicode_data is always present, but postgres-data is not for bind mounts + expect(result).toContain('\nvolumes:'); + expect(result).toContain('hagicode_data:'); + expect(result).not.toContain('postgres-data:'); }); it('Given an internal database with bind mount, When generating YAML, Then postgres service should use bind mount', () => { @@ -190,17 +190,17 @@ describe('Docker Compose Generation: Full Custom Profile', () => { expect(result).toContain('ConnectionStrings__Default: "Host=external-db.example.com;Port=5433'); }); - it('Given an external database configuration, When generating YAML, Then volumes section should not be included', () => { + it('Given an external database configuration, When generating YAML, Then volumes section should include hagicode_data but not postgres-data', () => { // Given const config = createExternalDbConfig(); // When const result = generateYAML(config, 'zh-CN', FIXED_DATE); - // Then - check that there's no top-level volumes section - const servicesEnd = result.indexOf('restart: unless-stopped'); - const afterServices = result.substring(servicesEnd); - expect(afterServices).not.toContain('\nvolumes:'); + // Then - hagicode_data is always present, but postgres-data should not be for external db + expect(result).toContain('\nvolumes:'); + expect(result).toContain('hagicode_data:'); + expect(result).not.toContain('postgres-data:'); }); }); }); diff --git a/src/lib/docker-compose/__tests__/bdd/quick-start-scenarios.test.ts b/src/lib/docker-compose/__tests__/bdd/quick-start-scenarios.test.ts index 673c2f3..8546906 100644 --- a/src/lib/docker-compose/__tests__/bdd/quick-start-scenarios.test.ts +++ b/src/lib/docker-compose/__tests__/bdd/quick-start-scenarios.test.ts @@ -20,7 +20,8 @@ describe('Docker Compose Generation: Quick Start Profile', () => { // Then: output should contain required sections expect(result).toContain('services:'); expect(result).toContain('hagicode:'); - expect(result).toContain('postgres:'); + // Quick start now uses SQLite, so no postgres service + expect(result).not.toContain('postgres:'); expect(result).toContain('volumes:'); expect(result).toContain('networks:'); }); diff --git a/src/lib/docker-compose/__tests__/helpers/config.ts b/src/lib/docker-compose/__tests__/helpers/config.ts index 2327130..b483830 100644 --- a/src/lib/docker-compose/__tests__/helpers/config.ts +++ b/src/lib/docker-compose/__tests__/helpers/config.ts @@ -12,12 +12,12 @@ export function createMockConfig( profile: 'quick-start', httpPort: '8080', containerName: 'hagicode', - imageTag: 'latest', + imageTag: '0', hostOS: 'linux', imageRegistry: 'docker-hub', aspNetEnvironment: 'Production', timezone: 'Asia/Shanghai', - databaseType: 'internal', + databaseType: 'sqlite', postgresDatabase: 'hagicode', postgresUser: 'postgres', postgresPassword: 'postgres', @@ -49,7 +49,7 @@ export function createQuickStartConfig( profile: 'quick-start', hostOS: 'linux', workdirCreatedByRoot: true, - databaseType: 'internal', + databaseType: 'sqlite', volumeType: 'named', ...overrides }); diff --git a/src/lib/docker-compose/__tests__/unit/generator.test.ts b/src/lib/docker-compose/__tests__/unit/generator.test.ts index 6ff1e41..3e2d805 100644 --- a/src/lib/docker-compose/__tests__/unit/generator.test.ts +++ b/src/lib/docker-compose/__tests__/unit/generator.test.ts @@ -46,7 +46,7 @@ describe('buildAppService', () => { const appServiceStr = appService.join('\n'); expect(appService).toContain(' hagicode:'); - expect(appServiceStr).toContain('image: newbe36524/hagicode:latest'); + expect(appServiceStr).toContain('image: newbe36524/hagicode:0'); expect(appServiceStr).toContain('container_name: hagicode'); }); @@ -55,7 +55,7 @@ describe('buildAppService', () => { const appService = buildAppService(config); const appServiceStr = appService.join('\n'); - expect(appServiceStr).toContain('image: registry.cn-hangzhou.aliyuncs.com/hagicode/hagicode:latest'); + expect(appServiceStr).toContain('image: registry.cn-hangzhou.aliyuncs.com/hagicode/hagicode:0'); }); it('should include Anthropic API configuration', () => { @@ -275,15 +275,18 @@ describe('buildVolumesSection', () => { expect(volumesStr).toContain('postgres-data:'); }); - it('should not generate volumes for external database', () => { + it('should not generate postgres-data volume for external database', () => { const config = createMockConfig({ databaseType: 'external' }); const volumes = buildVolumesSection(config); const volumesStr = volumes.join('\n'); - expect(volumesStr).not.toContain('volumes:'); + // hagicode_data is always present, but postgres-data should not be + expect(volumesStr).toContain('volumes:'); + expect(volumesStr).toContain('hagicode_data:'); + expect(volumesStr).not.toContain('postgres-data:'); }); - it('should not generate volumes for bind mount', () => { + it('should generate hagicode_data volume for bind mount (no postgres-data)', () => { const config = createMockConfig({ databaseType: 'internal', volumeType: 'bind' @@ -291,7 +294,10 @@ describe('buildVolumesSection', () => { const volumes = buildVolumesSection(config); const volumesStr = volumes.join('\n'); - expect(volumesStr).not.toContain('volumes:'); + // hagicode_data is always present, but postgres-data is not for bind mounts + expect(volumesStr).toContain('volumes:'); + expect(volumesStr).toContain('hagicode_data:'); + expect(volumesStr).not.toContain('postgres-data:'); }); }); @@ -325,7 +331,7 @@ describe('generateYAML', () => { expect(yaml).toContain('pcode-network:'); }); - it('should generate YAML without volumes for external database', () => { + it('should generate YAML with hagicode_data volume for external database (no postgres-data)', () => { const config = createMockConfig({ databaseType: 'external', externalDbHost: 'external-host', @@ -337,10 +343,12 @@ describe('generateYAML', () => { expect(yaml).toContain('hagicode:'); expect(yaml).not.toContain('postgres:'); - // Check that there's no top-level volumes section (after the services section) + // hagicode_data volume is always present, but postgres-data should not be const servicesEnd = yaml.indexOf('restart: unless-stopped'); const afterServices = yaml.substring(servicesEnd); - expect(afterServices).not.toContain('\nvolumes:'); + expect(afterServices).toContain('\nvolumes:'); + expect(afterServices).toContain('hagicode_data:'); + expect(afterServices).not.toContain('postgres-data:'); }); it('should use fixed date when provided', () => { diff --git a/src/lib/docker-compose/defaultConfig.ts b/src/lib/docker-compose/defaultConfig.ts index 7b5fe94..0587e6e 100644 --- a/src/lib/docker-compose/defaultConfig.ts +++ b/src/lib/docker-compose/defaultConfig.ts @@ -4,12 +4,12 @@ export const defaultConfig: DockerComposeConfig = { profile: 'quick-start', httpPort: '45000', containerName: 'hagicode-app', - imageTag: 'latest', + imageTag: '0', hostOS: 'linux', imageRegistry: 'aliyun-acr', aspNetEnvironment: 'Production', timezone: 'Asia/Shanghai', - databaseType: 'internal', + databaseType: 'sqlite', postgresDatabase: 'hagicode', postgresUser: 'postgres', postgresPassword: 'postgres', diff --git a/src/lib/docker-compose/generator.ts b/src/lib/docker-compose/generator.ts index 884f753..e8a42b5 100644 --- a/src/lib/docker-compose/generator.ts +++ b/src/lib/docker-compose/generator.ts @@ -74,10 +74,15 @@ export function buildAppService(config: DockerComposeConfig): string[] { lines.push(' ASPNETCORE_URLS: http://+:45000'); lines.push(` TZ: ${config.timezone}`); - // Database connection string - if (config.databaseType === 'internal') { + // Database connection string and provider + if (config.databaseType === 'sqlite') { + lines.push(' Database__Provider: sqlite'); + lines.push(' ConnectionStrings__Default: "Data Source=/app/data/hagicode.db"'); + } else if (config.databaseType === 'internal') { + lines.push(' Database__Provider: postgresql'); lines.push(` ConnectionStrings__Default: "Host=postgres;Port=5432;Database=${config.postgresDatabase};Username=${config.postgresUser};Password=${config.postgresPassword}"`); } else { + lines.push(' Database__Provider: postgresql'); lines.push(` ConnectionStrings__Default: "Host=${config.externalDbHost};Port=${config.externalDbPort};Database=${config.postgresDatabase};Username=${config.postgresUser};Password=${config.postgresPassword}"`); } @@ -137,7 +142,11 @@ export function buildAppService(config: DockerComposeConfig): string[] { lines.push(` - ${config.workdirPath || '/home/user/repos'}:/app/workdir`); } - // Depends on + // Application data volume (always present for all database types) + // Contains SQLite database file when using SQLite, or Orleans grain storage, logs, etc. for PostgreSQL + lines.push(' - hagicode_data:/app/data'); + + // Depends on (only for internal PostgreSQL) if (config.databaseType === 'internal') { lines.push(' depends_on:'); lines.push(' postgres:'); @@ -230,10 +239,15 @@ export function buildServicesSection(config: DockerComposeConfig): string[] { export function buildVolumesSection(config: DockerComposeConfig): string[] { const lines: string[] = []; + lines.push(''); + lines.push('volumes:'); + + // hagicode_data is always present (contains app data: SQLite database or Orleans grain storage, logs, etc.) + lines.push(' hagicode_data:'); + + // postgres-data volume only for internal PostgreSQL if (config.databaseType === 'internal' && config.volumeType === 'named') { const volName = config.volumeName || 'postgres-data'; - lines.push(''); - lines.push('volumes:'); lines.push(` ${volName}:`); } diff --git a/src/lib/docker-compose/slice.ts b/src/lib/docker-compose/slice.ts index c31334a..e5dc7a5 100644 --- a/src/lib/docker-compose/slice.ts +++ b/src/lib/docker-compose/slice.ts @@ -2,6 +2,9 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; import type { DockerComposeConfig } from '../../lib/docker-compose/types'; import { defaultConfig } from '../../lib/docker-compose/defaultConfig'; +// Configuration version - increment to invalidate old localStorage caches +const CONFIG_VERSION = '2.0'; + interface DockerComposeState { config: DockerComposeConfig; isLoading: boolean; @@ -14,6 +17,17 @@ const getInitialConfig = (): DockerComposeConfig => { } try { + const savedVersion = localStorage.getItem('docker-compose-config-version'); + + // If version doesn't match, clear old config and use new defaults + if (savedVersion !== CONFIG_VERSION) { + console.log('Config version mismatch, resetting to defaults'); + localStorage.setItem('docker-compose-config-version', CONFIG_VERSION); + localStorage.removeItem('docker-compose-config'); + localStorage.setItem('docker-compose-image-registry', defaultConfig.imageRegistry); + return { ...defaultConfig }; + } + const savedConfig = localStorage.getItem('docker-compose-config'); if (savedConfig) { return { ...defaultConfig, ...JSON.parse(savedConfig) }; @@ -49,6 +63,7 @@ const dockerComposeSlice = createSlice({ // Save to localStorage if (typeof window !== 'undefined') { try { + localStorage.setItem('docker-compose-config-version', CONFIG_VERSION); localStorage.setItem('docker-compose-config', JSON.stringify(state.config)); localStorage.setItem('docker-compose-image-registry', state.config.imageRegistry); } catch (error) { @@ -77,9 +92,15 @@ const dockerComposeSlice = createSlice({ const { field, value } = action.payload; state.config[field] = value; + // Auto-reset database type to SQLite when switching to quick-start profile + if (field === 'profile' && value === 'quick-start') { + state.config.databaseType = 'sqlite'; + } + // Save to localStorage if (typeof window !== 'undefined') { try { + localStorage.setItem('docker-compose-config-version', CONFIG_VERSION); localStorage.setItem('docker-compose-config', JSON.stringify(state.config)); if (field === 'imageRegistry') { localStorage.setItem('docker-compose-image-registry', value as string); diff --git a/src/lib/docker-compose/types.ts b/src/lib/docker-compose/types.ts index 6ae3215..bb7a5b0 100644 --- a/src/lib/docker-compose/types.ts +++ b/src/lib/docker-compose/types.ts @@ -3,7 +3,7 @@ * Migrated from pcode-docs project */ -export type DatabaseType = 'internal' | 'external'; +export type DatabaseType = 'sqlite' | 'internal' | 'external'; export type HostOS = 'windows' | 'linux'; export type LicenseKeyType = 'public' | 'custom'; export type VolumeType = 'named' | 'bind';