diff --git a/CLAUDE.md b/CLAUDE.md index ee84432..a25fe94 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,8 +22,7 @@ Root `compose.yaml` uses Docker Compose's `include` directive to orchestrate mul - **links**: Linkwarden bookmark manager (PostgreSQL + Meilisearch) - **vault**: Vaultwarden password manager (SQLite) - **joplin**: Joplin Server note-taking and sync platform (PostgreSQL) -- **vert**: VERT file format converter (WebAssembly-based, stateless) -- **paint**: miniPaint web-based image editor (built from GitHub) +- **kit**: Unified toolkit with Vert file converter and miniPaint image editor (path-routed) - **jelly**: Jellyfin media server with hardware transcoding - **drop**: PairDrop peer-to-peer file sharing - **restic**: Backrest backup system with restic backend @@ -251,40 +250,40 @@ Joplin Server note-taking and synchronization platform: 4. Enter server URL: `https://joplin.pivoine.art` 5. Enter email and password created in step 1 -### Vert (vert/compose.yaml) +### Kit (kit/compose.yaml) +Unified toolkit combining file conversion and image editing: +- **Base URL**: `kit.pivoine.art` +- **Services**: + - **Vert** (`/vert`): Universal file format converter + - **Paint** (`/paint`): Web-based image editor + +#### Vert Service (`/vert`) VERT universal file format converter: -- **vert**: VERT app exposed at `vert.pivoine.art:80` - - WebAssembly-based file conversion (client-side processing) - - Supports 250+ file formats (images, audio, documents, video) - - No file size limits - - Privacy-focused: all conversions happen in the browser - - No persistent data storage required - - Protected by HTTP Basic Auth (credentials in `.env`) - - Scale-to-zero enabled via Sablier (configurable via `VERT_SABLIER_ENABLED`) - - 1-hour session duration before automatic scale-down +- WebAssembly-based file conversion (client-side processing) +- Supports 250+ file formats (images, audio, documents, video) +- No file size limits +- Privacy-focused: all conversions happen in the browser +- No persistent data storage required +- Protected by HTTP Basic Auth (credentials in `.env`) **Configuration**: -- **PUB_HOSTNAME**: `vert.pivoine.art` (public hostname) +- **PUB_HOSTNAME**: `kit.pivoine.art` (public hostname) - **PUB_ENV**: `production` (environment mode) - **PUB_DISABLE_ALL_EXTERNAL_REQUESTS**: `true` (privacy mode) -- **VERT_SABLIER_ENABLED**: `true` (enable scale-to-zero) - **AUTH_USERS**: Shared HTTP Basic Auth credentials (htpasswd format in `.env`) **Usage**: -Simply access https://vert.pivoine.art and drag/drop files to convert between formats. All processing happens in your browser using WebAssembly - no data is uploaded to the server. +Access https://kit.pivoine.art/vert and drag/drop files to convert between formats. All processing happens in your browser using WebAssembly - no data is uploaded to the server. -**Note**: VERT is stateless and doesn't require backups as no data is persisted. - -### Paint (paint/compose.yaml) +#### Paint Service (`/paint`) miniPaint web-based image editor built from GitHub: -- **paint**: miniPaint app exposed at `paint.pivoine.art:80` - - Online image editor with layer support - - Built directly from https://github.com/viliusle/miniPaint - - Supports PNG, JPG, GIF, WebP formats - - Features: layers, filters, drawing tools, text, shapes - - Client-side processing (no uploads to server) - - No persistent data storage required - - Stateless architecture +- Online image editor with layer support +- Built directly from https://github.com/viliusle/miniPaint +- Supports PNG, JPG, GIF, WebP formats +- Features: layers, filters, drawing tools, text, shapes +- Client-side processing (no uploads to server) +- No persistent data storage required +- Stateless architecture **Build Process**: - Multi-stage Docker build clones from GitHub @@ -292,9 +291,9 @@ miniPaint web-based image editor built from GitHub: - Serves static files via nginx **Usage**: -Access https://paint.pivoine.art to use the image editor. All editing happens in the browser - images are not uploaded to the server. +Access https://kit.pivoine.art/paint to use the image editor. All editing happens in the browser - images are not uploaded to the server. -**Note**: miniPaint is stateless and doesn't require backups as no data is persisted. +**Note**: Both Kit services are stateless and don't require backups as no data is persisted. ### PairDrop (drop/compose.yaml) PairDrop peer-to-peer file sharing service: diff --git a/README.md b/README.md index 6f42735..3e90e3c 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,7 @@ The **Falcon** is a state-of-the-art containerized starship, powered by Docker's | **LINKS** | *Interstellar bookmark archive* | [links.pivoine.art](https://links.pivoine.art) | | **VAULT** | *Encrypted password vault* | [vault.pivoine.art](https://vault.pivoine.art) | | **JOPLIN** | *Note-taking server & sync hub* | [joplin.pivoine.art](https://joplin.pivoine.art) | -| **VERT** | *Universal file format converter* | [vert.pivoine.art](https://vert.pivoine.art) | -| **PAINT** | *Web-based image editor* | [paint.pivoine.art](https://paint.pivoine.art) | +| **KIT** | *Toolkit: file converter & image editor* | [kit.pivoine.art](https://kit.pivoine.art) | | **JELLY** | *Media streaming server* | [jelly.pivoine.art](https://jelly.pivoine.art) | | **DROP** | *Peer-to-peer file sharing* | [drop.pivoine.art](https://drop.pivoine.art) | | **RESTIC** | *Automated backup vault system* | [restic.pivoine.art](https://restic.pivoine.art) | @@ -198,12 +197,22 @@ arty env/sync # - Videos: /mnt/hidrive/users/valknar/Videos ``` -### Image Editing (PAINT System) +### Toolkit (KIT System) ```bash -# Access miniPaint image editor -# URL: https://paint.pivoine.art +# Access unified toolkit +# Base URL: https://kit.pivoine.art +# File Converter (Vert) - /vert +# URL: https://kit.pivoine.art/vert +# Features: +# - WebAssembly-based file conversion (250+ formats) +# - Images, audio, documents, video +# - Client-side processing (no uploads) +# - No file size limits + +# Image Editor (Paint) - /paint +# URL: https://kit.pivoine.art/paint # Features: # - Browser-based image editing # - Layer support @@ -266,8 +275,7 @@ THE FALCON (falcon_network) │ ├─ Linkwarden Marks [links.pivoine.art] │ ├─ Vaultwarden Vault [vault.pivoine.art] │ ├─ Joplin Sync Server [joplin.pivoine.art] -│ ├─ Vert Converter [vert.pivoine.art] -│ ├─ miniPaint Editor [paint.pivoine.art] +│ ├─ Kit Toolkit [kit.pivoine.art/vert, kit.pivoine.art/paint] │ ├─ Jellyfin Media [jelly.pivoine.art] │ ├─ PairDrop Sharing [drop.pivoine.art] │ ├─ Backrest Backups [restic.pivoine.art] diff --git a/arty.yml b/arty.yml index e2e39c0..f04f5fa 100644 --- a/arty.yml +++ b/arty.yml @@ -110,16 +110,11 @@ envs: JOPLIN_TRAEFIK_HOST: joplin.pivoine.art JOPLIN_APP_PORT: 22300 JOPLIN_DB_NAME: joplin - # Vert - VERT_TRAEFIK_ENABLED: true - VERT_COMPOSE_PROJECT_NAME: vert - VERT_IMAGE: ghcr.io/vert-sh/vert:latest - VERT_TRAEFIK_HOST: vert.pivoine.art - VERT_SABLIER_ENABLED: true - # Paint - PAINT_TRAEFIK_ENABLED: true - PAINT_COMPOSE_PROJECT_NAME: paint - PAINT_TRAEFIK_HOST: paint.pivoine.art + # Kit (combines Vert and Paint) + KIT_TRAEFIK_ENABLED: true + KIT_COMPOSE_PROJECT_NAME: kit + KIT_TRAEFIK_HOST: kit.pivoine.art + KIT_VERT_IMAGE: ghcr.io/vert-sh/vert:latest # Jellyfin JELLY_TRAEFIK_ENABLED: true JELLY_COMPOSE_PROJECT_NAME: jelly diff --git a/compose.yaml b/compose.yaml index 0960a7b..dfdfb9b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,8 +10,7 @@ include: - links/compose.yaml - vault/compose.yaml - joplin/compose.yaml - - vert/compose.yaml - - paint/compose.yaml + - kit/compose.yaml - jelly/compose.yaml - drop/compose.yaml - restic/compose.yaml diff --git a/kit/compose.yaml b/kit/compose.yaml new file mode 100644 index 0000000..000636d --- /dev/null +++ b/kit/compose.yaml @@ -0,0 +1,68 @@ +services: + vert: + image: ${KIT_VERT_IMAGE:-ghcr.io/vert-sh/vert:latest} + container_name: ${KIT_COMPOSE_PROJECT_NAME}_vert + restart: unless-stopped + environment: + PUB_HOSTNAME: ${KIT_TRAEFIK_HOST} + PUB_ENV: production + PUB_DISABLE_ALL_EXTERNAL_REQUESTS: true + networks: + - compose_network + labels: + - 'traefik.enable=${KIT_TRAEFIK_ENABLED}' + # HTTP to HTTPS redirect for /vert path + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-vert-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-vert-web.middlewares=${KIT_COMPOSE_PROJECT_NAME}-vert-redirect-web-secure' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-vert-web.rule=Host(`${KIT_TRAEFIK_HOST}`) && PathPrefix(`/vert`)' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-vert-web.entrypoints=web' + # HTTPS router for /vert path with auth + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-vert-web-secure.rule=Host(`${KIT_TRAEFIK_HOST}`) && PathPrefix(`/vert`)' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-vert-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-vert-web-secure.entrypoints=web-secure' + # Strip /vert prefix before forwarding to container + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-vert-stripprefix.stripprefix.prefixes=/vert' + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-vert-auth.basicauth.users=${AUTH_USERS}' + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-vert-compress.compress=true' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-vert-web-secure.middlewares=${KIT_COMPOSE_PROJECT_NAME}-vert-stripprefix,${KIT_COMPOSE_PROJECT_NAME}-vert-auth,${KIT_COMPOSE_PROJECT_NAME}-vert-compress,security-headers@file' + # Service + - 'traefik.http.services.${KIT_COMPOSE_PROJECT_NAME}-vert.loadbalancer.server.port=80' + - 'traefik.docker.network=${NETWORK_NAME}' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + + paint: + build: + context: ../paint + dockerfile: Dockerfile + image: minipaint:latest + container_name: ${KIT_COMPOSE_PROJECT_NAME}_paint + restart: unless-stopped + networks: + - compose_network + labels: + - 'traefik.enable=${KIT_TRAEFIK_ENABLED}' + # HTTP to HTTPS redirect for /paint path + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-paint-redirect-web-secure.redirectscheme.scheme=https' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-paint-web.middlewares=${KIT_COMPOSE_PROJECT_NAME}-paint-redirect-web-secure' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-paint-web.rule=Host(`${KIT_TRAEFIK_HOST}`) && PathPrefix(`/paint`)' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-paint-web.entrypoints=web' + # HTTPS router for /paint path with auth + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-paint-web-secure.rule=Host(`${KIT_TRAEFIK_HOST}`) && PathPrefix(`/paint`)' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-paint-web-secure.tls.certresolver=resolver' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-paint-web-secure.entrypoints=web-secure' + # Strip /paint prefix before forwarding to container + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-paint-stripprefix.stripprefix.prefixes=/paint' + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-paint-auth.basicauth.users=${AUTH_USERS}' + - 'traefik.http.middlewares.${KIT_COMPOSE_PROJECT_NAME}-paint-compress.compress=true' + - 'traefik.http.routers.${KIT_COMPOSE_PROJECT_NAME}-paint-web-secure.middlewares=${KIT_COMPOSE_PROJECT_NAME}-paint-stripprefix,${KIT_COMPOSE_PROJECT_NAME}-paint-auth,${KIT_COMPOSE_PROJECT_NAME}-paint-compress,security-headers@file' + # Service + - 'traefik.http.services.${KIT_COMPOSE_PROJECT_NAME}-paint.loadbalancer.server.port=80' + - 'traefik.docker.network=${NETWORK_NAME}' + # Watchtower + - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' + +networks: + compose_network: + name: ${NETWORK_NAME} + external: true diff --git a/paint/Dockerfile b/paint/Dockerfile deleted file mode 100644 index a35505a..0000000 --- a/paint/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# Build miniPaint from GitHub repository -FROM node:18-alpine AS builder - -WORKDIR /app - -# Clone the repository -RUN apk add --no-cache git && \ - git clone https://github.com/viliusle/miniPaint.git . && \ - npm install && \ - npm run build - -# Production stage with nginx -FROM nginx:alpine - -# Copy built files from builder -COPY --from=builder /app/dist /usr/share/nginx/html - -# Copy nginx configuration if needed -COPY --from=builder /app /usr/share/nginx/html - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/paint/compose.yaml b/paint/compose.yaml deleted file mode 100644 index 4eaa8c3..0000000 --- a/paint/compose.yaml +++ /dev/null @@ -1,34 +0,0 @@ -services: - paint: - build: - context: . - dockerfile: Dockerfile - image: minipaint:latest - container_name: ${PAINT_COMPOSE_PROJECT_NAME}_app - restart: unless-stopped - networks: - - compose_network - labels: - - 'traefik.enable=${PAINT_TRAEFIK_ENABLED}' - # HTTP to HTTPS redirect - - 'traefik.http.middlewares.${PAINT_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https' - - 'traefik.http.routers.${PAINT_COMPOSE_PROJECT_NAME}-web.middlewares=${PAINT_COMPOSE_PROJECT_NAME}-redirect-web-secure' - - 'traefik.http.routers.${PAINT_COMPOSE_PROJECT_NAME}-web.rule=Host(`${PAINT_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${PAINT_COMPOSE_PROJECT_NAME}-web.entrypoints=web' - # HTTPS router with auth - - 'traefik.http.routers.${PAINT_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${PAINT_TRAEFIK_HOST}`)' - - 'traefik.http.routers.${PAINT_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver' - - 'traefik.http.routers.${PAINT_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure' - - 'traefik.http.middlewares.${PAINT_COMPOSE_PROJECT_NAME}-auth.basicauth.users=${AUTH_USERS}' - - 'traefik.http.middlewares.${PAINT_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true' - - 'traefik.http.routers.${PAINT_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${PAINT_COMPOSE_PROJECT_NAME}-auth,${PAINT_COMPOSE_PROJECT_NAME}-web-secure-compress,security-headers@file' - # Service - - 'traefik.http.services.${PAINT_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=80' - - 'traefik.docker.network=${NETWORK_NAME}' - # Watchtower - - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - -networks: - compose_network: - name: ${NETWORK_NAME} - external: true diff --git a/vert/compose.yaml b/vert/compose.yaml deleted file mode 100644 index 36044b8..0000000 --- a/vert/compose.yaml +++ /dev/null @@ -1,29 +0,0 @@ -services: - vert: - image: ${VERT_IMAGE:-ghcr.io/vert-sh/vert:latest} - container_name: ${VERT_COMPOSE_PROJECT_NAME}_app - restart: unless-stopped - environment: - PUB_HOSTNAME: ${VERT_TRAEFIK_HOST} - PUB_ENV: production - PUB_DISABLE_ALL_EXTERNAL_REQUESTS: true - networks: - - compose_network - labels: - - 'traefik.enable=${VERT_TRAEFIK_ENABLED}' - # Middlewares (used by dynamic config) - - 'traefik.http.middlewares.${VERT_COMPOSE_PROJECT_NAME}-auth.basicauth.users=${AUTH_USERS}' - - 'traefik.http.middlewares.${VERT_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true' - # Service definition - - 'traefik.http.services.${VERT_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=80' - - 'traefik.docker.network=${NETWORK_NAME}' - # Sablier labels - - 'sablier.enable=${VERT_SABLIER_ENABLED}' - - 'sablier.group=${VERT_COMPOSE_PROJECT_NAME}' - # Watchtower - - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - -networks: - compose_network: - name: ${NETWORK_NAME} - external: true