Indie — Independent Website Platform
A platform that lets anyone create, own, and publish their own static website through AI prompts. Describe what you want, and the AI generates it. Every site is backed by a git repository.
Quick Start
Prerequisites
- Java 17+
- Node.js 18+ and npm
- Docker & Docker Compose (for PostgreSQL, MongoDB)
- npm packages:
@angular/cli@17(npm install -g @angular/cli@17)
Setup
-
Start infrastructure:
docker compose up -dThis starts PostgreSQL (port 5432) and MongoDB (port 27017).
-
Start the backend:
cd backend ./gradlew bootRunThe API will be available at
http://localhost:8080. Swagger UI athttp://localhost:8080/swagger-ui.html. -
Start the frontend (separate terminal):
cd frontend npm install ng serveThe app will be available at
http://localhost:4200. API calls are proxied tohttp://localhost:8080.
Infrastructure (Docker Compose)
| Service | Image | Ports | Credentials | Volume |
|---|---|---|---|---|
| PostgreSQL | postgres:16-alpine |
host:5432 | indie / indie_dev |
pgdata |
| MongoDB | mongo:7 |
host:27017 | (no auth) | mongodata |
Environment Variables
| Variable | Description | Default |
|---|---|---|
| Database | ||
SPRING_DATASOURCE_URL |
PostgreSQL JDBC URL | jdbc:postgresql://localhost:5432/indie |
SPRING_DATASOURCE_USERNAME |
DB username | indie |
SPRING_DATASOURCE_PASSWORD |
DB password | indie_dev |
MONGODB_URI |
MongoDB connection URI | mongodb://localhost:27017/indie |
| JWT | ||
INDIE_JWT_ACCESS_EXPIRATION |
Access token TTL (ms) | 900000 (15 min) |
INDIE_JWT_REFRESH_EXPIRATION |
Refresh token TTL (ms) | 604800000 (7 days) |
INDIE_JWT_PRIVATE_KEY_PATH |
RS256 private key path | config/jwt-private.key |
INDIE_JWT_PUBLIC_KEY_PATH |
RS256 public key path | config/jwt-public.key |
| Git | ||
INDIE_GIT_REPOS_PATH |
Git repositories root | /var/git/sites/ |
INDIE_GIT_AUTHOR_NAME |
Default git author name | Indie Platform |
INDIE_GIT_AUTHOR_EMAIL |
Default git author email | git@indie.local |
| Encryption | ||
INDIE_ENCRYPTION_KEY_PATH |
AES-256-GCM master key path | config/encryption.key |
| App | ||
INDIE_APP_BASE_URL |
Frontend base URL (for verification emails) | http://localhost:4200 |
INDIE_VERIFICATION_TOKEN_EXPIRATION |
Email verification token TTL (minutes) | 1440 (24h) |
INDIE_CORS_ORIGINS |
Allowed CORS origins (comma-separated) | http://localhost:4200,http://localhost:3000 |
| LLM | ||
GEMINI_API_KEY |
Google Gemini API key | — |
GEMINI_MODEL |
Gemini model name | gemini-2.0-flash |
OPENAI_API_KEY |
OpenAI API key | — |
OPENAI_MODEL |
OpenAI model name | gpt-4o-mini |
INDIE_LLM_RATE_LIMIT |
Max LLM requests per hour per user | 10 |
| Email (MailHog compatible) | ||
EMAIL_HOST |
SMTP host | localhost |
EMAIL_PORT |
SMTP port | 1025 |
EMAIL_USERNAME |
SMTP username | (empty) |
EMAIL_PASSWORD |
SMTP password | (empty) |
EMAIL_SMTP_AUTH |
Enable SMTP auth | true |
EMAIL_SMTP_STARTTLS |
Enable STARTTLS | true |
Project Structure
backend/
├── build.gradle # Dependencies and build config
├── settings.gradle # Project name
├── gradlew / gradlew.bat # Gradle wrapper
├── gradle/wrapper/ # Wrapper JAR + properties
└── src/
├── main/java/com/krrishg/
│ ├── IndieApplication.java # Spring Boot main class (@EnableScheduling)
│ ├── config/
│ │ ├── SecurityConfig.java # HTTP security, JWT filter, CORS
│ │ ├── JwtConfig.java # RS256 token generation and validation
│ │ ├── TokenService.java # Interface for token generation/validation
│ │ ├── JwtAuthenticationToken.java # Custom AbstractAuthenticationToken
│ │ ├── UserPrincipal.java # UserDetails record
│ │ ├── GitConfig.java # Git repository settings
│ │ ├── EncryptionService.java # AES-256/GCM for LLM API key encryption
│ │ ├── RateLimitConfig.java # Bucket4j token-bucket rate limiter
│ │ ├── SchemaMigration.java # Startup DDL migrations
│ │ ├── MapToJsonConverter.java # JPA converter for JSON text columns
│ │ └── GlobalExceptionHandler.java # @ControllerAdvice error handling
│ ├── model/
│ │ ├── User.java # User entity (email, password, emailVerified)
│ │ ├── VerificationToken.java # Email verification / set-password tokens
│ │ ├── Site.java # Site entity per user
│ │ ├── SiteStatus.java # Enum: DRAFT, PUBLISHED
│ │ ├── AuthProvider.java # Enum: LOCAL only (OAuth planned)
│ │ ├── GitRemote.java # Connected git remote (GitHub/GitLab)
│ │ ├── SelfHostedConfig.java # SSH/deploy configuration per site
│ │ └── GenerationVersion.java # MongoDB: LLM generation history per site
│ ├── repository/
│ │ ├── UserRepository.java
│ │ ├── SiteRepository.java
│ │ ├── GitRemoteRepository.java
│ │ ├── SelfHostedConfigRepository.java
│ │ └── VerificationTokenRepository.java
│ ├── dto/
│ │ ├── RegisterRequest.java
│ │ ├── RegistrationResponse.java
│ │ ├── LoginRequest.java
│ │ ├── RefreshTokenRequest.java
│ │ ├── AuthResponse.java # JWT tokens + user info
│ │ ├── VerifyEmailRequest.java
│ │ ├── VerifyEmailResponse.java
│ │ ├── ResendVerificationRequest.java
│ │ ├── SetPasswordRequest.java
│ │ ├── SiteRequest.java # Includes deploymentTarget field
│ │ ├── SiteResponse.java
│ │ ├── SiteStructure.java # Top-level DTO: pages + theme
│ │ ├── Reference.java # URL/IMAGE reference for LLM context
│ │ ├── LLMOptions.java # LLM generation options
│ │ ├── LLMResult.java # LLM generation result
│ │ ├── LLMConfigRequest.java # User LLM provider config
│ │ ├── SelfHostedConfigRequest.java
│ │ └── SelfHostedConfigResponse.java
│ ├── service/
│ │ ├── AuthService.java # Register, login, token refresh, verify email
│ │ ├── EmailService.java # Verification email sender
│ │ ├── CleanupService.java # Scheduled cleanup of stale unverified accounts
│ │ ├── LLMService.java # Orchestrates LLM calls, stores/restores versions
│ │ ├── LLMResponseParser.java # Parses LLM JSON into SiteStructure
│ │ ├── ReferenceService.java # Fetches web page content for LLM context
│ │ ├── RenderService.java # Renders SiteStructure into HTML + CSS + JS
│ │ ├── DeploymentService.java # Orchestrates publish/unpublish
│ │ ├── Deployer.java # Interface for self-hosted deployment
│ │ ├── SshRsyncDeployer.java # SSH + rsync + nginx + Let's Encrypt
│ │ ├── PagesProvider.java # Interface for git provider Pages
│ │ ├── GitHubPagesProvider.java # GitHub Pages implementation
│ │ ├── GitLabPagesProvider.java # GitLab Pages implementation
│ │ ├── GitService.java # JGit: init, commit, push, log, rollback
│ │ ├── KeyManager.java # SSH key-pair generation per site
│ │ ├── ExportService.java # Exports site structure to ZIP
│ │ └── DomainService.java # Custom domain resolution skeleton
│ ├── service/llm/
│ │ ├── LLMProvider.java # Interface for LLM providers
│ │ ├── GeminiProvider.java # Google Gemini implementation
│ │ ├── OpenAIProvider.java # OpenAI implementation
│ │ └── ProviderFactory.java # Factory creating providers by name + key
│ ├── engine/
│ │ ├── JsBundle.java # Generates static JS (nav, smooth scroll, lightbox, contact form)
│ │ └── StyleCompiler.java # Generates base CSS with custom property fallbacks
│ └── controller/
│ ├── AuthController.java # POST /api/auth/*
│ ├── SiteController.java # CRUD /api/sites
│ ├── LLMController.java # POST /api/llm/generate, /refine, /versions/*
│ ├── GitController.java # POST /api/sites/{id}/git/*
│ ├── DeploymentController.java # POST /api/sites/{id}/publish, /unpublish
│ ├── ExportController.java # GET /api/sites/{id}/export
│ ├── UserSettingsController.java # GET/PUT/DELETE /api/user/llm-config
│ └── SelfHostedController.java # CRUD /api/sites/{id}/self-hosted
├── main/resources/
│ ├── application.yml # All configuration
│ └── prompts/
│ ├── site-generator.txt # LLM system prompt for site generation
│ └── site-refiner.txt # LLM system prompt for refinement
└── test/java/com/krrishg/
├── config/ # 7 config tests
├── controller/ # 9 controller tests
├── dto/ # 7 DTO tests
├── engine/ # 2 engine tests
├── model/ # 7 model tests
├── repository/ # 4 repository tests
├── service/ # 16 service tests + fakes
├── service/llm/ # 3 LLM provider tests
└── support/ # 13 test fakes, stubs, and configs
frontend/
├── Dockerfile # Multi-stage production build
├── nginx.conf # Nginx config (SPA fallback + /api/ proxy)
├── proxy.conf.json # Dev proxy to backend
├── angular.json # Angular CLI configuration
├── tailwind.config.js # Tailwind CSS configuration
├── postcss.config.js # PostCSS (tailwindcss + autoprefixer)
├── package.json
├── tsconfig.json / tsconfig.app.json / tsconfig.spec.json
├── src/
│ ├── index.html
│ ├── main.ts # Angular bootstrap
│ ├── styles.css # Global styles + Tailwind imports
│ ├── favicon.ico
│ ├── assets/
│ └── app/
│ ├── app.component.ts / .html / .css
│ ├── app.config.ts # App providers
│ ├── app.routes.ts # Lazy-loaded routes
│ ├── core/
│ │ ├── models/
│ │ │ ├── auth-response.ts # AuthResponse & UserInfo
│ │ │ ├── site.ts # SiteResponse
│ │ │ ├── page.ts # PageResponse
│ │ │ ├── llm.ts # LLM request/response types, GenerationVersion
│ │ │ └── deployment.ts # Git/self-hosted config types
│ │ ├── services/
│ │ │ ├── auth.service.ts # Auth HTTP + token storage
│ │ │ ├── site.service.ts # Site CRUD HTTP
│ │ │ ├── page.service.ts # Page CRUD HTTP
│ │ │ ├── deployment.service.ts # Export, git, publish HTTP
│ │ │ ├── llm-api.service.ts # LLM generate/refine/version HTTP
│ │ │ └── llm-config.service.ts # User LLM key storage HTTP
│ │ ├── guards/
│ │ │ ├── auth.guard.ts # Redirects unauthenticated to /login
│ │ │ └── login.guard.ts # Redirects authenticated to /dashboard
│ │ └── interceptors/
│ │ └── jwt.interceptor.ts # Attaches Bearer token
│ ├── auth/
│ │ ├── login/
│ │ │ └── login.component.ts
│ │ ├── register/
│ │ │ └── register.component.ts
│ │ └── verify-email/
│ │ └── verify-email.component.ts # Email verification + set-password
│ ├── dashboard/
│ │ ├── dashboard.component.ts # Site cards grid + toolbar
│ │ ├── create-site-dialog/
│ │ │ └── create-site-dialog.ts
│ │ └── delete-site-dialog/
│ │ └── delete-site-dialog.ts
│ └── llm-workspace/
│ ├── llm-workspace.component.ts # Main workspace (prompt, preview, timeline)
│ ├── llm-session.service.ts # LLM generation session state
│ ├── settings/
│ │ └── settings.component.ts # LLM provider key/dialog settings
│ ├── git-settings/
│ │ └── git-settings.component.ts # Git remote connection settings
│ ├── preview-dialog/
│ │ └── preview-dialog.component.ts # Preview generated site
│ └── delete-version-dialog/
│ └── delete-version-dialog.ts # Confirm delete version
config/ # Auto-generated on first boot
├── jwt-private.key # RS256 private key
├── jwt-public.key # RS256 public key
└── encryption.key # AES-256-GCM master key (base64)
API Overview
Authentication (/api/auth)
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/register |
Register with name + email + password (returns verification pending) |
| POST | /api/auth/login |
Login with email + password |
| POST | /api/auth/refresh |
Refresh access token |
| POST | /api/auth/logout |
Logout |
| POST | /api/auth/verify-email |
Verify email via token, returns setup token |
| POST | /api/auth/resend-verification |
Resend verification email |
| POST | /api/auth/set-password |
Set password after email verification |
Sites (/api/sites)
| Method | Path | Description |
|---|---|---|
| GET | /api/sites |
List user's sites |
| POST | /api/sites |
Create a new site (with deploymentTarget: GIT_PAGES, SELF_HOSTED, or NONE) |
| GET | /api/sites/{id} |
Get site details |
| PUT | /api/sites/{id} |
Update site |
| DELETE | /api/sites/{id} |
Delete site |
LLM Generation (/api/llm)
| Method | Path | Description |
|---|---|---|
| POST | /api/llm/generate |
Generate site from prompt (rate-limited to 10/hour/user) |
| POST | /api/llm/refine |
Refine existing site structure with follow-up prompt |
| GET | /api/llm/versions/{siteId} |
List generation versions for a site |
| POST | /api/llm/versions/{id}/restore |
Restore a specific generation version |
| DELETE | /api/llm/versions/{id} |
Delete a generation version |
Git Remotes (/api/sites/{id}/git)
| Method | Path | Description |
|---|---|---|
| POST | /api/sites/{id}/git/connect |
Connect git remote (GitHub/GitLab) |
| POST | /api/sites/{id}/git/disconnect |
Disconnect git remote |
| GET | /api/sites/{id}/git/status |
Get connection status, SSH keys, commit log |
Deployment (/api/sites/{id})
| Method | Path | Description |
|---|---|---|
| POST | /api/sites/{id}/publish |
Publish site (git push) |
| POST | /api/sites/{id}/unpublish |
Unpublish site |
| GET | /api/sites/{id}/export |
Download site as .zip archive |
Self-Hosted Deployment (/api/sites/{id}/self-hosted)
| Method | Path | Description |
|---|---|---|
| GET | /api/sites/{id}/self-hosted |
Get SSH/deploy configuration |
| PUT | /api/sites/{id}/self-hosted |
Save SSH/deploy configuration |
| DELETE | /api/sites/{id}/self-hosted |
Remove configuration |
User Settings (/api/user)
| Method | Path | Description |
|---|---|---|
| GET | /api/user/llm-config |
Get saved LLM provider configs (keys redacted) |
| PUT | /api/user/llm-config |
Save API key / base URL / model for a provider |
| DELETE | /api/user/llm-config/{provider} |
Delete saved config for a provider |
Frontend Routes
| Path | Component | Auth |
|---|---|---|
/login |
LoginComponent | Redirects to /dashboard if already logged in |
/register |
RegisterComponent | Redirects to /dashboard if already logged in |
/verify-email |
VerifyEmailComponent | Public (email verification flow) |
/dashboard |
DashboardComponent | Requires auth |
/llm/:siteId |
LlmWorkspaceComponent | Requires auth |
/llm/:siteId/settings |
SettingsComponent | Requires auth |
/llm/:siteId/git |
GitSettingsComponent | Requires auth |
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Java 17, Spring Boot 3.2, Gradle (Groovy) |
| Frontend | Angular 17, Angular Material, Tailwind CSS, Leaflet |
| Relational DB | PostgreSQL 16 |
| Document DB | MongoDB 7 (generation versions, LLM sessions) |
| Auth | Spring Security + JWT (RS256), email/password | | LLM | Gemini (primary), OpenAI (fallback), provider-abstraction | | Git | JGit — local repo per site, GitHub/GitLab remote push | | Email | Spring Boot Mail (MailHog compatible) | | Rate Limiting | Bucket4j | | API Docs | Springdoc OpenAPI (Swagger UI) |
Architecture
┌──────────────────────┐
│ Spring Boot API │
│ (VPS / Docker) │
└────────┬─────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────────▼───┐ ┌──────▼──────┐ ┌───▼────────┐
│ PostgreSQL │ │ MongoDB │ │ Git Repos │
│ (users, │ │ (versions, │ │ /var/git/ │
│ sites) │ │ sessions) │ └────────────┘
└─────────────┘ └─────────────┘
Development
# Backend
cd backend
./gradlew build # Build
./gradlew test # Run tests (unit + slice + integration)
./gradlew bootRun # Run locally (requires docker compose up -d first)
# Frontend
cd frontend
npm install # Install dependencies
ng serve # Dev server at localhost:4200
ng build # Production build (outputs to dist/)
ng test # Run unit tests (Jasmine + Karma)
Production Build (Docker)
docker build -t indie-frontend frontend/
docker run -p 80:80 indie-frontend
The frontend Docker image serves the app via nginx (port 80) and proxies /api/ requests to http://backend:8080.
Roadmap
v0.1 — Auth & Foundation ✅
- User registration (email/password + email verification)
- User login (email/password + JWT)
- Token refresh with 7-day refresh token
- Site CRUD (create, list, get, update, delete)
- Frontend: Login, Register, Verify Email, Dashboard
- Frontend: JWT interceptor, auth guard, login guard, lazy routes
- Scheduled cleanup of unverified accounts
v0.2 — LLM Prompt Workspace ✅
- Prompt-to-site generation via Gemini/OpenAI
- Version timeline with MongoDB storage
- Live preview (sandboxed iframe)
- Content editor (grouped by page)
- Refine via follow-up prompts
- Rate limiting (10 generations/hour/user)
- Reference URL support for context
v0.3 — Export, Git & Hosting ✅
- Render engine (HTML + CSS + JS generation)
- Git init, commit, push via JGit
- SSH key-pair management per site
- GitHub Pages / GitLab Pages providers
- Export as
.ziparchive - Publish/unpublish flow
- Custom domain skeleton
- Self-hosted deployment (SSH + rsync + nginx)
LLM Provider Abstraction
public interface LLMProvider {
LLMResult generate(String systemPrompt, String userPrompt,
List<Reference> references, LLMOptions options);
String getName();
}
Adding a new provider = implement LLMProvider interface + add config block in application.yml.
Provider API keys can be configured globally via env vars (GEMINI_API_KEY, OPENAI_API_KEY) or per-user through the /api/user/llm-config endpoint (encrypted at rest via AES-256-GCM).
Key Design Decisions
-
Pure prompt-based — No drag-and-drop builder. Every site starts from a prompt. Iteration happens through follow-up prompts and inline text edits.
-
Static output only — Generated sites are pure HTML/CSS/JS. No runtime framework dependency. Users own the output forever.
-
Git as source of truth — Every site is a git repository. Publish = commit. Rollback = checkout. Export includes full
.git/. -
Version timeline — Every generation creates a version in MongoDB. Users can browse, preview, and restore any previous version.
-
No billing in MVP — Monetization is post-MVP.
config/ Directory
The following files live in config/ and are auto-generated on first boot if they don't exist.
In production, generate and mount them manually:
# RS256 key pair for JWT
openssl genpkey -algorithm RSA -out config/jwt-private.key -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in config/jwt-private.key -out config/jwt-public.key
# AES-256-GCM master key for LLM API key encryption
openssl rand -base64 32 > config/encryption.key
Set the INDIE_JWT_PRIVATE_KEY_PATH, INDIE_JWT_PUBLIC_KEY_PATH, and
INDIE_ENCRYPTION_KEY_PATH env vars to point to these files.