Add architecture, deployment, and development documentation for b0esche.cloud
This commit is contained in:
@@ -220,7 +220,13 @@ cd b0esche_cloud && flutter test
|
||||
|
||||
## Documentation
|
||||
|
||||
- [AUTH.md](docs/AUTH.md) - Complete authentication system documentation (Passkeys, OIDC, roles)
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | System architecture, components, data flows |
|
||||
| [API.md](docs/API.md) | Complete API endpoint reference |
|
||||
| [AUTH.md](docs/AUTH.md) | Authentication system (Passkeys, OIDC, roles) |
|
||||
| [DEVELOPMENT.md](docs/DEVELOPMENT.md) | Local setup, coding conventions, testing |
|
||||
| [DEPLOYMENT.md](docs/DEPLOYMENT.md) | Production deployment, operations, troubleshooting |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
596
docs/API.md
Normal file
596
docs/API.md
Normal file
@@ -0,0 +1,596 @@
|
||||
# b0esche.cloud API Reference
|
||||
|
||||
Base URL: `https://go.b0esche.cloud`
|
||||
|
||||
## Authentication
|
||||
|
||||
All authenticated endpoints require a JWT token in the Authorization header:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Check
|
||||
|
||||
### GET /health
|
||||
Check if the API is running.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2026-01-13T19:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication Endpoints
|
||||
|
||||
### Passkey Registration
|
||||
|
||||
#### POST /auth/passkey/register/start
|
||||
Start passkey registration for a new user.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "johndoe"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"publicKey": {
|
||||
"challenge": "base64-encoded-challenge",
|
||||
"rp": {
|
||||
"name": "b0esche.cloud",
|
||||
"id": "www.b0esche.cloud"
|
||||
},
|
||||
"user": {
|
||||
"id": "base64-user-id",
|
||||
"name": "johndoe",
|
||||
"displayName": "johndoe"
|
||||
},
|
||||
"pubKeyCredParams": [...],
|
||||
"timeout": 300000,
|
||||
"attestation": "none"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /auth/passkey/register/verify
|
||||
Complete passkey registration.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "johndoe",
|
||||
"credential": {
|
||||
"id": "credential-id",
|
||||
"rawId": "base64-raw-id",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"clientDataJSON": "base64-client-data",
|
||||
"attestationObject": "base64-attestation"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "johndoe"
|
||||
},
|
||||
"token": "jwt-token"
|
||||
}
|
||||
```
|
||||
|
||||
### Passkey Login
|
||||
|
||||
#### POST /auth/passkey/login/start
|
||||
Start passkey authentication.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "johndoe"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"publicKey": {
|
||||
"challenge": "base64-challenge",
|
||||
"timeout": 300000,
|
||||
"rpId": "www.b0esche.cloud",
|
||||
"allowCredentials": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /auth/passkey/login/verify
|
||||
Complete passkey authentication.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "johndoe",
|
||||
"credential": {
|
||||
"id": "credential-id",
|
||||
"rawId": "base64-raw-id",
|
||||
"type": "public-key",
|
||||
"response": {
|
||||
"clientDataJSON": "base64-client-data",
|
||||
"authenticatorData": "base64-auth-data",
|
||||
"signature": "base64-signature"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "johndoe",
|
||||
"role": "user"
|
||||
},
|
||||
"token": "jwt-token"
|
||||
}
|
||||
```
|
||||
|
||||
### Device Management
|
||||
|
||||
#### GET /auth/passkey/devices
|
||||
List user's registered passkeys.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"credentialId": "credential-id",
|
||||
"deviceLabel": "MacBook Pro",
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"lastUsedAt": "2026-01-13T19:00:00Z",
|
||||
"backupEligible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /auth/passkey/devices/add
|
||||
Add a new passkey to existing account.
|
||||
|
||||
#### DELETE /auth/passkey/devices/{passkeyId}
|
||||
Remove a passkey from account.
|
||||
|
||||
### Recovery Codes
|
||||
|
||||
#### POST /auth/recovery/codes/generate
|
||||
Generate new recovery codes (invalidates old ones).
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"codes": [
|
||||
"XXXX-XXXX-XXXX",
|
||||
"YYYY-YYYY-YYYY",
|
||||
...
|
||||
],
|
||||
"expiresAt": "2027-01-13T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /auth/recovery/codes/use
|
||||
Use a recovery code to authenticate.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "johndoe",
|
||||
"code": "XXXX-XXXX-XXXX"
|
||||
}
|
||||
```
|
||||
|
||||
### Password (Optional Fallback)
|
||||
|
||||
#### POST /auth/password/add
|
||||
Add password to account.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"password": "secure-password"
|
||||
}
|
||||
```
|
||||
|
||||
#### DELETE /auth/password/remove
|
||||
Remove password from account.
|
||||
|
||||
---
|
||||
|
||||
## User Endpoints
|
||||
|
||||
### GET /api/me
|
||||
Get current user profile.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"username": "johndoe",
|
||||
"email": "john@example.com",
|
||||
"displayName": "John Doe",
|
||||
"role": "user",
|
||||
"createdAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### PATCH /api/me
|
||||
Update user profile.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"displayName": "John D.",
|
||||
"email": "newemail@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Organization Endpoints
|
||||
|
||||
### GET /api/organizations
|
||||
List user's organizations.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"organizations": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Team",
|
||||
"slug": "my-team",
|
||||
"role": "owner",
|
||||
"memberCount": 5,
|
||||
"createdAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/organizations
|
||||
Create a new organization.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "My New Team",
|
||||
"slug": "my-new-team"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/organizations/{orgId}
|
||||
Get organization details.
|
||||
|
||||
### PATCH /api/organizations/{orgId}
|
||||
Update organization.
|
||||
|
||||
### DELETE /api/organizations/{orgId}
|
||||
Delete organization (owner only).
|
||||
|
||||
### GET /api/organizations/{orgId}/members
|
||||
List organization members.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"members": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"userId": "user-uuid",
|
||||
"username": "johndoe",
|
||||
"displayName": "John Doe",
|
||||
"role": "owner",
|
||||
"joinedAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/organizations/{orgId}/members
|
||||
Add member to organization.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"userId": "user-uuid",
|
||||
"role": "member"
|
||||
}
|
||||
```
|
||||
|
||||
### PATCH /api/organizations/{orgId}/members/{memberId}
|
||||
Update member role.
|
||||
|
||||
### DELETE /api/organizations/{orgId}/members/{memberId}
|
||||
Remove member from organization.
|
||||
|
||||
---
|
||||
|
||||
## File Endpoints
|
||||
|
||||
### GET /api/files
|
||||
List files in a directory.
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `path` | string | Directory path (default: `/`) |
|
||||
| `orgId` | string | Organization ID (optional) |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "document.pdf",
|
||||
"path": "/documents/document.pdf",
|
||||
"type": "file",
|
||||
"mimeType": "application/pdf",
|
||||
"size": 1048576,
|
||||
"createdAt": "2026-01-01T00:00:00Z",
|
||||
"modifiedAt": "2026-01-13T19:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "photos",
|
||||
"path": "/photos",
|
||||
"type": "folder",
|
||||
"createdAt": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/files/upload
|
||||
Upload a file.
|
||||
|
||||
**Request:** `multipart/form-data`
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `file` | file | The file to upload |
|
||||
| `path` | string | Destination path |
|
||||
| `orgId` | string | Organization ID (optional) |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"file": {
|
||||
"id": "uuid",
|
||||
"name": "uploaded-file.pdf",
|
||||
"path": "/documents/uploaded-file.pdf",
|
||||
"size": 1048576
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/files/download
|
||||
Download a file.
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `path` | string | File path |
|
||||
| `orgId` | string | Organization ID (optional) |
|
||||
|
||||
**Response:** File binary with appropriate Content-Type header.
|
||||
|
||||
### POST /api/files/folder
|
||||
Create a folder.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"path": "/new-folder",
|
||||
"orgId": "org-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
### DELETE /api/files
|
||||
Delete a file or folder.
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `path` | string | Path to delete |
|
||||
| `orgId` | string | Organization ID (optional) |
|
||||
|
||||
### POST /api/files/move
|
||||
Move/rename a file or folder.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"sourcePath": "/old-name.pdf",
|
||||
"destinationPath": "/new-name.pdf",
|
||||
"orgId": "org-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/files/copy
|
||||
Copy a file or folder.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"sourcePath": "/original.pdf",
|
||||
"destinationPath": "/copy.pdf",
|
||||
"orgId": "org-uuid"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Endpoints
|
||||
|
||||
*Requires admin or superadmin role.*
|
||||
|
||||
### GET /api/admin/users
|
||||
List all users.
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `page` | int | Page number (default: 1) |
|
||||
| `limit` | int | Items per page (default: 50) |
|
||||
| `search` | string | Search by username/email |
|
||||
|
||||
### GET /api/admin/users/{userId}
|
||||
Get user details.
|
||||
|
||||
### PATCH /api/admin/users/{userId}
|
||||
Update user (role, status).
|
||||
|
||||
### DELETE /api/admin/users/{userId}
|
||||
Delete user account.
|
||||
|
||||
### Admin Invitations
|
||||
|
||||
#### GET /auth/admin/invitations
|
||||
List admin invitations.
|
||||
|
||||
#### POST /auth/admin/invitations
|
||||
Create admin invitation.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "newadmin",
|
||||
"roleId": "admin-role-uuid",
|
||||
"expiresIn": 86400
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"invitation": {
|
||||
"id": "uuid",
|
||||
"token": "invite-token",
|
||||
"expiresAt": "2026-01-14T19:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /auth/admin/invitations/{token}/accept
|
||||
Accept an admin invitation.
|
||||
|
||||
#### DELETE /auth/admin/invitations/{token}
|
||||
Revoke an invitation.
|
||||
|
||||
---
|
||||
|
||||
## Activity Endpoints
|
||||
|
||||
### GET /api/activities
|
||||
Get activity log.
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `page` | int | Page number |
|
||||
| `limit` | int | Items per page |
|
||||
| `orgId` | string | Filter by organization |
|
||||
| `userId` | string | Filter by user |
|
||||
| `action` | string | Filter by action type |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"activities": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"userId": "user-uuid",
|
||||
"username": "johndoe",
|
||||
"action": "file.upload",
|
||||
"resourceType": "file",
|
||||
"resourceId": "/documents/report.pdf",
|
||||
"metadata": {
|
||||
"size": 1048576
|
||||
},
|
||||
"createdAt": "2026-01-13T19:00:00Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 50,
|
||||
"total": 150
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All errors follow this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "Human-readable error message",
|
||||
"details": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
| Code | HTTP Status | Description |
|
||||
|------|-------------|-------------|
|
||||
| `UNAUTHORIZED` | 401 | Missing or invalid token |
|
||||
| `FORBIDDEN` | 403 | Insufficient permissions |
|
||||
| `NOT_FOUND` | 404 | Resource not found |
|
||||
| `VALIDATION_ERROR` | 400 | Invalid request data |
|
||||
| `CONFLICT` | 409 | Resource already exists |
|
||||
| `INTERNAL_ERROR` | 500 | Server error |
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
- **Authentication endpoints**: 10 requests/minute
|
||||
- **API endpoints**: 100 requests/minute
|
||||
- **File uploads**: 50 requests/hour
|
||||
|
||||
Rate limit headers:
|
||||
```
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 95
|
||||
X-RateLimit-Reset: 1705172400
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhooks (Future)
|
||||
|
||||
Planned webhook events:
|
||||
- `user.created`
|
||||
- `user.deleted`
|
||||
- `file.uploaded`
|
||||
- `file.deleted`
|
||||
- `org.member.added`
|
||||
- `org.member.removed`
|
||||
313
docs/ARCHITECTURE.md
Normal file
313
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# b0esche.cloud Architecture
|
||||
|
||||
## System Overview
|
||||
|
||||
b0esche.cloud is a self-hosted cloud storage platform inspired by Google Workspace, built with a modern microservices-style architecture.
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Internet │
|
||||
└─────────────────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Traefik Reverse Proxy │
|
||||
│ (SSL Termination, Routing, Load Balancing) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ www.* │ │ go.* │ │ storage.* │ │ of.* │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||||
└─────────┼────────────────┼────────────────┼────────────────┼────────────────────────┘
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Flutter Web │ │ Go Backend │ │ Nextcloud │ │ Collabora │
|
||||
│ (Nginx) │ │ (API) │ │ (Storage) │ │ (Office) │
|
||||
└──────────────┘ └──────┬───────┘ └──────────────┘ └──────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ PostgreSQL │
|
||||
│ (Database) │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### 1. Flutter Web Frontend (`b0esche_cloud/`)
|
||||
|
||||
The user-facing web application built with Flutter.
|
||||
|
||||
**Technology Stack:**
|
||||
- Flutter 3.x with Dart
|
||||
- BLoC pattern for state management
|
||||
- Material Design 3 theming
|
||||
|
||||
**Key Modules:**
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `blocs/` | Business logic components (auth, files, orgs) |
|
||||
| `models/` | Data models (User, File, Organization) |
|
||||
| `pages/` | UI screens (Home, Files, Settings, Admin) |
|
||||
| `repositories/` | Data access layer |
|
||||
| `services/` | API client, WebAuthn service |
|
||||
| `widgets/` | Reusable UI components |
|
||||
|
||||
**State Management Flow:**
|
||||
```
|
||||
User Action → BLoC Event → BLoC Logic → State Update → UI Rebuild
|
||||
↓
|
||||
Repository
|
||||
↓
|
||||
API Service
|
||||
↓
|
||||
Go Backend
|
||||
```
|
||||
|
||||
### 2. Go Backend (`go_cloud/`)
|
||||
|
||||
The API server handling business logic, authentication, and service orchestration.
|
||||
|
||||
**Technology Stack:**
|
||||
- Go 1.21+
|
||||
- Chi Router for HTTP routing
|
||||
- sqlx for database access
|
||||
- go-webauthn for passkey authentication
|
||||
|
||||
**Key Packages:**
|
||||
| Package | Purpose |
|
||||
|---------|---------|
|
||||
| `internal/auth/` | Authentication (OIDC, Passkeys, Sessions) |
|
||||
| `internal/files/` | File metadata and operations |
|
||||
| `internal/org/` | Organization and membership management |
|
||||
| `internal/storage/` | Nextcloud/WebDAV integration |
|
||||
| `internal/http/` | HTTP handlers and WOPI endpoints |
|
||||
| `internal/middleware/` | Auth, logging, CORS middleware |
|
||||
| `pkg/jwt/` | JWT token utilities |
|
||||
|
||||
**Request Flow:**
|
||||
```
|
||||
HTTP Request → Traefik → Chi Router → Middleware → Handler → Service → Response
|
||||
↓
|
||||
Database/Storage
|
||||
```
|
||||
|
||||
### 3. PostgreSQL Database
|
||||
|
||||
Stores application metadata (not files).
|
||||
|
||||
**Key Tables:**
|
||||
- `users` - User accounts and profiles
|
||||
- `roles` - Permission roles (user, admin, superadmin)
|
||||
- `passkeys` - WebAuthn credentials
|
||||
- `organizations` - Org definitions
|
||||
- `org_memberships` - User-org relationships
|
||||
- `activities` - Audit log
|
||||
|
||||
**Schema Relationships:**
|
||||
```
|
||||
users ──┬── passkeys (1:N)
|
||||
├── org_memberships (N:M) ── organizations
|
||||
├── recovery_codes (1:N)
|
||||
└── activities (1:N)
|
||||
```
|
||||
|
||||
### 4. Nextcloud (Storage)
|
||||
|
||||
File storage backend and OIDC provider.
|
||||
|
||||
**Responsibilities:**
|
||||
- File storage via WebDAV
|
||||
- User authentication (OIDC)
|
||||
- File sharing capabilities
|
||||
- Version control
|
||||
|
||||
**Integration Points:**
|
||||
- WebDAV API for file operations
|
||||
- OIDC for authentication
|
||||
- User provisioning sync
|
||||
|
||||
### 5. Collabora Online (Office)
|
||||
|
||||
Document editing service for Office files.
|
||||
|
||||
**Supported Formats:**
|
||||
- Documents: DOCX, ODT, RTF
|
||||
- Spreadsheets: XLSX, ODS, CSV
|
||||
- Presentations: PPTX, ODP
|
||||
|
||||
**Integration:**
|
||||
- WOPI protocol for document access
|
||||
- Embedded iframe in Flutter app
|
||||
|
||||
### 6. Traefik (Reverse Proxy)
|
||||
|
||||
SSL termination and request routing.
|
||||
|
||||
**Features:**
|
||||
- Automatic SSL via Let's Encrypt (DNS-01 challenge)
|
||||
- Dynamic service discovery
|
||||
- Load balancing
|
||||
- Request routing based on hostname
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```
|
||||
┌────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Client │───▶│ Frontend │───▶│ Backend │───▶│ Database │
|
||||
└────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
│ │
|
||||
│ 1. Username + Passkey │
|
||||
│─────────────────────────────▶│
|
||||
│ │
|
||||
│ 2. WebAuthn Challenge │
|
||||
│◀─────────────────────────────│
|
||||
│ │
|
||||
│ 3. Signed Challenge │
|
||||
│─────────────────────────────▶│
|
||||
│ │ 4. Verify Signature
|
||||
│ │ 5. Create Session
|
||||
│ 6. JWT Token │
|
||||
│◀─────────────────────────────│
|
||||
```
|
||||
|
||||
### File Upload Flow
|
||||
|
||||
```
|
||||
┌────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐
|
||||
│ Client │───▶│ Frontend │───▶│ Backend │───▶│ Nextcloud │
|
||||
└────────┘ └──────────┘ └──────────┘ └───────────┘
|
||||
│ │ │
|
||||
│ 1. Select File │ │
|
||||
│─────────────────────────────▶│ │
|
||||
│ │ │
|
||||
│ │ 2. WebDAV PUT │
|
||||
│ │───────────────▶│
|
||||
│ │ │
|
||||
│ │ 3. Success │
|
||||
│ │◀───────────────│
|
||||
│ │ │
|
||||
│ │ 4. Save Metadata
|
||||
│ │ (PostgreSQL) │
|
||||
│ 5. Confirmation │ │
|
||||
│◀─────────────────────────────│ │
|
||||
```
|
||||
|
||||
## Network Architecture
|
||||
|
||||
### Docker Networks
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ proxy (172.20.0.0/16) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ traefik │ │ flutter │ │ go │ │nextcloud│ │
|
||||
│ │ │ │ web │ │ backend │ │ │ │
|
||||
│ └─────────┘ └─────────┘ └────┬────┘ └─────────┘ │
|
||||
└───────────────────────────────┼─────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────────────┼─────────────────────────────┐
|
||||
│ backend (internal) │
|
||||
│ ┌────┴────┐ │
|
||||
│ │postgres │ │
|
||||
│ └─────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Port Mapping
|
||||
|
||||
| Service | Internal Port | External |
|
||||
|---------|---------------|----------|
|
||||
| Traefik | 80, 443 | Exposed |
|
||||
| Flutter Web | 80 | Via Traefik |
|
||||
| Go Backend | 8080 | Via Traefik |
|
||||
| PostgreSQL | 5432 | Internal only |
|
||||
| Nextcloud | 80 | Via Traefik |
|
||||
| Collabora | 9980 | Via Traefik |
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication Layers
|
||||
|
||||
1. **Primary**: WebAuthn Passkeys (FIDO2)
|
||||
2. **Fallback**: Optional password authentication
|
||||
3. **Legacy**: OIDC via Nextcloud (deprecated)
|
||||
4. **Recovery**: One-time recovery codes
|
||||
|
||||
### Authorization Model
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Role Hierarchy │
|
||||
│ │
|
||||
│ superadmin (Level 3) │
|
||||
│ ├── All system access │
|
||||
│ ├── User management │
|
||||
│ └── Can manage admins │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ admin (Level 2) │
|
||||
│ ├── Organization management │
|
||||
│ ├── User role management (within orgs) │
|
||||
│ └── Activity monitoring │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ user (Level 1) │
|
||||
│ ├── Personal file management │
|
||||
│ ├── Organization membership │
|
||||
│ └── Basic settings │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Organization Roles
|
||||
|
||||
Within each organization:
|
||||
- **Owner**: Full control, can delete org
|
||||
- **Admin**: Can manage members and files
|
||||
- **Member**: Read/write access to shared files
|
||||
|
||||
## Scalability Considerations
|
||||
|
||||
### Current Architecture (Single Server)
|
||||
|
||||
- All services on one VPS
|
||||
- Suitable for small teams (< 100 users)
|
||||
- Simple deployment and maintenance
|
||||
|
||||
### Future Scaling Options
|
||||
|
||||
1. **Database**: Read replicas, connection pooling
|
||||
2. **Storage**: S3-compatible backends, CDN for static assets
|
||||
3. **Backend**: Horizontal scaling with load balancer
|
||||
4. **Frontend**: CDN distribution, edge caching
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
### Logging
|
||||
|
||||
- **Traefik**: Access logs, error logs
|
||||
- **Go Backend**: Structured JSON logs
|
||||
- **PostgreSQL**: Query logs, slow query analysis
|
||||
- **Docker**: Container logs via `docker logs`
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Backend health
|
||||
curl https://go.b0esche.cloud/health
|
||||
|
||||
# Frontend availability
|
||||
curl -I https://www.b0esche.cloud
|
||||
|
||||
# Database connectivity
|
||||
docker exec go-postgres pg_isready
|
||||
```
|
||||
|
||||
### Metrics (Future)
|
||||
|
||||
- Prometheus for metrics collection
|
||||
- Grafana for visualization
|
||||
- AlertManager for alerting
|
||||
579
docs/DEPLOYMENT.md
Normal file
579
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,579 @@
|
||||
# b0esche.cloud Deployment Guide
|
||||
|
||||
This guide covers production deployment, server configuration, and operations.
|
||||
|
||||
## Production Architecture
|
||||
|
||||
### Server Overview
|
||||
|
||||
| Component | Domain | Port | Container |
|
||||
|-----------|--------|------|-----------|
|
||||
| Flutter Web | www.b0esche.cloud | 80 | `flutter-web` |
|
||||
| Go Backend | go.b0esche.cloud | 8080 | `go-backend` |
|
||||
| PostgreSQL | internal | 5432 | `go-postgres` |
|
||||
| Nextcloud | storage.b0esche.cloud | 80 | `nextcloud` |
|
||||
| Collabora | of.b0esche.cloud | 9980 | `collabora` |
|
||||
| Traefik | - | 80, 443 | `traefik` |
|
||||
|
||||
### Server Directory Structure
|
||||
|
||||
```
|
||||
/opt/
|
||||
├── traefik/
|
||||
│ ├── docker-compose.yml # Traefik + Nextcloud + Collabora
|
||||
│ ├── traefik.yml # Static configuration
|
||||
│ ├── .env # DNS credentials
|
||||
│ └── acme/ # SSL certificates
|
||||
├── go/
|
||||
│ ├── docker-compose.yml # Go backend + PostgreSQL
|
||||
│ ├── .env.production # Production environment
|
||||
│ └── data/
|
||||
│ └── postgres/
|
||||
│ └── backend/
|
||||
│ └── go_cloud/ # Backend source code
|
||||
├── flutter/
|
||||
│ ├── docker-compose.yml # Nginx for Flutter
|
||||
│ ├── nginx.conf # Nginx configuration
|
||||
│ └── web/ # Built Flutter files
|
||||
├── scripts/
|
||||
│ ├── auto-deploy.sh # Daily auto-deployment
|
||||
│ ├── deploy-now.sh # Manual deployment trigger
|
||||
│ ├── backup.sh # Backup script
|
||||
│ ├── monitor.sh # Health monitoring
|
||||
│ └── webhook-server.py # GitLab webhook receiver
|
||||
└── auto-deploy/
|
||||
└── b0esche_cloud_rollout/ # Deployment workspace
|
||||
```
|
||||
|
||||
## Deployment Methods
|
||||
|
||||
### 1. Automatic Deployment (Recommended)
|
||||
|
||||
Deployments run automatically at 3 AM daily via cron:
|
||||
|
||||
```cron
|
||||
0 3 * * * /opt/scripts/auto-deploy.sh >> /var/log/auto-deploy.log 2>&1
|
||||
```
|
||||
|
||||
The auto-deploy script:
|
||||
1. Pulls latest changes from GitLab
|
||||
2. Builds Flutter web app
|
||||
3. Rebuilds Go backend Docker image
|
||||
4. Restarts services
|
||||
5. Validates health checks
|
||||
|
||||
### 2. Manual Deployment (Immediate)
|
||||
|
||||
Trigger an immediate deployment:
|
||||
|
||||
```bash
|
||||
# From local machine
|
||||
ssh b0esche-cloud '/opt/scripts/deploy-now.sh'
|
||||
|
||||
# Or directly on server
|
||||
/opt/scripts/deploy-now.sh
|
||||
```
|
||||
|
||||
### 3. GitLab Webhook (On Push)
|
||||
|
||||
The webhook server listens for push events:
|
||||
|
||||
```bash
|
||||
# Start webhook server (runs as systemd service)
|
||||
systemctl start webhook-server
|
||||
|
||||
# Check webhook logs
|
||||
journalctl -u webhook-server -f
|
||||
```
|
||||
|
||||
## Service Management
|
||||
|
||||
### Starting All Services
|
||||
|
||||
```bash
|
||||
# Start in order (dependencies first)
|
||||
cd /opt/traefik && docker-compose up -d
|
||||
cd /opt/go && docker-compose up -d
|
||||
cd /opt/flutter && docker-compose up -d
|
||||
```
|
||||
|
||||
### Stopping All Services
|
||||
|
||||
```bash
|
||||
cd /opt/flutter && docker-compose down
|
||||
cd /opt/go && docker-compose down
|
||||
cd /opt/traefik && docker-compose down
|
||||
```
|
||||
|
||||
### Restarting Individual Services
|
||||
|
||||
```bash
|
||||
# Restart Go backend
|
||||
cd /opt/go && docker-compose restart go-backend
|
||||
|
||||
# Restart Flutter frontend
|
||||
cd /opt/flutter && docker-compose restart flutter-web
|
||||
|
||||
# Restart Traefik (caution: brief SSL interruption)
|
||||
cd /opt/traefik && docker-compose restart traefik
|
||||
```
|
||||
|
||||
### Viewing Logs
|
||||
|
||||
```bash
|
||||
# Follow Go backend logs
|
||||
docker logs -f go-backend
|
||||
|
||||
# Follow Flutter/Nginx logs
|
||||
docker logs -f flutter-web
|
||||
|
||||
# Follow Traefik logs
|
||||
docker logs -f traefik
|
||||
|
||||
# All logs with timestamps
|
||||
docker logs -f --timestamps go-backend
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### Traefik Configuration
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v2.10
|
||||
container_name: traefik
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./traefik.yml:/etc/traefik/traefik.yml:ro
|
||||
- ./acme:/etc/traefik/acme
|
||||
networks:
|
||||
- proxy
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
**traefik.yml:**
|
||||
```yaml
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: ":443"
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: admin@b0esche.cloud
|
||||
storage: /etc/traefik/acme/acme.json
|
||||
dnsChallenge:
|
||||
provider: bunny
|
||||
delayBeforeCheck: 30
|
||||
|
||||
providers:
|
||||
docker:
|
||||
exposedByDefault: false
|
||||
```
|
||||
|
||||
### Go Backend Configuration
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: go-postgres
|
||||
environment:
|
||||
POSTGRES_USER: go_backend
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: go_backend
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
|
||||
go-backend:
|
||||
build:
|
||||
context: ./data/postgres/backend/go_cloud
|
||||
dockerfile: Dockerfile
|
||||
container_name: go-backend
|
||||
env_file: .env.production
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.go.rule=Host(`go.b0esche.cloud`)"
|
||||
- "traefik.http.routers.go.tls.certresolver=letsencrypt"
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- proxy
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
backend:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
### Flutter/Nginx Configuration
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
flutter-web:
|
||||
image: nginx:alpine
|
||||
container_name: flutter-web
|
||||
volumes:
|
||||
- ./web:/usr/share/nginx/html:ro
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.flutter.rule=Host(`www.b0esche.cloud`)"
|
||||
- "traefik.http.routers.flutter.tls.certresolver=letsencrypt"
|
||||
networks:
|
||||
- proxy
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
**nginx.conf:**
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name www.b0esche.cloud;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Flutter web app routing
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript;
|
||||
}
|
||||
```
|
||||
|
||||
## Database Operations
|
||||
|
||||
### Running Migrations
|
||||
|
||||
```bash
|
||||
# Enter backend container
|
||||
docker exec -it go-backend sh
|
||||
|
||||
# Run migrations
|
||||
./api migrate up
|
||||
|
||||
# Or from host
|
||||
docker exec go-backend ./api migrate up
|
||||
```
|
||||
|
||||
### Database Backup
|
||||
|
||||
```bash
|
||||
# Manual backup
|
||||
docker exec go-postgres pg_dump -U go_backend -Fc go_backend > backup.sqlc
|
||||
|
||||
# Restore from backup
|
||||
docker exec -i go-postgres pg_restore -U go_backend -d go_backend < backup.sqlc
|
||||
```
|
||||
|
||||
### Connecting to Database
|
||||
|
||||
```bash
|
||||
# Via docker exec
|
||||
docker exec -it go-postgres psql -U go_backend -d go_backend
|
||||
|
||||
# Common queries
|
||||
\dt # List tables
|
||||
\d users # Describe table
|
||||
SELECT count(*) FROM users; # Count users
|
||||
```
|
||||
|
||||
## SSL Certificate Management
|
||||
|
||||
### Certificate Status
|
||||
|
||||
```bash
|
||||
# Check certificate expiry
|
||||
docker exec traefik cat /etc/traefik/acme/acme.json | jq '.letsencrypt.Certificates[].certificate.NotAfter'
|
||||
|
||||
# Force certificate renewal
|
||||
docker restart traefik
|
||||
```
|
||||
|
||||
### Manual Certificate Operations
|
||||
|
||||
```bash
|
||||
# Backup certificates
|
||||
cp -r /opt/traefik/acme /opt/traefik/acme.backup
|
||||
|
||||
# View certificate details
|
||||
openssl s_client -connect www.b0esche.cloud:443 -servername www.b0esche.cloud </dev/null 2>/dev/null | openssl x509 -noout -dates
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Check all services
|
||||
/opt/scripts/monitor.sh
|
||||
|
||||
# Manual health checks
|
||||
curl -s https://go.b0esche.cloud/health
|
||||
curl -s -o /dev/null -w "%{http_code}" https://www.b0esche.cloud
|
||||
curl -s -o /dev/null -w "%{http_code}" https://storage.b0esche.cloud
|
||||
```
|
||||
|
||||
### Container Status
|
||||
|
||||
```bash
|
||||
# All containers
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
# Resource usage
|
||||
docker stats --no-stream
|
||||
|
||||
# Container health
|
||||
docker inspect --format='{{.State.Health.Status}}' go-backend
|
||||
```
|
||||
|
||||
### Disk Usage
|
||||
|
||||
```bash
|
||||
# Docker disk usage
|
||||
docker system df
|
||||
|
||||
# PostgreSQL data size
|
||||
du -sh /opt/go/data/postgres
|
||||
|
||||
# Log sizes
|
||||
du -sh /var/lib/docker/containers/*/
|
||||
```
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Automated Backups
|
||||
|
||||
Backups run daily via cron:
|
||||
```cron
|
||||
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
|
||||
```
|
||||
|
||||
### Backup Contents
|
||||
|
||||
1. **PostgreSQL database** (pg_dump)
|
||||
2. **Nextcloud database** (mysqldump)
|
||||
3. **Traefik certificates** (/opt/traefik/acme)
|
||||
4. **Configuration files** (.env, docker-compose.yml)
|
||||
5. **Nextcloud data volume**
|
||||
|
||||
### Backup Retention
|
||||
|
||||
- Keep backups for 30 days
|
||||
- Stored in `/opt/backups/b0esche_cloud/`
|
||||
- Compressed as `.tar.gz`
|
||||
|
||||
### Manual Backup
|
||||
|
||||
```bash
|
||||
# Run backup now
|
||||
/opt/scripts/backup.sh
|
||||
|
||||
# List backups
|
||||
ls -lh /opt/backups/b0esche_cloud/
|
||||
```
|
||||
|
||||
### Restore Procedure
|
||||
|
||||
```bash
|
||||
# 1. Stop services
|
||||
cd /opt/go && docker-compose down
|
||||
cd /opt/flutter && docker-compose down
|
||||
|
||||
# 2. Extract backup
|
||||
cd /opt/backups/b0esche_cloud
|
||||
tar -xzf 20260113_020000.tar.gz
|
||||
|
||||
# 3. Restore database
|
||||
docker exec -i go-postgres pg_restore -U go_backend -d go_backend < go_backend.sqlc
|
||||
|
||||
# 4. Restore configurations
|
||||
cp .env.production /opt/go/
|
||||
cp go-docker-compose.yml /opt/go/docker-compose.yml
|
||||
|
||||
# 5. Restart services
|
||||
cd /opt/go && docker-compose up -d
|
||||
cd /opt/flutter && docker-compose up -d
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Service won't start
|
||||
|
||||
```bash
|
||||
# Check logs for errors
|
||||
docker logs go-backend --tail 50
|
||||
|
||||
# Check container status
|
||||
docker inspect go-backend | jq '.[0].State'
|
||||
|
||||
# Check port conflicts
|
||||
netstat -tlnp | grep -E '80|443|8080'
|
||||
```
|
||||
|
||||
#### Database connection issues
|
||||
|
||||
```bash
|
||||
# Test database connectivity
|
||||
docker exec go-backend ping -c 3 postgres
|
||||
|
||||
# Check PostgreSQL logs
|
||||
docker logs go-postgres --tail 50
|
||||
|
||||
# Verify credentials
|
||||
docker exec go-postgres psql -U go_backend -c "SELECT 1"
|
||||
```
|
||||
|
||||
#### SSL certificate errors
|
||||
|
||||
```bash
|
||||
# Check certificate status
|
||||
curl -vI https://www.b0esche.cloud 2>&1 | grep -A 5 "Server certificate"
|
||||
|
||||
# Force renewal
|
||||
docker restart traefik
|
||||
sleep 60
|
||||
curl -vI https://www.b0esche.cloud
|
||||
```
|
||||
|
||||
#### Out of disk space
|
||||
|
||||
```bash
|
||||
# Check disk usage
|
||||
df -h
|
||||
|
||||
# Clean Docker resources
|
||||
docker system prune -a --volumes
|
||||
|
||||
# Clean old backups
|
||||
find /opt/backups -mtime +30 -delete
|
||||
|
||||
# Clean old logs
|
||||
truncate -s 0 /var/log/auto-deploy.log
|
||||
```
|
||||
|
||||
### Emergency Procedures
|
||||
|
||||
#### Rollback Deployment
|
||||
|
||||
```bash
|
||||
# 1. Stop current services
|
||||
cd /opt/go && docker-compose down
|
||||
cd /opt/flutter && docker-compose down
|
||||
|
||||
# 2. Checkout previous version
|
||||
cd /opt/auto-deploy/b0esche_cloud_rollout
|
||||
git log --oneline -10 # Find last working commit
|
||||
git checkout <commit-hash>
|
||||
|
||||
# 3. Redeploy
|
||||
/opt/scripts/auto-deploy.sh
|
||||
```
|
||||
|
||||
#### Database Recovery
|
||||
|
||||
```bash
|
||||
# Find latest backup
|
||||
ls -lt /opt/backups/b0esche_cloud/ | head -5
|
||||
|
||||
# Restore (see Restore Procedure above)
|
||||
```
|
||||
|
||||
#### Full System Recovery
|
||||
|
||||
1. Provision new server
|
||||
2. Install Docker
|
||||
3. Copy `/opt` from backup
|
||||
4. Start services in order
|
||||
5. Restore database from backup
|
||||
6. Verify health checks
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] All services behind Traefik (no direct port exposure)
|
||||
- [ ] SSL certificates valid and auto-renewing
|
||||
- [ ] Database not accessible from internet
|
||||
- [ ] Strong passwords in `.env.production`
|
||||
- [ ] Regular backups verified
|
||||
- [ ] Firewall configured (only 80, 443, 22 open)
|
||||
- [ ] SSH key authentication only
|
||||
- [ ] Auto-deploy logs monitored
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```sql
|
||||
-- Check slow queries
|
||||
SELECT * FROM pg_stat_activity WHERE state = 'active';
|
||||
|
||||
-- Analyze tables
|
||||
ANALYZE;
|
||||
|
||||
-- Vacuum
|
||||
VACUUM ANALYZE;
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
# Add to nginx.conf for better performance
|
||||
worker_connections 1024;
|
||||
keepalive_timeout 65;
|
||||
gzip_comp_level 6;
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Limit container resources
|
||||
docker update --memory="512m" --cpus="1" go-backend
|
||||
|
||||
# Clean up unused resources
|
||||
docker system prune -f
|
||||
```
|
||||
643
docs/DEVELOPMENT.md
Normal file
643
docs/DEVELOPMENT.md
Normal file
@@ -0,0 +1,643 @@
|
||||
# b0esche.cloud Development Guide
|
||||
|
||||
This guide covers local development setup, coding conventions, and contribution guidelines.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Software
|
||||
|
||||
| Software | Version | Installation |
|
||||
|----------|---------|--------------|
|
||||
| Go | 1.21+ | `brew install go` |
|
||||
| Flutter | 3.10+ | [flutter.dev](https://flutter.dev/docs/get-started/install) |
|
||||
| Docker | 24+ | [docker.com](https://docker.com) |
|
||||
| PostgreSQL | 15+ | `brew install postgresql@15` or Docker |
|
||||
| Git | 2.x | `brew install git` |
|
||||
|
||||
### Recommended Tools
|
||||
|
||||
- **VS Code** with extensions:
|
||||
- Go
|
||||
- Flutter
|
||||
- Dart
|
||||
- Docker
|
||||
- GitLens
|
||||
- **TablePlus** or **DBeaver** for database management
|
||||
- **Postman** or **Bruno** for API testing
|
||||
|
||||
## Project Setup
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://lab.b0esche.cloud/b0esche/b0esche_cloud.git
|
||||
cd b0esche_cloud
|
||||
```
|
||||
|
||||
### 2. Backend Setup
|
||||
|
||||
```bash
|
||||
cd go_cloud
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env with your local settings
|
||||
# Key variables to set:
|
||||
# - DATABASE_URL=postgres://user:pass@localhost:5432/b0esche_dev?sslmode=disable
|
||||
# - JWT_SECRET=your-dev-secret
|
||||
# - WEBAUTHN_RP_ID=localhost
|
||||
# - WEBAUTHN_RP_ORIGIN=http://localhost:8080
|
||||
```
|
||||
|
||||
#### Start PostgreSQL
|
||||
|
||||
**Option A: Using Docker (Recommended)**
|
||||
```bash
|
||||
docker run -d \
|
||||
--name b0esche-postgres \
|
||||
-e POSTGRES_USER=b0esche \
|
||||
-e POSTGRES_PASSWORD=devpassword \
|
||||
-e POSTGRES_DB=b0esche_dev \
|
||||
-p 5432:5432 \
|
||||
postgres:15-alpine
|
||||
```
|
||||
|
||||
**Option B: Using local PostgreSQL**
|
||||
```bash
|
||||
createdb b0esche_dev
|
||||
```
|
||||
|
||||
#### Run Migrations
|
||||
|
||||
```bash
|
||||
# Install goose
|
||||
go install github.com/pressly/goose/v3/cmd/goose@latest
|
||||
|
||||
# Run migrations
|
||||
goose -dir migrations postgres "$DATABASE_URL" up
|
||||
```
|
||||
|
||||
#### Start Backend
|
||||
|
||||
```bash
|
||||
# Development mode with hot reload
|
||||
go run ./cmd/api
|
||||
|
||||
# Or build and run
|
||||
go build -o bin/api ./cmd/api
|
||||
./bin/api
|
||||
```
|
||||
|
||||
The backend will be available at `http://localhost:8080`.
|
||||
|
||||
### 3. Frontend Setup
|
||||
|
||||
```bash
|
||||
cd b0esche_cloud
|
||||
|
||||
# Get dependencies
|
||||
flutter pub get
|
||||
|
||||
# Run in Chrome (recommended for web development)
|
||||
flutter run -d chrome
|
||||
|
||||
# Or run with specific port
|
||||
flutter run -d chrome --web-port=3000
|
||||
```
|
||||
|
||||
The frontend will be available at `http://localhost:3000` (or the port shown).
|
||||
|
||||
### 4. Quick Start Script
|
||||
|
||||
Use the provided development script:
|
||||
|
||||
```bash
|
||||
./scripts/dev-all.sh
|
||||
```
|
||||
|
||||
This starts all services in the correct order.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Backend (`go_cloud/`)
|
||||
|
||||
```
|
||||
go_cloud/
|
||||
├── cmd/
|
||||
│ └── api/
|
||||
│ └── main.go # Application entry point
|
||||
├── internal/
|
||||
│ ├── auth/
|
||||
│ │ ├── auth.go # Authentication service
|
||||
│ │ ├── passkey.go # WebAuthn implementation
|
||||
│ │ └── auth_test.go # Tests
|
||||
│ ├── config/
|
||||
│ │ └── config.go # Configuration loading
|
||||
│ ├── database/
|
||||
│ │ └── database.go # Database connection
|
||||
│ ├── files/
|
||||
│ │ └── files.go # File operations
|
||||
│ ├── http/
|
||||
│ │ ├── routes.go # Route definitions
|
||||
│ │ ├── server.go # HTTP server setup
|
||||
│ │ └── wopi_handlers.go # WOPI protocol handlers
|
||||
│ ├── middleware/
|
||||
│ │ └── middleware.go # HTTP middleware
|
||||
│ ├── models/
|
||||
│ │ └── *.go # Data models
|
||||
│ ├── org/
|
||||
│ │ └── org.go # Organization logic
|
||||
│ ├── storage/
|
||||
│ │ ├── nextcloud.go # Nextcloud integration
|
||||
│ │ └── webdav.go # WebDAV client
|
||||
│ └── ...
|
||||
├── migrations/
|
||||
│ ├── 0001_initial.sql
|
||||
│ ├── 0002_passkeys.sql
|
||||
│ └── ...
|
||||
├── pkg/
|
||||
│ └── jwt/
|
||||
│ └── jwt.go # JWT utilities
|
||||
├── .env.example
|
||||
├── Dockerfile
|
||||
├── go.mod
|
||||
└── Makefile
|
||||
```
|
||||
|
||||
### Frontend (`b0esche_cloud/`)
|
||||
|
||||
```
|
||||
b0esche_cloud/
|
||||
├── lib/
|
||||
│ ├── main.dart # App entry point
|
||||
│ ├── injection.dart # Dependency injection
|
||||
│ ├── blocs/
|
||||
│ │ ├── auth/
|
||||
│ │ │ ├── auth_bloc.dart
|
||||
│ │ │ ├── auth_event.dart
|
||||
│ │ │ └── auth_state.dart
|
||||
│ │ ├── files/
|
||||
│ │ └── org/
|
||||
│ ├── models/
|
||||
│ │ ├── user.dart
|
||||
│ │ ├── file.dart
|
||||
│ │ └── organization.dart
|
||||
│ ├── pages/
|
||||
│ │ ├── home_page.dart
|
||||
│ │ ├── files_page.dart
|
||||
│ │ ├── settings_page.dart
|
||||
│ │ └── admin/
|
||||
│ ├── repositories/
|
||||
│ │ ├── auth_repository.dart
|
||||
│ │ └── file_repository.dart
|
||||
│ ├── services/
|
||||
│ │ ├── api_client.dart
|
||||
│ │ └── webauthn_service.dart
|
||||
│ ├── theme/
|
||||
│ │ └── app_theme.dart
|
||||
│ └── widgets/
|
||||
│ ├── file_list.dart
|
||||
│ └── ...
|
||||
├── web/
|
||||
│ └── index.html
|
||||
├── pubspec.yaml
|
||||
└── analysis_options.yaml
|
||||
```
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
### Go Backend
|
||||
|
||||
#### Code Style
|
||||
- Follow [Effective Go](https://golang.org/doc/effective_go)
|
||||
- Use `gofmt` for formatting
|
||||
- Use `golint` and `go vet` for linting
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
gofmt -w .
|
||||
|
||||
# Lint
|
||||
golint ./...
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
#### Naming Conventions
|
||||
- **Packages**: lowercase, single word (`auth`, `files`)
|
||||
- **Exported functions**: PascalCase (`CreateUser`)
|
||||
- **Private functions**: camelCase (`validateToken`)
|
||||
- **Constants**: PascalCase (`DefaultTimeout`)
|
||||
|
||||
#### Error Handling
|
||||
```go
|
||||
// Always handle errors explicitly
|
||||
user, err := s.GetUser(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
// Use custom error types for API errors
|
||||
type APIError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Project Patterns
|
||||
```go
|
||||
// Service pattern
|
||||
type AuthService struct {
|
||||
db *sqlx.DB
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewAuthService(db *sqlx.DB, config *config.Config) *AuthService {
|
||||
return &AuthService{db: db, config: config}
|
||||
}
|
||||
|
||||
// Handler pattern
|
||||
func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse request
|
||||
// Call service
|
||||
// Return response
|
||||
}
|
||||
```
|
||||
|
||||
### Flutter Frontend
|
||||
|
||||
#### Code Style
|
||||
- Follow [Effective Dart](https://dart.dev/guides/language/effective-dart)
|
||||
- Use `dart format` for formatting
|
||||
- Use `dart analyze` for linting
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
dart format .
|
||||
|
||||
# Analyze
|
||||
dart analyze
|
||||
flutter analyze
|
||||
```
|
||||
|
||||
#### Naming Conventions
|
||||
- **Classes**: PascalCase (`AuthBloc`)
|
||||
- **Files**: snake_case (`auth_bloc.dart`)
|
||||
- **Variables/Functions**: camelCase (`getUserName`)
|
||||
- **Constants**: camelCase or SCREAMING_CAPS
|
||||
|
||||
#### BLoC Pattern
|
||||
```dart
|
||||
// Events
|
||||
abstract class AuthEvent {}
|
||||
class LoginRequested extends AuthEvent {
|
||||
final String username;
|
||||
LoginRequested(this.username);
|
||||
}
|
||||
|
||||
// States
|
||||
abstract class AuthState {}
|
||||
class AuthInitial extends AuthState {}
|
||||
class AuthLoading extends AuthState {}
|
||||
class AuthAuthenticated extends AuthState {
|
||||
final User user;
|
||||
AuthAuthenticated(this.user);
|
||||
}
|
||||
class AuthError extends AuthState {
|
||||
final String message;
|
||||
AuthError(this.message);
|
||||
}
|
||||
|
||||
// BLoC
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
AuthBloc() : super(AuthInitial()) {
|
||||
on<LoginRequested>(_onLoginRequested);
|
||||
}
|
||||
|
||||
Future<void> _onLoginRequested(
|
||||
LoginRequested event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
emit(AuthLoading());
|
||||
try {
|
||||
final user = await _authRepository.login(event.username);
|
||||
emit(AuthAuthenticated(user));
|
||||
} catch (e) {
|
||||
emit(AuthError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Widget Structure
|
||||
```dart
|
||||
class MyWidget extends StatelessWidget {
|
||||
const MyWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<MyBloc, MyState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
MyLoading() => const CircularProgressIndicator(),
|
||||
MyLoaded(:final data) => _buildContent(data),
|
||||
MyError(:final message) => Text('Error: $message'),
|
||||
_ => const SizedBox.shrink(),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Backend Tests
|
||||
|
||||
```bash
|
||||
cd go_cloud
|
||||
|
||||
# Run all tests
|
||||
go test ./...
|
||||
|
||||
# Run with coverage
|
||||
go test -cover ./...
|
||||
|
||||
# Run specific package
|
||||
go test ./internal/auth/...
|
||||
|
||||
# Verbose output
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
Example test:
|
||||
```go
|
||||
func TestAuthService_Login(t *testing.T) {
|
||||
// Setup
|
||||
db := setupTestDB(t)
|
||||
service := NewAuthService(db, testConfig)
|
||||
|
||||
// Test
|
||||
user, err := service.Login(context.Background(), "testuser")
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "testuser", user.Username)
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend Tests
|
||||
|
||||
```bash
|
||||
cd b0esche_cloud
|
||||
|
||||
# Run all tests
|
||||
flutter test
|
||||
|
||||
# Run with coverage
|
||||
flutter test --coverage
|
||||
|
||||
# Run specific test file
|
||||
flutter test test/auth_bloc_test.dart
|
||||
|
||||
# Run integration tests
|
||||
flutter test integration_test/
|
||||
```
|
||||
|
||||
Example test:
|
||||
```dart
|
||||
void main() {
|
||||
group('AuthBloc', () {
|
||||
late AuthBloc authBloc;
|
||||
late MockAuthRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockAuthRepository();
|
||||
authBloc = AuthBloc(authRepository: mockRepository);
|
||||
});
|
||||
|
||||
blocTest<AuthBloc, AuthState>(
|
||||
'emits [AuthLoading, AuthAuthenticated] on successful login',
|
||||
build: () => authBloc,
|
||||
act: (bloc) => bloc.add(LoginRequested('testuser')),
|
||||
expect: () => [
|
||||
AuthLoading(),
|
||||
isA<AuthAuthenticated>(),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Database Migrations
|
||||
|
||||
### Creating a Migration
|
||||
|
||||
```bash
|
||||
cd go_cloud
|
||||
|
||||
# Create new migration
|
||||
goose -dir migrations create add_new_table sql
|
||||
```
|
||||
|
||||
### Migration Best Practices
|
||||
|
||||
```sql
|
||||
-- migrations/0005_add_feature.sql
|
||||
|
||||
-- +goose Up
|
||||
-- Add new column with default
|
||||
ALTER TABLE users ADD COLUMN new_field TEXT DEFAULT '';
|
||||
|
||||
-- Create index for performance
|
||||
CREATE INDEX idx_users_new_field ON users(new_field);
|
||||
|
||||
-- +goose Down
|
||||
DROP INDEX IF EXISTS idx_users_new_field;
|
||||
ALTER TABLE users DROP COLUMN new_field;
|
||||
```
|
||||
|
||||
### Running Migrations
|
||||
|
||||
```bash
|
||||
# Apply all pending migrations
|
||||
goose -dir migrations postgres "$DATABASE_URL" up
|
||||
|
||||
# Rollback last migration
|
||||
goose -dir migrations postgres "$DATABASE_URL" down
|
||||
|
||||
# Check migration status
|
||||
goose -dir migrations postgres "$DATABASE_URL" status
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Backend Debugging
|
||||
|
||||
**VS Code launch.json:**
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Go Backend",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/go_cloud/cmd/api",
|
||||
"envFile": "${workspaceFolder}/go_cloud/.env"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Logging:**
|
||||
```go
|
||||
import "log"
|
||||
|
||||
log.Printf("User login attempt: %s", username)
|
||||
```
|
||||
|
||||
### Frontend Debugging
|
||||
|
||||
**Chrome DevTools:**
|
||||
- Press F12 in Chrome
|
||||
- Use the Flutter DevTools extension
|
||||
|
||||
**Debug print:**
|
||||
```dart
|
||||
debugPrint('Current state: $state');
|
||||
```
|
||||
|
||||
**VS Code launch.json:**
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter Web",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "lib/main.dart",
|
||||
"deviceId": "chrome"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Git Workflow
|
||||
|
||||
### Branch Naming
|
||||
|
||||
- `feature/description` - New features
|
||||
- `fix/description` - Bug fixes
|
||||
- `refactor/description` - Code refactoring
|
||||
- `docs/description` - Documentation updates
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Follow conventional commits:
|
||||
```
|
||||
type(scope): description
|
||||
|
||||
feat(auth): add passkey registration flow
|
||||
fix(files): correct upload progress display
|
||||
docs(readme): update deployment instructions
|
||||
refactor(api): extract common error handling
|
||||
```
|
||||
|
||||
### Pull Request Process
|
||||
|
||||
1. Create feature branch from `main`
|
||||
2. Make changes with atomic commits
|
||||
3. Run tests locally
|
||||
4. Push and create PR
|
||||
5. Wait for review
|
||||
6. Squash and merge
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Backend won't start
|
||||
```bash
|
||||
# Check if port is in use
|
||||
lsof -i :8080
|
||||
|
||||
# Check database connection
|
||||
psql $DATABASE_URL -c "SELECT 1"
|
||||
|
||||
# Check logs
|
||||
go run ./cmd/api 2>&1 | head -50
|
||||
```
|
||||
|
||||
#### Flutter build fails
|
||||
```bash
|
||||
# Clean and rebuild
|
||||
flutter clean
|
||||
flutter pub get
|
||||
flutter run -d chrome
|
||||
|
||||
# Check for dependency issues
|
||||
flutter pub deps
|
||||
```
|
||||
|
||||
#### Database migration fails
|
||||
```bash
|
||||
# Check current status
|
||||
goose -dir migrations postgres "$DATABASE_URL" status
|
||||
|
||||
# Force specific version
|
||||
goose -dir migrations postgres "$DATABASE_URL" fix
|
||||
```
|
||||
|
||||
#### WebAuthn not working locally
|
||||
- WebAuthn requires HTTPS in production
|
||||
- For localhost, use `WEBAUTHN_RP_ID=localhost`
|
||||
- Chrome allows WebAuthn on localhost without HTTPS
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
### Backend (.env)
|
||||
|
||||
```bash
|
||||
# Server
|
||||
SERVER_ADDR=:8080
|
||||
DEV_MODE=true
|
||||
|
||||
# Database
|
||||
DATABASE_URL=postgres://user:pass@localhost:5432/dbname?sslmode=disable
|
||||
|
||||
# Authentication
|
||||
JWT_SECRET=your-secret-key
|
||||
WEBAUTHN_RP_ID=localhost
|
||||
WEBAUTHN_RP_NAME=b0esche.cloud
|
||||
WEBAUTHN_RP_ORIGIN=http://localhost:8080
|
||||
|
||||
# External Services (optional for local dev)
|
||||
NEXTCLOUD_BASE_URL=https://storage.b0esche.cloud
|
||||
NEXTCLOUD_USERNAME=admin
|
||||
NEXTCLOUD_PASSWORD=password
|
||||
COLLABORA_BASE_URL=https://of.b0esche.cloud
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
API base URL is configured in `lib/services/api_client.dart`:
|
||||
```dart
|
||||
class ApiClient {
|
||||
// For development
|
||||
static const baseUrl = 'http://localhost:8080';
|
||||
|
||||
// For production (set via build args)
|
||||
// static const baseUrl = String.fromEnvironment('API_URL');
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Go Documentation](https://golang.org/doc/)
|
||||
- [Flutter Documentation](https://flutter.dev/docs)
|
||||
- [WebAuthn Guide](https://webauthn.guide/)
|
||||
- [BLoC Library](https://bloclibrary.dev/)
|
||||
- [Chi Router](https://github.com/go-chi/chi)
|
||||
Reference in New Issue
Block a user