Added MATTERMOST_WEBHOOK_URL environment variable to n8n:
- Available as environment variable in workflow expressions
- Can be used in HTTP Request nodes to send notifications
- Enables easy integration with Mattermost from any workflow
Usage in n8n workflows:
- Use {{ $env.MATTERMOST_WEBHOOK_URL }} in expressions
- Send POST requests with JSON payload to notify Mattermost
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Mattermost webhook notifications for infrastructure services:
- **Netdata**: Health monitoring alerts sent to Mattermost
- Configured via MATTERMOST_WEBHOOK_URL environment variable
- All alert roles route to Mattermost notifications channel
- **Restic/Backrest**: Backup status notifications
- Webhook URL passed as environment variable
- Backrest web UI can be configured to use the webhook
- **Watchtower**: Container update notifications
- Uses Shoutrrr mattermost:// URL format
- Sends notifications when containers are updated
Configuration:
- Webhook URL stored securely in .env file
- All services reference ${MATTERMOST_WEBHOOK_URL} variable
- Watchtower uses specialized Shoutrrr format URL
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Mattermost stack to the docker-compose infrastructure:
- **Mattermost stack** (mattermost.pivoine.art):
- Team collaboration and chat platform
- PostgreSQL backend for message persistence
- Email notifications via IONOS SMTP
- Support for channels, direct messages, and integrations
- Mobile and desktop app support
- Full Traefik integration with SSL termination
Infrastructure updates:
- Updated PostgreSQL init script to create mattermost database
- Added environment variables to arty.yml for Mattermost configuration
- Updated compose.yaml include list
- Configured email settings for notifications and invitations
This will replace Gotify as the primary notification and messaging platform.
All services integrated with Traefik for SSL termination and include
Watchtower auto-update labels.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Netdata container is Debian-based, not Alpine. Updated package
manager from apk to apt-get for installing msmtp packages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Created custom Dockerfile for Netdata container that includes msmtp
and msmtp-mta packages for sending email notifications.
Changes:
- Added netdata/Dockerfile extending netdata/netdata:latest
- Installed msmtp, msmtp-mta, and ca-certificates via apk
- Set proper permissions (600) for msmtprc config file
- Updated compose.yaml to build from custom Dockerfile
This enables Netdata to send health alarm notifications via email
using the IONOS SMTP configuration in msmtprc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added comprehensive email configuration to enable notifications for
password resets, user invitations, system alerts, and backup failures.
**Services configured:**
- **Vaultwarden** (vault/compose.yaml):
- SMTP settings for password resets, 2FA, emergency access emails
- Uses IONOS SMTP with force_tls security
- Sender: hi@pivoine.art
- **Linkwarden** (links/compose.yaml):
- Email server configuration for user invitations
- Password reset functionality via email
- Uses smtp:// connection string format
- **n8n** (n8n/compose.yaml):
- SMTP mode enabled for workflow notifications
- User invitation emails
- Password reset support
- SSL-secured connection
- **Netdata** (netdata/compose.yaml, health_alarm_notify.conf, msmtprc):
- Health alarm notifications via email
- MSMTP configuration for sending alerts
- Notifications sent to valknar@pivoine.art
- Alerts for system issues, resource exhaustion, service failures
**Common SMTP settings** (from .env):
- Provider: IONOS (smtp.ionos.de:465)
- From address: hi@pivoine.art
- Transport: SMTP with TLS/SSL
- Admin email: valknar@pivoine.art
**Backrest notifications:**
- Configured via web UI at restic.pivoine.art
- Supports webhooks to Gotify for push notifications
- Email notifications can be added through UI settings
All services now have proper email notification capabilities for
improved monitoring, user management, and security features.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from mounting entire go.d directory to mounting specific
config files (postgres.conf, filecheck.conf) to avoid conflicts
with Netdata's initialization script which needs to write to the
go.d directory during startup.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added HTTP Basic Authentication to secure the Netdata monitoring dashboard:
- Added basicauth middleware using shared AUTH_USERS credentials
- Protects sensitive infrastructure metrics from unauthorized access
- Uses same credentials as Scrapy and other protected services
- Maintains SSL/TLS encryption via Traefik
Security improvements:
- Dashboard now requires username/password
- Prevents public access to server metrics
- Infrastructure monitoring data protected
- Follows security best practices from Netdata documentation
Access requires credentials stored in AUTH_USERS environment variable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Netdata real-time monitoring system at netdata.pivoine.art:
- Real-time infrastructure and container monitoring
- Auto-discovers all Docker containers
- Tracks CPU, memory, disk, network usage per service
- Low overhead monitoring (~1-3% CPU)
- Self-hosted with web dashboard on port 19999
Configuration:
- Created netdata/compose.yaml with full Traefik integration
- Added to main compose.yaml include list
- Added environment variables to arty.yml
- Mounted Docker socket for container metrics
- Mounted system directories for host metrics (/proc, /sys, /var/log)
- Three persistent volumes: config, lib, cache
- Required capabilities: SYS_PTRACE, SYS_ADMIN for system monitoring
- Watchtower enabled for automatic updates
Benefits for infrastructure:
- Monitor 20+ running services in real-time
- Track PostgreSQL, Redis, Traefik performance
- Watch backup processes (Backrest/Restic)
- Monitor Jellyfin transcoding load
- Alert on resource issues before they become critical
- Historical data for capacity planning
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Excalidraw collaborative drawing tool to the Kit toolkit at draw.kit.pivoine.art:
- Virtual whiteboard for sketching hand-drawn diagrams
- Infinite canvas with collaborative features
- Image: excalidraw/excalidraw:latest
- Exposed on port 80 via Traefik with SSL
Configuration:
- Added draw service to kit/compose.yaml
- Added KIT_DRAW_IMAGE and KIT_DRAW_TRAEFIK_HOST to arty.yml
- Health check disabled to prevent false negatives
- Follows same pattern as other Kit tools
- Watchtower enabled for automatic updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Disabled health check for Units converter service as it was failing
due to IPv6 connection attempts. The service is running correctly
but the health check was trying to connect to [::1]:80 instead of
127.0.0.1:80, causing false negatives.
Similar to Pastel services, disabling the built-in health check.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed port configuration for Units converter service:
- Changed loadbalancer.server.port from 3000 to 80
- Matches the actual nginx port in the units-ui container
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Units converter to the Kit toolkit at units.kit.pivoine.art:
- Unit conversion tool for length, weight, temperature, etc.
- Image: ghcr.io/valknarness/units-ui:latest
- Exposed on port 3000 via Traefik with SSL
Configuration:
- Added units service to kit/compose.yaml
- Added KIT_UNITS_IMAGE and KIT_UNITS_TRAEFIK_HOST to arty.yml
- Follows same pattern as other Kit tools (Vert, Paint, Pastel, Stirling)
- Watchtower enabled for automatic updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed typo in Stirling PDF service naming:
- Renamed service from sterling to stirling
- Updated all variable names: KIT_STERLING_* to KIT_STIRLING_*
- Updated domain: sterling.kit.pivoine.art to stirling.kit.pivoine.art
- Updated container name: kit_sterling to kit_stirling
This matches the correct spelling of Stirling-PDF.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Stirling PDF (S-PDF) to the Kit toolkit at sterling.kit.pivoine.art:
- PDF manipulation tool with 100+ features
- Merge, split, convert, OCR, compress PDFs
- Privacy-focused: local processing, no data collection
- Image: frooodle/s-pdf:latest
- Exposed on port 8080 via Traefik with SSL
Configuration:
- Added sterling service to kit/compose.yaml
- Added KIT_STERLING_IMAGE and KIT_STERLING_TRAEFIK_HOST to arty.yml
- Follows same pattern as other Kit tools (Vert, Paint, Pastel)
- Watchtower enabled for automatic updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed all Traefik labels from pastel_api service since the API
is now only accessed internally by pastel_ui via Docker network.
Changes:
- Removed traefik.enable and all HTTP/HTTPS router configurations
- Removed path prefix routing (/api)
- Kept only Watchtower label for auto-updates
- API now accessible only at http://pastel_api:3000 internally
This simplifies the configuration and improves security by not
exposing the API endpoint externally.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed Pastel UI environment variable to use internal Docker
network communication instead of external HTTPS:
- Changed from NEXT_PUBLIC_API_URL/NEXT_PUBLIC_APP_URL to PASTEL_API_URL
- Use internal container name: http://pastel_api:3000
- Removes dependency on external domain resolution
- Improves performance by avoiding Traefik proxy for internal calls
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed custom Dockerfile and switched to using the official
machines/filestash image from Docker Hub:
- Removed stash/Dockerfile (custom build with ffmpeg)
- Updated compose.yaml to use ${STASH_IMAGE} variable
- Defaults to machines/filestash:latest
This simplifies maintenance and ensures we use the officially
maintained image with proper updates via Watchtower.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed Pastel color palette generator services in Kit stack:
- Disabled health checks for pastel_api (curl not available in image)
- Disabled health checks for pastel_ui (unnecessary)
- Added NEXT_PUBLIC_API_URL environment variable to pastel_ui
- Added NEXT_PUBLIC_APP_URL environment variable to pastel_ui
Health checks were showing containers as unhealthy even though services
were running correctly. The API health check used curl which isn't
installed in the image, causing false negatives.
Environment variables configure the Next.js frontend to correctly
connect to the API endpoint.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed authentication middleware from Vert and Paint services:
- Removed basicauth middleware labels from vert service
- Removed basicauth middleware labels from paint service
- Updated middleware chains to exclude auth
- Updated CLAUDE.md to reflect public access
All Kit stack services (landing, vert, paint) are now publicly
accessible without authentication as they are client-side tools
that don't require protection.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added kit-ui landing page as the main entry point for the toolkit:
- Image: ghcr.io/valknarness/kit-ui:latest
- URL: https://kit.pivoine.art
- Provides links to Vert (file converter) and Paint (image editor)
Changes:
- Added landing service to kit/compose.yaml
- Updated arty.yml with KIT_TRAEFIK_HOST and KIT_LANDING_IMAGE
- Updated CLAUDE.md with landing page documentation
- Updated README.md with landing page reference
Toolkit structure:
- Landing: kit.pivoine.art (main hub)
- Vert: vert.kit.pivoine.art (file converter)
- Paint: paint.kit.pivoine.art (image editor)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Moved Dockerfile from paint/ to kit/ directory and updated
build context to use local directory instead of ../paint.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Created new kit.pivoine.art stack with path-based routing:
- /vert: VERT file format converter (250+ formats)
- /paint: miniPaint image editor
Changes:
- Created kit/compose.yaml with both services
- Removed Sablier scale-to-zero from Vert (no longer needed)
- Deleted old vert/ and paint/ stack directories
- Updated compose.yaml includes (removed vert, paint; added kit)
- Updated arty.yml with KIT_* environment variables
- Updated CLAUDE.md documentation with new Kit section
- Updated README.md with consolidated toolkit entry
Benefits:
- Single domain for related utilities
- Simplified service management
- Maintained HTTP Basic Auth protection
- No Sablier dependency for Vert
Access:
- File converter: https://kit.pivoine.art/vert
- Image editor: https://kit.pivoine.art/paint🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added documentation for three new services:
- **DROP (PairDrop)**: Peer-to-peer file sharing with WebRTC
- Direct device-to-device transfers (no server storage)
- STUN-enabled cross-network support
- Automatic device discovery
- Share files, text, and clipboard content
- **JELLY (Jellyfin)**: Media streaming server
- HiDrive photo and video streaming
- Hardware transcoding support
- Multi-device playback
- **PAINT (miniPaint)**: Web-based image editor
- Layer support and filters
- Client-side processing
- PNG, JPG, GIF, WebP support
Updated:
- Core systems table with new services
- Ship architecture diagram
- Storage volumes list
- Navigation commands with usage instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enabled WebSocket server and fallback to help with device discovery
when devices appear to have the same public IP through the proxy.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Documented the PairDrop service with WebRTC STUN configuration:
- Service description and features
- RTC_CONFIG configuration with Google STUN servers
- Usage instructions for cross-network file sharing
- Technical details about WebRTC and NAT traversal
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed from ./drop/rtc_config.json to ./rtc_config.json since the
compose file is already in the drop/ directory. The previous path
caused Docker to create an empty directory instead of mounting the file.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed mount path from /config/rtc_config.json to /rtc_config.json
to resolve EISDIR error where container was trying to read a directory
instead of the file.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added RTC configuration with Google's public STUN servers to enable
peer-to-peer connections across different networks (e.g., WiFi to
cellular data).
Changes:
- Created drop/rtc_config.json with 5 Google STUN servers
- Updated drop/compose.yaml to mount RTC config file
- Added RTC_CONFIG environment variable pointing to config file
This should resolve connectivity issues when devices are on different
networks or behind NAT.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added PairDrop stack for peer-to-peer file sharing:
- WebRTC-based direct file transfers between devices
- No file size limits or server storage
- End-to-end encrypted transfers
- Local network auto-discovery
- Cross-platform support (desktop, mobile, tablets)
- Progressive Web App installable on mobile
- Rate limiting enabled for security
PairDrop provides secure, private file sharing without uploading
files to any server - all transfers happen directly between devices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added jellyfin-backup plan to Backrest configuration:
- Backs up /volumes/jelly_config daily at 9 AM
- Retention: 7 daily, 4 weekly, 6 monthly, 2 yearly
- Added jelly_config volume mount to restic/compose.yaml
- Updated documentation in CLAUDE.md
This ensures Jellyfin configuration, library metadata, and user
settings are backed up to HiDrive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added Jellyfin stack for streaming photos and videos from HiDrive:
- Maps /mnt/hidrive/users/valknar/Pictures to /media/pictures (read-only)
- Maps /mnt/hidrive/users/valknar/Videos to /media/videos (read-only)
- Hardware transcoding support for optimal video playback
- Multi-device streaming (web, mobile, TV apps)
- Automatic media organization with metadata fetching
Jellyfin provides superior video playback compared to Filestash's
transcoding plugin, which has compatibility issues with named pipes
in containerized environments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Video transcoding was failing with 'Failed to open segment pipe:out000.ts'
because ffmpeg couldn't create named pipes. Added tmpfs mount with exec
permissions to /tmp to allow ffmpeg to create temporary files and pipes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added custom Dockerfile to build Filestash with ffmpeg and ffprobe installed,
enabling the built-in video transcoding plugin for seamless video playback.
Changes:
- Created stash/Dockerfile extending machines/filestash:latest
- Installed ffmpeg package with apt-get
- Updated stash/compose.yaml to build from Dockerfile
- Video transcoding plugin will automatically detect ffmpeg presence
This enables Filestash to transcode mov, mkv, avi, mpeg, and other video
formats for in-browser playback without manual conversion.
Note: Enable the video transcoding feature in Filestash admin panel at
https://stash.pivoine.art/admin after deployment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Protected paint.pivoine.art with HTTP Basic Auth using shared AUTH_USERS
credentials, matching the security setup of scrapy and other protected services.
Changes:
- Added basicauth middleware with AUTH_USERS variable
- Added compression middleware
- Updated router middlewares chain to include auth, compress, and security headers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added new paint service stack to the docker-compose infrastructure:
- **Paint stack** (paint.pivoine.art):
- miniPaint: Web-based image editor built from GitHub
- Multi-stage Docker build clones from https://github.com/viliusle/miniPaint
- Features: layers, filters, drawing tools, text, shapes support
- Client-side processing with no server uploads
- Stateless architecture (no backups needed)
Infrastructure updates:
- Created paint/compose.yaml with Traefik routing and SSL
- Created paint/Dockerfile with Node.js build stage and nginx serve
- Added PAINT environment variables to arty.yml
- Updated compose.yaml include list
- Updated CLAUDE.md documentation
All services integrated with Traefik for SSL termination and include
Watchtower auto-update labels.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Properly configured AUTH_USERS environment variable for Traefik container
to use in the vert-sablier.yaml dynamic configuration via Go templating.
Changes:
- Added AUTH_USERS environment variable to proxy compose file
- Updated vert-auth middleware to use {{ env AUTH_USERS }} template
- Fixed environment syntax to use map format instead of list format
This keeps credentials secure in the .env file (not tracked in git) while
the tracked vert-sablier.yaml file only contains the template reference.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed AUTH_USERS environment variable from Traefik container as Docker
Compose was incorrectly expanding the $ signs in the htpasswd hash.
Instead, hardcoded the credentials directly in the vert-sablier.yaml dynamic
configuration file (which is not tracked in git, so changes remain local).
The AUTH_USERS variable with $$ escaping continues to work correctly in
Docker labels for other services.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed "middleware does not exist" error for VERT by defining all middlewares
in the file provider configuration instead of relying on Docker provider
middlewares from a stopped container (Sablier scale-to-zero).
Changes:
- Added vert-auth middleware using AUTH_USERS environment variable
- Added vert-compress middleware for gzip compression
- Passed AUTH_USERS to Traefik container via environment section
- Removed @docker suffixes from middleware references
This ensures middlewares are always available, even when the VERT container
is stopped by Sablier's dynamic scaling feature.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed "the service 'vert-web-secure@docker' does not exist" error by defining
the service directly in the vert-sablier.yaml file provider configuration.
Previously, the router was defined in the file provider but tried to reference
a service from the Docker provider (@docker suffix), which caused a mismatch.
Now both the router and service are defined in the same file provider, while
still using Docker-based middlewares (auth, compression).
Changes:
- Added services.vert-web-secure with loadBalancer pointing to vert_app:80
- Changed router service reference from "vert-web-secure@docker" to "vert-web-secure"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed the global `--entrypoints.web-secure.http.tls.options=default@file`
configuration from proxy/compose.yaml that was causing "unknown TLS options"
errors during Traefik startup.
The issue occurred because Traefik attempted to apply TLS options to all
routers before the file provider finished loading security.yaml, creating
a race condition. Services now use Let's Encrypt certificates without
explicit TLS options at the entrypoint level.
The TLS security settings (minimum TLS 1.2, strong cipher suites, etc.)
remain defined in proxy/dynamic/security.yaml and can be applied to
individual services via their router configurations if needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>