Unified background colors for smoother visual flow:
Changes:
- Header background: bg-darker → bg-dark (matches body)
- Footer background: bg-darker → bg-dark (matches body)
- Body background: bg-darker → bg-dark (consistent base color)
- Removed redundant main content background override
This creates a cohesive color scheme where:
- Body, header, and footer all use bg-dark (HSL 0, 0%, 17.5%)
- Cards and panels use bg-lighter (HSL 0, 0%, 22%) for subtle contrast
- Rose borders on header/footer provide visual separation
- No more jarring black-to-gray transitions
The result is a smooth, unified dark theme with the Pivoine rose
accent colors providing visual interest and hierarchy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced the Pivoine theme with better visual hierarchy and usability:
Logo improvements:
- Fixed logo selector to target .navbar-brand img (not just .logo)
- Logo now properly colorized with primary rose/magenta color
Contrast improvements:
- Changed body background to darker shade (bg-darker)
- Added main content area with lighter background (bg-dark) for better separation
- Added heading styles with rose accent color
- H1/H2 headings now have rose bottom border for visual hierarchy
Button refinements:
- Split button styling into primary buttons and filter buttons
- Filter buttons (.btn-light in .btn-group) now have subtle, elegant styling:
* Lighter background with border
* Smaller size and lighter font weight
* Hover shows rose border and text color
* Active state has solid rose background
- Primary buttons maintain bold rose background styling
The result is clearer visual separation between navigation, content, and
interactive elements with better contrast throughout.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed awkward appearance where Bootstrap .container elements were being
styled as cards with backgrounds, borders, and padding.
Changes:
- Removed .container from card/panel styling rule
- Added explicit .container reset to transparent background
- Removed .container from responsive design rule
- .container now maintains proper Bootstrap layout behavior
This fixes the "inner looks like a card" issue where main content areas
had unwanted card styling applied.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated custom.css to target the actual Bootstrap classes used by
asciinema server instead of generic selectors:
- Added .navbar-dark and .bg-dark overrides for navigation
- Added .btn-light targeting for button styles
- Added .btn.active and .btn-light.active for active button states
This ensures the Pivoine theme properly overrides Bootstrap's default
dark theme classes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated entrypoint-wrapper.sh to delete .css.gz files after injecting
custom CSS. The web server was serving old pre-compressed files instead
of our updated CSS with the Pivoine theme.
Changes:
- Remove app.css.gz and app-*.css.gz after CSS injection
- Forces web server to serve uncompressed updated CSS files
- Ensures custom Pivoine theme is visible on production
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed server endpoints not starting by correcting the entrypoint wrapper
script to use the proper command: /sbin/tini -- /opt/app/bin/server
Changes:
- Updated entrypoint-wrapper.sh to exec /sbin/tini instead of /opt/app/bin/asciinema
- Removed incorrect command: ["start"] from compose.yaml
- Custom CSS injection now working with proper server startup
- Both main (port 4000) and admin (port 4002) endpoints now running
The custom Pivoine theme CSS is successfully injected at container startup
and served via both app.css and hashed app-*.css files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added entrypoint wrapper script that injects custom.css into the
main app.css file at container startup. This allows the custom
Pivoine theme to be applied without building a custom image.
Changes:
- Mount custom.css to static assets directory
- Add entrypoint-wrapper.sh to inject CSS on startup
- Append custom CSS to both app.css and hashed app-*.css
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed 404 error by explicitly specifying which service each router
should use. Traefik was unable to automatically link routers when
multiple services were defined.
Added service specification to all routers:
- asciinema-web → asciinema service
- asciinema-web-secure → asciinema service
- asciinema-admin-web → asciinema-admin service
- asciinema-admin-web-secure → asciinema-admin service
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added admin interface routing at admin.asciinema.pivoine.art:
- Port 4002 exposed via Traefik
- HTTP Basic Auth protection using AUTH_USERS
- HTTPS with Let's Encrypt certificate
- Security headers and compression middleware
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Force IONOS SMTP server IP (213.165.67.97) in /etc/hosts
to bypass potential DNS resolution issues.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed SMTP_NO_MX_LOOKUPS and SMTP_ALLOWED_TLS_VERSIONS settings
that were added during troubleshooting. Using only basic SMTP
configuration that works for other services (mattermost, tandoor).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
For implicit SSL on port 465, we need SMTP_SSL: true and should
NOT set SMTP_TLS (which is for STARTTLS on port 587).
Added SMTP_NO_MX_LOOKUPS to skip MX record lookups.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Reverted to use ${EMAIL_SMTP_PORT} from .env (port 465) since
this configuration works for other services.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Port 465 with implicit SSL is not working. Switching to port 587
with STARTTLS (SMTP_TLS: always) which is more compatible with
Swoosh SMTP adapter.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added SMTP_NO_MX_LOOKUPS, SMTP_TLS_VERIFY_HOST, and
SMTP_TLS_VERIFY_CERT settings to fix SSL connection issues
with IONOS SMTP on port 465.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Environment variables are strings. Use 'never' not ':never' so
the Elixir code can convert it to an atom properly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
SMTP_TLS must be an Elixir atom (:never, :always, or :if_available).
Using :never for implicit SSL on port 465.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
IONOS SMTP on port 465 uses implicit SSL, not STARTTLS.
Set SMTP_TLS: false and SMTP_SSL: true.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Port 465 requires implicit TLS, not SSL. Changed SMTP_SSL to SMTP_TLS
with value 'always' and added SMTP_AUTH: always.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add EMAIL_FROM to arty.yml environment defaults
- Configure asciinema to use EMAIL_FROM for MAIL_FROM_ADDRESS
- Set SMTP_SSL to true for IONOS SMTP on port 465
- Set SIGN_UP_DISABLED default to false (will enable after admin creation)
- Follow netdata compose.yaml pattern for Traefik labels
- Add proper HTTP to HTTPS redirect middlewares
- Configure compression and security headers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed API key reference from ${ANTHROPIC_API_KEY} to
os.environ/ANTHROPIC_API_KEY to match LiteLLM's documented syntax.
The os.environ/ prefix tells LiteLLM to use os.getenv() to retrieve
the environment variable at runtime, which is the correct way to
reference environment variables in LiteLLM config files.
Reference: https://docs.litellm.ai/docs/proxy/deploy🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed custom Dockerfile and SFTP function integration in favor of
the simpler REST API approach (webui-export.py).
Changes:
- Restored webui service to use official Open WebUI image
- Removed custom Dockerfile.webui (paramiko build)
- Removed ai/functions/save_to_disk.py SFTP function
- Removed SSH key and functions volume mounts
The REST API export script (webui-export.py) is a simpler and more
flexible solution that doesn't require Docker modifications.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Python script to extract and save code blocks from Open WebUI
chat conversations to local disk using the REST API.
Features:
- Export code blocks from specific chats or all chats
- Automatic language detection and proper file extensions
- Organizes files by chat title with metadata
- No Docker modifications needed
- Remote access support via SSH tunnel or public URL
Usage:
python3 ai/webui-export.py --all --output-dir ./exports
python3 ai/webui-export.py --chat-id <id> --output-dir ./code
This replaces the complex SFTP integration with a simple API-based
approach that's easier to maintain and use.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added custom Open WebUI function for SSH/SFTP file operations:
**New Function: save_to_disk.py**
- save_file(): Write generated code to local filesystem via SFTP
- read_file(): Read files from local disk
- list_files(): List directory contents
- Configurable via Valves (host, port, username, paths)
**Custom Dockerfile (Dockerfile.webui)**
- Based on ghcr.io/open-webui/open-webui:main
- Installs paramiko library for SSH/SFTP support
- Creates .ssh directory for key storage
**Configuration Updates**
- Mount SSH private key from host (/root/.ssh/id_rsa)
- Mount functions directory for custom tools
- Build custom image with SFTP capabilities
**Usage in Open WebUI**
Claude can now use these tools to:
- Generate code and save it directly to your local disk
- Read existing files for context
- List project directories
- Create new files in any project
Default base path: /home/valknar/Projects
Authentication: SSH key-based (passwordless)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added LiteLLM as an OpenAI-compatible proxy for Anthropic's API to
enable Claude models in Open WebUI.
**New Service: litellm**
- Image: ghcr.io/berriai/litellm:main-latest
- Internal proxy on port 4000
- Converts Anthropic API to OpenAI-compatible format
- Health check with 30s intervals
- Not exposed via Traefik (internal only)
**LiteLLM Configuration (litellm-config.yaml)**
- Claude Sonnet 4 (claude-sonnet-4-20250514)
- Claude Sonnet 4.5 (claude-sonnet-4-5-20250929)
- Claude 3.5 Sonnet (claude-3-5-sonnet-20241022)
- Claude 3 Opus (claude-3-opus-20240229)
- Claude 3 Haiku (claude-3-haiku-20240307)
**Open WebUI Configuration Updates**
- Changed OPENAI_API_BASE_URLS to point to LiteLLM proxy
- URL: http://litellm:4000/v1
- Added litellm as dependency for webui service
- Dummy API key for proxy authentication
**Why LiteLLM?**
Anthropic's API uses different endpoint structure and authentication
headers compared to OpenAI. LiteLLM acts as a translation layer,
allowing Open WebUI to use Claude models through its OpenAI-compatible
interface.
**Available Models in Open WebUI**
- claude-sonnet-4 (latest Claude Sonnet 4)
- claude-sonnet-4.5 (Claude Sonnet 4.5)
- claude-3-5-sonnet
- claude-3-opus
- claude-3-haiku
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated README.md with AI intelligence system:
**Core Systems Table**:
- Added AI system entry with ai.pivoine.art access point
**Infrastructure Section**:
- Added AI Intelligence Core with PostgreSQL 16 + pgvector
- Listed Open WebUI, Crawl4AI, and document embeddings
**Navigation Commands**:
- New AI Operations section with usage instructions
- How to configure Claude API in Open WebUI
- Steps: create account, add API connection, select model, upload docs
- Internal services documentation (Crawl4AI, PostgreSQL)
- n8n workflow integration examples
**Ship Architecture**:
- Added AI Intelligence category with 3 services
- PostgreSQL+pgvector for vector database
- Open WebUI for Claude interface
- Crawl4AI for web scraping (internal)
**Storage Volumes**:
- ai_postgres_data: AI vector database
- ai_webui_data: Open WebUI application data
- ai_crawl4ai_data: Web scraping cache
**Backup Protocol**:
- Updated backup window to 2-10 AM
- Updated count from 11 to 16 backup plans
All AI services accessible at https://ai.pivoine.art with Claude
integration, RAG support, and web search capabilities.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed service name from 'postgres' to 'ai_postgres' to avoid naming
conflict with the core PostgreSQL service in Docker Compose include.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Created complete AI infrastructure stack at ai.pivoine.art:
**New Services:**
- **Open WebUI** (ai.pivoine.art)
- ChatGPT-like interface for AI models
- Multi-user chat with authentication
- RAG (Retrieval-Augmented Generation) support
- Document upload and processing
- Claude API integration via Anthropic
- **PostgreSQL with pgvector** (dedicated AI database)
- Vector similarity search for RAG
- Separate from production databases
- Stores embeddings and documents
- **Crawl4AI** (internal API service)
- Web scraping optimized for LLMs
- Converts websites to clean Markdown
- Called by n8n workflows
- No public exposure (internal only)
**Configuration:**
- Added 18 AI environment variables to arty.yml
- Configured email notifications via IONOS SMTP
- OpenAI API compatibility for Claude integration
- Traefik SSL termination and compression
**Backup:**
- Added 3 AI volumes to Restic backup
- Daily backup at 3 AM
- Retention: 7 daily, 4 weekly, 6 monthly, 2 yearly
**Integration:**
- Shares falcon_network with existing services
- Ready for n8n workflow automation
- Mattermost notifications support
- Watchtower auto-updates enabled
Ready for Phase 2: GPU server integration with Ollama, Whisper, and
Stable Diffusion when IONOS A10 server is provisioned.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed PostgreSQL image from postgres:16-alpine to
pgvector/pgvector:pg16-alpine to enable vector similarity search
capabilities for AI/RAG applications.
This is required for Open WebUI's RAG functionality to store and
query document embeddings.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Tandoor's internal Nginx listens on port 80, not 8080. This was causing
502 Bad Gateway errors because Traefik was trying to connect to the wrong port.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed VPN backup configuration since the VPN service is not currently
included in the compose.yaml and the vpn_etc_wireguard volume doesn't exist.
Kept Netdata backup as those volumes exist and the service is running.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Tandoor Recipes as a comprehensive recipe management solution:
**Tandoor Stack** (tandoor.pivoine.art):
- Modern recipe manager with smart scaling and collaboration
- PostgreSQL backend for recipe persistence
- Email notifications via IONOS SMTP
- Static and media file storage in dedicated volumes
- User signups disabled (admin-only access)
**Features:**
- Smart recipe scaling (auto-adjust ingredients for servings)
- Spaces for collaboration (family/roommate recipe sharing)
- Meal planning and shopping lists
- Recipe import from URLs
- Mobile app support (Kitshn app)
- Nutritional information and pricing
**Infrastructure updates:**
- Added tandoor database to PostgreSQL init script
- Added environment variables to arty.yml
- Updated compose.yaml include list
- Added Tandoor volumes (staticfiles, mediafiles) to Restic backup
- Configured email notifications for invitations and notifications
**Tech stack:**
- Django/Python backend
- Vue.js frontend
- PostgreSQL database (shared core instance)
- Gunicorn WSGI server
Tandoor provides superior UX compared to Mealie with better recipe
scaling, collaboration features, and mobile app experience.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated documentation to reflect current infrastructure:
**Added:**
- Mattermost team collaboration platform
- Team chat, file sharing, integrations
- Email notifications via IONOS SMTP
- Incoming webhooks for infrastructure alerts
- PostgreSQL backend
- Netdata real-time monitoring
- System and service monitoring
- PostgreSQL and Docker monitoring
- Restic backup repository monitoring
- Email and Mattermost alerts
- HTTP Basic Auth protection
**Removed:**
- Gotify notification server (replaced by Mattermost)
**Updated:**
- Database initialization: Added mattermost database
- Backup configuration: Added Mattermost volumes
- Environment variables: Added MATTERMOST_WEBHOOK_URL and WATCHTOWER_NOTIFICATION_URL
- Volume management: Added Mattermost, Joplin, and Jellyfin volumes
- Service list in compose include pattern
All documentation now reflects the current state of the infrastructure
with Mattermost as the central notification and collaboration hub.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Mattermost volumes to backup configuration:
- mattermost_config: Server configuration and settings
- mattermost_data: User data, posts, files, and attachments
- mattermost_plugins: Installed plugins
These volumes contain critical Mattermost data and should be
backed up regularly to ensure team chat history and configurations
can be restored if needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed Gotify completely from infrastructure, replaced by Mattermost:
- Removed gotify/compose.yaml stack
- Removed Gotify environment variables from arty.yml
- Removed Gotify from compose.yaml include list
- Removed Gotify volume backup from Restic configuration
Gotify has been fully replaced by Mattermost for:
- Infrastructure notifications (Netdata, Watchtower, Restic)
- n8n workflow notifications
- Team collaboration and chat
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>