feat: docker
This commit is contained in:
84
.dockerignore
Normal file
84
.dockerignore
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
.svelte-kit
|
||||||
|
**/dist
|
||||||
|
**/build
|
||||||
|
**/.svelte-kit
|
||||||
|
|
||||||
|
# Rust/Cargo
|
||||||
|
target
|
||||||
|
**/target
|
||||||
|
Cargo.lock
|
||||||
|
**/Cargo.lock
|
||||||
|
|
||||||
|
# WASM outputs
|
||||||
|
wasm
|
||||||
|
**/wasm
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
**/.env
|
||||||
|
**/.env.*
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
docker-compose.yml
|
||||||
|
docker-compose*.yml
|
||||||
|
build.sh
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
DOCKER.md
|
||||||
|
CLAUDE.md
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github
|
||||||
|
.gitlab-ci.yml
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.prettierrc
|
||||||
|
.prettierignore
|
||||||
|
.eslintrc
|
||||||
|
.eslintignore
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
261
.github/DOCKER_SETUP.md
vendored
Normal file
261
.github/DOCKER_SETUP.md
vendored
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
# Docker & CI/CD Setup Summary
|
||||||
|
|
||||||
|
This document summarizes all Docker and CI/CD files created for sexy.pivoine.art.
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
|
||||||
|
### Docker Files
|
||||||
|
|
||||||
|
1. **`Dockerfile`** (root)
|
||||||
|
- Multi-stage build (base → builder → runner)
|
||||||
|
- Rust toolchain installation for WASM builds
|
||||||
|
- Optimized layer caching
|
||||||
|
- Non-root user for security
|
||||||
|
- Health checks included
|
||||||
|
|
||||||
|
2. **`.dockerignore`** (root)
|
||||||
|
- Excludes unnecessary files from build context
|
||||||
|
- Optimizes build performance
|
||||||
|
|
||||||
|
3. **`docker-compose.production.yml`** (root)
|
||||||
|
- Production orchestration
|
||||||
|
- Pre-configured to use GHCR images
|
||||||
|
- Resource limits and health checks
|
||||||
|
- Environment variable management
|
||||||
|
|
||||||
|
4. **`.env.production.example`** (root)
|
||||||
|
- Template for all environment variables
|
||||||
|
- Documented with examples
|
||||||
|
|
||||||
|
### Build Scripts
|
||||||
|
|
||||||
|
5. **`build.sh`** (root)
|
||||||
|
- Convenience script for building images
|
||||||
|
- Supports tags, platforms, and pushing
|
||||||
|
- Executable (`chmod +x`)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
6. **`DOCKER.md`** (root)
|
||||||
|
- Comprehensive Docker deployment guide
|
||||||
|
- Building, running, troubleshooting
|
||||||
|
- Production best practices
|
||||||
|
- Updated with GHCR information
|
||||||
|
|
||||||
|
7. **`QUICKSTART.md`** (root)
|
||||||
|
- 5-minute quick start guide
|
||||||
|
- Docker Run and Docker Compose examples
|
||||||
|
- Common commands reference
|
||||||
|
|
||||||
|
8. **`README.md`** (root) - **UPDATED**
|
||||||
|
- Added Docker quick start
|
||||||
|
- Added CI/CD badges
|
||||||
|
- Added documentation links
|
||||||
|
|
||||||
|
9. **`CLAUDE.md`** (root) - **UPDATED**
|
||||||
|
- Added Docker deployment section
|
||||||
|
- Referenced DOCKER.md
|
||||||
|
|
||||||
|
### GitHub Actions Workflows
|
||||||
|
|
||||||
|
10. **`.github/workflows/docker-build-push.yml`**
|
||||||
|
- Builds and pushes to `ghcr.io/valknarxxx/sexy`
|
||||||
|
- Multi-platform (AMD64 + ARM64)
|
||||||
|
- Smart tagging (latest, semver, branch, SHA)
|
||||||
|
- Triggers: push to main/develop, tags, PRs, manual
|
||||||
|
- BuildKit cache for faster builds
|
||||||
|
|
||||||
|
11. **`.github/workflows/docker-scan.yml`**
|
||||||
|
- Daily security scans with Trivy
|
||||||
|
- Reports to GitHub Security tab
|
||||||
|
- Scans CRITICAL and HIGH vulnerabilities
|
||||||
|
- Triggers: schedule, push to main, tags, manual
|
||||||
|
|
||||||
|
12. **`.github/workflows/cleanup-images.yml`**
|
||||||
|
- Weekly cleanup of old images
|
||||||
|
- Keeps last 10 versions (configurable)
|
||||||
|
- Deletes untagged images
|
||||||
|
- Triggers: schedule, manual
|
||||||
|
|
||||||
|
13. **`.github/workflows/README.md`**
|
||||||
|
- Comprehensive workflow documentation
|
||||||
|
- Setup requirements
|
||||||
|
- Usage examples
|
||||||
|
- Troubleshooting guide
|
||||||
|
|
||||||
|
14. **`.github/DOCKER_SETUP.md`** (this file)
|
||||||
|
- Summary of all Docker/CI files
|
||||||
|
- Quick reference
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Image Registry
|
||||||
|
|
||||||
|
- **Registry:** GitHub Container Registry (GHCR)
|
||||||
|
- **Image Name:** `ghcr.io/valknarxxx/sexy`
|
||||||
|
- **Tags:**
|
||||||
|
- `latest` - Latest from main branch
|
||||||
|
- `v1.0.0` - Semantic versions
|
||||||
|
- `develop` - Latest from develop branch
|
||||||
|
- `main-abc123` - Commit-specific
|
||||||
|
|
||||||
|
### Pull & Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull latest
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
|
||||||
|
# Run
|
||||||
|
docker run -d -p 3000:3000 --env-file .env.production ghcr.io/valknarxxx/sexy:latest
|
||||||
|
|
||||||
|
# Or use docker-compose
|
||||||
|
docker-compose -f docker-compose.production.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using script
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
# Manual
|
||||||
|
docker build -t sexy.pivoine.art:latest .
|
||||||
|
|
||||||
|
# Multi-platform
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 -t sexy.pivoine.art:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trigger CI/CD
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and push 'latest'
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# Build and push version tags
|
||||||
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
|
||||||
|
# PR builds (test only, doesn't push)
|
||||||
|
git push origin feature/branch
|
||||||
|
# Create PR on GitHub
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- ✅ Non-root user in container
|
||||||
|
- ✅ Minimal base image (node:20.19.1-slim)
|
||||||
|
- ✅ Daily vulnerability scans
|
||||||
|
- ✅ Security reports in GitHub Security tab
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- ✅ Multi-stage builds for smaller images
|
||||||
|
- ✅ BuildKit cache for faster builds
|
||||||
|
- ✅ Production-only dependencies
|
||||||
|
- ✅ Optimized layer caching
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
- ✅ Health checks built-in
|
||||||
|
- ✅ dumb-init for proper signal handling
|
||||||
|
- ✅ Resource limits configurable
|
||||||
|
- ✅ Auto-restart on failure
|
||||||
|
|
||||||
|
### Automation
|
||||||
|
- ✅ Automatic builds on push/tag
|
||||||
|
- ✅ Multi-platform support
|
||||||
|
- ✅ Smart semantic versioning
|
||||||
|
- ✅ Weekly image cleanup
|
||||||
|
|
||||||
|
## Workflow Triggers Summary
|
||||||
|
|
||||||
|
| Workflow | Push Main | Push Develop | Tags | PR | Schedule | Manual |
|
||||||
|
|----------|-----------|--------------|------|----|----------|--------|
|
||||||
|
| Build & Push | ✅ | ✅ | ✅ | ✅ (no push) | ❌ | ✅ |
|
||||||
|
| Security Scan | ✅ | ❌ | ✅ | ❌ | Daily 2AM | ✅ |
|
||||||
|
| Cleanup | ❌ | ❌ | ❌ | ❌ | Weekly Sun 3AM | ✅ |
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Required
|
||||||
|
- `PUBLIC_API_URL` - Directus API endpoint
|
||||||
|
- `PUBLIC_URL` - Frontend URL
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
- `PUBLIC_UMAMI_ID` - Analytics
|
||||||
|
- `LETTERSPACE_API_URL` - Newsletter API
|
||||||
|
- `LETTERSPACE_API_KEY` - Newsletter key
|
||||||
|
- `LETTERSPACE_LIST_ID` - Mailing list ID
|
||||||
|
|
||||||
|
See `.env.production.example` for full reference.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Test Local Build**
|
||||||
|
```bash
|
||||||
|
./build.sh
|
||||||
|
docker run -d -p 3000:3000 --env-file .env.production sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Push to GitHub**
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Add Docker and CI/CD setup"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Monitor First Build**
|
||||||
|
- Go to GitHub Actions tab
|
||||||
|
- Watch "Build and Push Docker Image" workflow
|
||||||
|
- Wait ~30-45 minutes for multi-platform build
|
||||||
|
|
||||||
|
4. **Test GHCR Image**
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
docker run -d -p 3000:3000 --env-file .env.production ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Create First Release**
|
||||||
|
```bash
|
||||||
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Check Security**
|
||||||
|
- Wait for daily scan or trigger manually
|
||||||
|
- Check GitHub → Security → Code scanning alerts
|
||||||
|
|
||||||
|
## Support Resources
|
||||||
|
|
||||||
|
- **Docker Guide:** [DOCKER.md](../DOCKER.md)
|
||||||
|
- **Quick Start:** [QUICKSTART.md](../QUICKSTART.md)
|
||||||
|
- **Development:** [CLAUDE.md](../CLAUDE.md)
|
||||||
|
- **Workflows:** [.github/workflows/README.md](workflows/README.md)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Build takes too long**
|
||||||
|
- Multi-platform builds take 30-45 minutes (normal)
|
||||||
|
- Consider using self-hosted runners
|
||||||
|
|
||||||
|
2. **Permission denied on push**
|
||||||
|
- Check Settings → Actions → General → Workflow permissions
|
||||||
|
- Enable "Read and write permissions"
|
||||||
|
|
||||||
|
3. **Image not found**
|
||||||
|
- For private repos, login to GHCR first
|
||||||
|
- Check package exists at github.com/valknarxxx?tab=packages
|
||||||
|
|
||||||
|
4. **Container exits immediately**
|
||||||
|
- Check logs: `docker logs <container>`
|
||||||
|
- Verify environment variables
|
||||||
|
- Ensure port 3000 is not in use
|
||||||
|
|
||||||
|
See [DOCKER.md](../DOCKER.md) for detailed troubleshooting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Created:** 2025-10-25
|
||||||
|
**Last Updated:** 2025-10-25
|
||||||
|
**Status:** ✅ Ready for production
|
||||||
344
.github/workflows/README.md
vendored
Normal file
344
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
# GitHub Actions Workflows
|
||||||
|
|
||||||
|
This directory contains automated workflows for building, scanning, and managing Docker images for sexy.pivoine.art.
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
### 1. Build and Push Docker Image (`docker-build-push.yml`)
|
||||||
|
|
||||||
|
**Triggers:**
|
||||||
|
- Push to `main` or `develop` branches
|
||||||
|
- New version tags (e.g., `v1.0.0`)
|
||||||
|
- Pull requests to `main`
|
||||||
|
- Manual trigger via workflow_dispatch
|
||||||
|
|
||||||
|
**What it does:**
|
||||||
|
- Builds multi-platform Docker images (AMD64 + ARM64)
|
||||||
|
- Pushes to GitHub Container Registry as `ghcr.io/valknarxxx/sexy`
|
||||||
|
- Creates tags based on branch, version, and commit SHA
|
||||||
|
- Uses build cache for faster builds
|
||||||
|
- Only builds (doesn't push) for PRs
|
||||||
|
|
||||||
|
**Image Tags:**
|
||||||
|
- `latest` - Latest build from main branch
|
||||||
|
- `main`, `develop` - Branch-based tags
|
||||||
|
- `v1.0.0`, `v1.0`, `v1` - Semantic version tags
|
||||||
|
- `main-abc123` - Branch + commit SHA
|
||||||
|
- `pr-123` - Pull request builds
|
||||||
|
- Custom tags via manual trigger
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Automatically triggered on push to main
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# Create a release tag
|
||||||
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
|
||||||
|
# Manual trigger from GitHub UI
|
||||||
|
# Actions → Build and Push Docker Image → Run workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pulling images:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Latest version
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
|
||||||
|
# Specific version
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:v1.0.0
|
||||||
|
|
||||||
|
# Specific branch
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:develop
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Docker Image Security Scan (`docker-scan.yml`)
|
||||||
|
|
||||||
|
**Triggers:**
|
||||||
|
- Daily at 2 AM UTC (scheduled)
|
||||||
|
- Push to `main` branch
|
||||||
|
- New version tags
|
||||||
|
- Manual trigger
|
||||||
|
|
||||||
|
**What it does:**
|
||||||
|
- Scans the latest image for vulnerabilities using Trivy
|
||||||
|
- Reports CRITICAL and HIGH severity issues
|
||||||
|
- Uploads results to GitHub Security tab
|
||||||
|
- Runs on a schedule to detect new vulnerabilities
|
||||||
|
|
||||||
|
**Viewing results:**
|
||||||
|
- Go to repository → Security → Code scanning alerts
|
||||||
|
- Check workflow run summary for table output
|
||||||
|
|
||||||
|
**Manual scan:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From GitHub UI
|
||||||
|
# Actions → Docker Image Security Scan → Run workflow
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Cleanup Old Docker Images (`cleanup-images.yml`)
|
||||||
|
|
||||||
|
**Triggers:**
|
||||||
|
- Weekly on Sunday at 3 AM UTC
|
||||||
|
- Manual trigger
|
||||||
|
|
||||||
|
**What it does:**
|
||||||
|
- Removes old untagged image versions
|
||||||
|
- Keeps the 10 most recent versions by default
|
||||||
|
- Frees up GitHub Container Registry storage
|
||||||
|
|
||||||
|
**Manual cleanup:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From GitHub UI with custom retention
|
||||||
|
# Actions → Cleanup Old Docker Images → Run workflow
|
||||||
|
# Set "keep_count" parameter (default: 10)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Requirements
|
||||||
|
|
||||||
|
### 1. Enable GitHub Container Registry
|
||||||
|
|
||||||
|
The workflows automatically use GitHub's Container Registry (ghcr.io). No additional setup needed - the `GITHUB_TOKEN` is automatically provided to workflows.
|
||||||
|
|
||||||
|
### 2. Repository Settings
|
||||||
|
|
||||||
|
Ensure the following permissions are enabled:
|
||||||
|
|
||||||
|
1. **Settings → Actions → General**
|
||||||
|
- Allow GitHub Actions: ✅ Enabled
|
||||||
|
- Workflow permissions: "Read and write permissions"
|
||||||
|
|
||||||
|
2. **Settings → Packages**
|
||||||
|
- Package visibility will inherit from repository visibility
|
||||||
|
- Can be changed to public/private as needed
|
||||||
|
|
||||||
|
### 3. Branch Protection (Optional)
|
||||||
|
|
||||||
|
For production use, consider:
|
||||||
|
|
||||||
|
1. **Settings → Branches → Branch protection rules**
|
||||||
|
- Protect `main` branch
|
||||||
|
- Require PR reviews
|
||||||
|
- Require status checks (Docker build) to pass
|
||||||
|
|
||||||
|
## Secrets and Environment Variables
|
||||||
|
|
||||||
|
### Required Secrets
|
||||||
|
|
||||||
|
None! The workflows use the built-in `GITHUB_TOKEN` which is automatically provided.
|
||||||
|
|
||||||
|
### Optional Secrets
|
||||||
|
|
||||||
|
If you need to deploy to production automatically, you can add:
|
||||||
|
|
||||||
|
- `PRODUCTION_SSH_KEY` - For SSH deployment
|
||||||
|
- `PRODUCTION_HOST` - Production server hostname
|
||||||
|
- `PRODUCTION_USER` - Production server user
|
||||||
|
|
||||||
|
## Image Visibility
|
||||||
|
|
||||||
|
By default, GitHub Container Registry packages inherit repository visibility:
|
||||||
|
|
||||||
|
- **Public repository** → Public images
|
||||||
|
- **Private repository** → Private images
|
||||||
|
|
||||||
|
To change package visibility:
|
||||||
|
|
||||||
|
1. Go to https://github.com/users/valknarxxx/packages/container/sexy/settings
|
||||||
|
2. Change visibility under "Danger Zone"
|
||||||
|
|
||||||
|
## Pulling Images
|
||||||
|
|
||||||
|
### Public Images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Private Images
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create a GitHub Personal Access Token with read:packages scope
|
||||||
|
# Settings → Developer settings → Personal access tokens → Tokens (classic)
|
||||||
|
# Scopes: read:packages
|
||||||
|
|
||||||
|
# 2. Login to GitHub Container Registry
|
||||||
|
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
|
||||||
|
|
||||||
|
# 3. Pull the image
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Version Tagging
|
||||||
|
|
||||||
|
Use semantic versioning for releases:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Major release (breaking changes)
|
||||||
|
git tag v2.0.0
|
||||||
|
|
||||||
|
# Minor release (new features)
|
||||||
|
git tag v1.1.0
|
||||||
|
|
||||||
|
# Patch release (bug fixes)
|
||||||
|
git tag v1.0.1
|
||||||
|
|
||||||
|
# Always push tags
|
||||||
|
git push origin --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create feature branch
|
||||||
|
git checkout -b feature/new-feature
|
||||||
|
|
||||||
|
# 2. Make changes and commit
|
||||||
|
git add .
|
||||||
|
git commit -m "Add new feature"
|
||||||
|
|
||||||
|
# 3. Push and create PR
|
||||||
|
git push origin feature/new-feature
|
||||||
|
# Create PR on GitHub - this triggers a test build
|
||||||
|
|
||||||
|
# 4. Merge to develop for staging
|
||||||
|
# Merging to develop triggers a build with 'develop' tag
|
||||||
|
|
||||||
|
# 5. Merge to main for production
|
||||||
|
# Merging to main triggers a build with 'latest' tag
|
||||||
|
|
||||||
|
# 6. Tag releases
|
||||||
|
git checkout main
|
||||||
|
git pull
|
||||||
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
# This triggers a build with version tags
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Build Status
|
||||||
|
|
||||||
|
Check workflow status:
|
||||||
|
- Repository → Actions → Select workflow
|
||||||
|
- View logs, artifacts, and summaries
|
||||||
|
|
||||||
|
### Image Registry
|
||||||
|
|
||||||
|
View published images:
|
||||||
|
- https://github.com/valknarxxx?tab=packages
|
||||||
|
- Or repository → Packages
|
||||||
|
|
||||||
|
### Security Alerts
|
||||||
|
|
||||||
|
View vulnerability scans:
|
||||||
|
- Repository → Security → Code scanning alerts
|
||||||
|
- Filter by "Trivy" tool
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build Failures
|
||||||
|
|
||||||
|
**Problem:** Build fails at Rust installation
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Check GitHub Actions runner status
|
||||||
|
- Verify Dockerfile Rust installation steps
|
||||||
|
- Check build logs for network issues
|
||||||
|
|
||||||
|
**Problem:** Permission denied when pushing
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Verify repository Settings → Actions → General → Workflow permissions
|
||||||
|
- Ensure "Read and write permissions" is enabled
|
||||||
|
|
||||||
|
**Problem:** Multi-platform build timeout
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Builds can take 30-45 minutes for multi-platform
|
||||||
|
- This is normal for Rust/WASM compilation
|
||||||
|
- Consider splitting platforms or using self-hosted runners
|
||||||
|
|
||||||
|
### Pull Failures
|
||||||
|
|
||||||
|
**Problem:** Cannot pull private image
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Ensure you're logged in
|
||||||
|
echo $GITHUB_TOKEN | docker login ghcr.io -u valknarxxx --password-stdin
|
||||||
|
|
||||||
|
# Verify token has read:packages scope
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem:** Image not found
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Check image exists: https://github.com/valknarxxx?tab=packages
|
||||||
|
- Verify tag name is correct
|
||||||
|
- Ensure you have access to private packages
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### Self-Hosted Runners
|
||||||
|
|
||||||
|
For faster builds, use self-hosted runners:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: self-hosted # Change from ubuntu-latest
|
||||||
|
# ... rest of job
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build-time Variables
|
||||||
|
|
||||||
|
Pass build arguments:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
build-args: |
|
||||||
|
NODE_ENV=production
|
||||||
|
CUSTOM_VAR=value
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy on Push
|
||||||
|
|
||||||
|
Add a deployment job:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
needs: build-and-push
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
steps:
|
||||||
|
- name: Deploy to production
|
||||||
|
# Add deployment steps
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cost Optimization
|
||||||
|
|
||||||
|
### Storage Limits
|
||||||
|
|
||||||
|
GitHub provides:
|
||||||
|
- **Public repos:** Unlimited storage and bandwidth
|
||||||
|
- **Private repos:** 500MB storage, 1GB bandwidth (free tier)
|
||||||
|
|
||||||
|
### Optimization Tips
|
||||||
|
|
||||||
|
1. **Regular cleanup:** Run cleanup workflow weekly
|
||||||
|
2. **Limit platforms:** Build only needed architectures
|
||||||
|
3. **Use cache:** BuildKit cache reduces rebuild time
|
||||||
|
4. **Minimize layers:** Optimize Dockerfile
|
||||||
|
|
||||||
|
### Monitoring Usage
|
||||||
|
|
||||||
|
Check package storage:
|
||||||
|
- Settings → Billing → Packages
|
||||||
|
- View storage usage per package
|
||||||
42
.github/workflows/cleanup-images.yml
vendored
Normal file
42
.github/workflows/cleanup-images.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Cleanup Old Docker Images
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run weekly on Sunday at 3 AM UTC
|
||||||
|
- cron: '0 3 * * 0'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
keep_count:
|
||||||
|
description: 'Number of recent images to keep (per tag pattern)'
|
||||||
|
required: false
|
||||||
|
default: '10'
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: valknarxxx/sexy
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Delete old container images
|
||||||
|
uses: actions/delete-package-versions@v5
|
||||||
|
with:
|
||||||
|
package-name: 'sexy'
|
||||||
|
package-type: 'container'
|
||||||
|
min-versions-to-keep: ${{ github.event.inputs.keep_count || 10 }}
|
||||||
|
delete-only-untagged-versions: 'true'
|
||||||
|
|
||||||
|
- name: Generate cleanup summary
|
||||||
|
run: |
|
||||||
|
echo "### Docker Image Cleanup :broom:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Registry:** \`${{ env.REGISTRY }}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Image:** \`${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Kept versions:** ${{ github.event.inputs.keep_count || 10 }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Cleanup Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Old untagged images have been removed to free up storage." >> $GITHUB_STEP_SUMMARY
|
||||||
115
.github/workflows/docker-build-push.yml
vendored
Normal file
115
.github/workflows/docker-build-push.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Custom tag for the image'
|
||||||
|
required: false
|
||||||
|
default: 'manual'
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: valknarxxx/sexy
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels)
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
# Tag as 'latest' for main branch
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
# Tag with branch name
|
||||||
|
type=ref,event=branch
|
||||||
|
# Tag with PR number
|
||||||
|
type=ref,event=pr
|
||||||
|
# Tag with git tag (semver)
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
# Tag with commit SHA
|
||||||
|
type=sha,prefix={{branch}}-
|
||||||
|
# Custom tag from workflow_dispatch
|
||||||
|
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.title=sexy.pivoine.art
|
||||||
|
org.opencontainers.image.description=Adult content platform with SvelteKit, Directus, and hardware integration
|
||||||
|
org.opencontainers.image.vendor=valknarxxx
|
||||||
|
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
build-args: |
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
- name: Generate image digest
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
run: |
|
||||||
|
echo "### Docker Image Published :rocket:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Registry:** \`${{ env.REGISTRY }}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Image:** \`${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Pull command:**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: PR Comment - Image built but not pushed
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
run: |
|
||||||
|
echo "### Docker Image Built Successfully :white_check_mark:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Image was built successfully but **not pushed** (PR builds are not published)." >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Would be tagged as:**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
71
.github/workflows/docker-scan.yml
vendored
Normal file
71
.github/workflows/docker-scan.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: Docker Image Security Scan
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run daily at 2 AM UTC
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: valknarxxx/sexy
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
scan:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Pull latest image
|
||||||
|
run: |
|
||||||
|
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest || echo "Image not found, will skip scan"
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
format: 'sarif'
|
||||||
|
output: 'trivy-results.sarif'
|
||||||
|
severity: 'CRITICAL,HIGH'
|
||||||
|
|
||||||
|
- name: Upload Trivy results to GitHub Security
|
||||||
|
uses: github/codeql-action/upload-sarif@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
sarif_file: 'trivy-results.sarif'
|
||||||
|
|
||||||
|
- name: Run Trivy vulnerability scanner (table output)
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
format: 'table'
|
||||||
|
severity: 'CRITICAL,HIGH,MEDIUM'
|
||||||
|
|
||||||
|
- name: Generate scan summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "### Security Scan Results :shield:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Image:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Scan Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Check the Security tab for detailed vulnerability reports." >> $GITHUB_STEP_SUMMARY
|
||||||
180
CLAUDE.md
Normal file
180
CLAUDE.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a monorepo for an adult content platform built with SvelteKit, Directus CMS, and hardware integration via Buttplug.io. The project uses pnpm workspaces with three main packages.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Install Node.js 20.19.1
|
||||||
|
2. Enable corepack: `corepack enable`
|
||||||
|
3. Install dependencies: `pnpm install`
|
||||||
|
4. Install Rust toolchain and wasm-bindgen: `cargo install wasm-bindgen-cli`
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Packages
|
||||||
|
|
||||||
|
- **`packages/frontend`**: SvelteKit application (main frontend)
|
||||||
|
- **`packages/bundle`**: Directus extension bundle (custom endpoints, hooks, themes)
|
||||||
|
- **`packages/buttplug`**: Hardware control library with TypeScript/WebAssembly bindings
|
||||||
|
|
||||||
|
### Frontend (SvelteKit + Tailwind CSS 4)
|
||||||
|
|
||||||
|
- **Framework**: SvelteKit 2 with adapter-node
|
||||||
|
- **Styling**: Tailwind CSS v4 via @tailwindcss/vite
|
||||||
|
- **UI Components**: bits-ui, custom components in `src/lib/components/ui/`
|
||||||
|
- **Backend**: Directus headless CMS
|
||||||
|
- **Routes**: File-based routing in `src/routes/`
|
||||||
|
- `+page.server.ts`: Server-side data loading
|
||||||
|
- `+layout.server.ts`: Layout data (authentication, etc.)
|
||||||
|
- **Authentication**: Session-based via Directus SDK (cookies)
|
||||||
|
- **API Proxy**: Dev server proxies `/api` to `http://localhost:8055` (Directus)
|
||||||
|
- **i18n**: svelte-i18n for internationalization
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- `src/lib/directus.ts`: Directus client configuration
|
||||||
|
- `src/lib/types.ts`: Shared TypeScript types
|
||||||
|
- `src/hooks.server.ts`: Server-side auth middleware
|
||||||
|
- `vite.config.ts`: Dev server on port 3000 with API proxy
|
||||||
|
|
||||||
|
### Bundle (Directus Extensions)
|
||||||
|
|
||||||
|
Custom Directus extensions providing:
|
||||||
|
- **Endpoint** (`src/endpoint/index.ts`): `/sexy/stats` endpoint for platform statistics
|
||||||
|
- **Hook** (`src/hook/index.ts`):
|
||||||
|
- Auto-generates slugs for users based on artist_name
|
||||||
|
- Processes uploaded videos with ffmpeg to extract duration
|
||||||
|
- **Theme** (`src/theme/index.ts`): Custom Directus admin theme
|
||||||
|
|
||||||
|
### Buttplug (Hardware Control)
|
||||||
|
|
||||||
|
Hybrid TypeScript/Rust package for intimate hardware control:
|
||||||
|
- **TypeScript**: Client library, connectors (WebSocket, Browser WebSocket)
|
||||||
|
- **Rust/WASM**: Core buttplug implementation compiled to WebAssembly
|
||||||
|
- Provides browser-based Bluetooth device control via WebBluetooth API
|
||||||
|
|
||||||
|
Key concepts:
|
||||||
|
- `ButtplugClient`: Main client interface
|
||||||
|
- `ButtplugClientDevice`: Device abstraction
|
||||||
|
- `ButtplugWasmClientConnector`: WASM-based connector
|
||||||
|
- Messages defined in `src/core/Messages.ts`
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
Start full development environment (data + Directus + frontend):
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Individual services:
|
||||||
|
```bash
|
||||||
|
pnpm dev:data # Start Docker Compose data services
|
||||||
|
pnpm dev:directus # Start Directus in Docker
|
||||||
|
pnpm --filter @sexy.pivoine.art/frontend dev # Frontend dev server only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
Build all packages:
|
||||||
|
```bash
|
||||||
|
pnpm install # Ensure dependencies are installed first
|
||||||
|
```
|
||||||
|
|
||||||
|
Build specific packages:
|
||||||
|
```bash
|
||||||
|
pnpm build:frontend # Pulls git, installs, builds frontend
|
||||||
|
pnpm build:bundle # Pulls git, installs, builds Directus extensions
|
||||||
|
```
|
||||||
|
|
||||||
|
Individual package builds:
|
||||||
|
```bash
|
||||||
|
pnpm --filter @sexy.pivoine.art/frontend build
|
||||||
|
pnpm --filter @sexy.pivoine.art/bundle build
|
||||||
|
pnpm --filter @sexy.pivoine.art/buttplug build # TypeScript build
|
||||||
|
pnpm --filter @sexy.pivoine.art/buttplug build:wasm # Rust WASM build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
Start production frontend server (local):
|
||||||
|
```bash
|
||||||
|
pnpm --filter @sexy.pivoine.art/frontend start
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker deployment (recommended for production):
|
||||||
|
```bash
|
||||||
|
# Build Docker image
|
||||||
|
docker build -t sexy.pivoine.art:latest .
|
||||||
|
|
||||||
|
# Run with docker-compose
|
||||||
|
docker-compose -f docker-compose.production.yml up -d
|
||||||
|
|
||||||
|
# Or run directly
|
||||||
|
docker run -d -p 3000:3000 --env-file .env.production sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
See `DOCKER.md` for comprehensive Docker deployment guide.
|
||||||
|
|
||||||
|
## Architecture Notes
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
1. **Frontend** → `/api/*` (proxied) → **Directus CMS**
|
||||||
|
2. Directus uses **bundle extensions** for custom logic (stats, video processing, user management)
|
||||||
|
3. Frontend uses **Directus SDK** with session authentication
|
||||||
|
4. Hardware control uses **buttplug package** (TypeScript → WASM → Bluetooth)
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
- Session tokens stored in `directus_session_token` cookie
|
||||||
|
- `hooks.server.ts` validates token on every request via `isAuthenticated()`
|
||||||
|
- User roles: Model, Viewer (checked via role or policy)
|
||||||
|
- `isModel()` helper in `src/lib/directus.ts` checks user permissions
|
||||||
|
|
||||||
|
### Content Types
|
||||||
|
|
||||||
|
Core types in `packages/frontend/src/lib/types.ts`:
|
||||||
|
- **User/CurrentUser**: User profiles with roles and policies
|
||||||
|
- **Video**: Videos with models, tags, premium flag
|
||||||
|
- **Model**: Creator profiles with photos and banner
|
||||||
|
- **Article**: Magazine/blog content
|
||||||
|
- **BluetoothDevice**: Hardware device state
|
||||||
|
|
||||||
|
### Docker Environment
|
||||||
|
|
||||||
|
Development uses Docker Compose in `../compose/` directory:
|
||||||
|
- `../compose/data`: Database/storage services
|
||||||
|
- `../compose/sexy`: Directus instance (uses `.env.local`)
|
||||||
|
|
||||||
|
### Asset URLs
|
||||||
|
|
||||||
|
Assets served via Directus with transforms:
|
||||||
|
```typescript
|
||||||
|
getAssetUrl(id, "thumbnail" | "preview" | "medium" | "banner")
|
||||||
|
// Returns: ${directusApiUrl}/assets/${id}?transform=...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. Ensure Docker services are running: `pnpm dev:data && pnpm dev:directus`
|
||||||
|
2. Start frontend dev server: `pnpm --filter @sexy.pivoine.art/frontend dev`
|
||||||
|
3. Access frontend at `http://localhost:3000`
|
||||||
|
4. Access Directus admin at `http://localhost:8055`
|
||||||
|
|
||||||
|
When modifying:
|
||||||
|
- **Frontend code**: Hot reload via Vite
|
||||||
|
- **Bundle extensions**: Rebuild with `pnpm --filter @sexy.pivoine.art/bundle build` and restart Directus
|
||||||
|
- **Buttplug library**: Rebuild TypeScript (`pnpm build`) and/or WASM (`pnpm build:wasm`)
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- This is a pnpm workspace; always use `pnpm` not `npm` or `yarn`
|
||||||
|
- Package manager is locked to `pnpm@10.17.0`
|
||||||
|
- Buttplug package requires Rust toolchain for WASM builds
|
||||||
|
- Frontend uses SvelteKit's adapter-node for production deployment
|
||||||
|
- All TypeScript packages use ES modules (`"type": "module"`)
|
||||||
378
DOCKER.md
Normal file
378
DOCKER.md
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
# Docker Deployment Guide
|
||||||
|
|
||||||
|
This guide covers building and deploying sexy.pivoine.art using Docker.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Dockerfile uses a multi-stage build process:
|
||||||
|
|
||||||
|
1. **Base stage**: Sets up Node.js and pnpm
|
||||||
|
2. **Builder stage**: Installs Rust, compiles WASM, builds all packages
|
||||||
|
3. **Runner stage**: Minimal production image with only runtime dependencies
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker 20.10+ with BuildKit support
|
||||||
|
- Docker Compose 2.0+ (optional, for orchestration)
|
||||||
|
|
||||||
|
## Building the Image
|
||||||
|
|
||||||
|
### Basic Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t sexy.pivoine.art:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build with Build Arguments
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build \
|
||||||
|
--build-arg NODE_ENV=production \
|
||||||
|
-t sexy.pivoine.art:latest \
|
||||||
|
.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-platform Build (for ARM64 and AMD64)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
-t sexy.pivoine.art:latest \
|
||||||
|
--push \
|
||||||
|
.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Container
|
||||||
|
|
||||||
|
### Run with Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name sexy-pivoine-frontend \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-e PUBLIC_API_URL=https://api.pivoine.art \
|
||||||
|
-e PUBLIC_URL=https://sexy.pivoine.art \
|
||||||
|
-e PUBLIC_UMAMI_ID=your-umami-id \
|
||||||
|
-e LETTERSPACE_API_URL=https://api.letterspace.com/v1 \
|
||||||
|
-e LETTERSPACE_API_KEY=your-api-key \
|
||||||
|
-e LETTERSPACE_LIST_ID=your-list-id \
|
||||||
|
sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run with Environment File
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create .env.production from template
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
|
||||||
|
# Edit .env.production with your values
|
||||||
|
nano .env.production
|
||||||
|
|
||||||
|
# Run container
|
||||||
|
docker run -d \
|
||||||
|
--name sexy-pivoine-frontend \
|
||||||
|
-p 3000:3000 \
|
||||||
|
--env-file .env.production \
|
||||||
|
sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Compose Deployment
|
||||||
|
|
||||||
|
### Using docker-compose.production.yml
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create environment file
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
|
||||||
|
# 2. Edit environment variables
|
||||||
|
nano .env.production
|
||||||
|
|
||||||
|
# 3. Build and start
|
||||||
|
docker-compose -f docker-compose.production.yml up -d --build
|
||||||
|
|
||||||
|
# 4. View logs
|
||||||
|
docker-compose -f docker-compose.production.yml logs -f frontend
|
||||||
|
|
||||||
|
# 5. Stop services
|
||||||
|
docker-compose -f docker-compose.production.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scale the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.production.yml up -d --scale frontend=3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables Reference
|
||||||
|
|
||||||
|
### Required Variables
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `PUBLIC_API_URL` | Directus API backend URL | `https://api.pivoine.art` |
|
||||||
|
| `PUBLIC_URL` | Frontend application URL | `https://sexy.pivoine.art` |
|
||||||
|
|
||||||
|
### Optional Variables
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `PUBLIC_UMAMI_ID` | Umami analytics tracking ID | `abc123def-456` |
|
||||||
|
| `LETTERSPACE_API_URL` | Letterspace API endpoint | `https://api.letterspace.com/v1` |
|
||||||
|
| `LETTERSPACE_API_KEY` | Letterspace authentication key | `sk_live_...` |
|
||||||
|
| `LETTERSPACE_LIST_ID` | Mailing list identifier | `list_abc123` |
|
||||||
|
| `PORT` | Application port (inside container) | `3000` |
|
||||||
|
| `HOST` | Host binding | `0.0.0.0` |
|
||||||
|
| `NODE_ENV` | Node environment | `production` |
|
||||||
|
|
||||||
|
## Health Checks
|
||||||
|
|
||||||
|
The container includes a built-in health check that pings the HTTP server every 30 seconds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check container health
|
||||||
|
docker inspect --format='{{.State.Health.Status}}' sexy-pivoine-frontend
|
||||||
|
|
||||||
|
# View health check logs
|
||||||
|
docker inspect --format='{{json .State.Health}}' sexy-pivoine-frontend | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logs and Debugging
|
||||||
|
|
||||||
|
### View Container Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Follow logs
|
||||||
|
docker logs -f sexy-pivoine-frontend
|
||||||
|
|
||||||
|
# Last 100 lines
|
||||||
|
docker logs --tail 100 sexy-pivoine-frontend
|
||||||
|
|
||||||
|
# With timestamps
|
||||||
|
docker logs -f --timestamps sexy-pivoine-frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execute Commands in Running Container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Open shell
|
||||||
|
docker exec -it sexy-pivoine-frontend sh
|
||||||
|
|
||||||
|
# Check Node.js version
|
||||||
|
docker exec sexy-pivoine-frontend node --version
|
||||||
|
|
||||||
|
# Check environment variables
|
||||||
|
docker exec sexy-pivoine-frontend env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug Build Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build with no cache
|
||||||
|
docker build --no-cache -t sexy.pivoine.art:latest .
|
||||||
|
|
||||||
|
# Build specific stage for debugging
|
||||||
|
docker build --target builder -t sexy.pivoine.art:builder .
|
||||||
|
|
||||||
|
# Inspect builder stage
|
||||||
|
docker run -it --rm sexy.pivoine.art:builder sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Best Practices
|
||||||
|
|
||||||
|
### 1. Use Specific Tags
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tag with version
|
||||||
|
docker build -t sexy.pivoine.art:1.0.0 .
|
||||||
|
docker tag sexy.pivoine.art:1.0.0 sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Image Scanning
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scan for vulnerabilities (requires Docker Scout or Trivy)
|
||||||
|
docker scout cves sexy.pivoine.art:latest
|
||||||
|
|
||||||
|
# Or with Trivy
|
||||||
|
trivy image sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Resource Limits
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name sexy-pivoine-frontend \
|
||||||
|
-p 3000:3000 \
|
||||||
|
--memory="2g" \
|
||||||
|
--cpus="2" \
|
||||||
|
--env-file .env.production \
|
||||||
|
sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Restart Policies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name sexy-pivoine-frontend \
|
||||||
|
--restart=unless-stopped \
|
||||||
|
-p 3000:3000 \
|
||||||
|
--env-file .env.production \
|
||||||
|
sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Use Docker Secrets (Docker Swarm)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create secrets
|
||||||
|
echo "your-api-key" | docker secret create letterspace_api_key -
|
||||||
|
|
||||||
|
# Deploy with secrets
|
||||||
|
docker service create \
|
||||||
|
--name sexy-pivoine-frontend \
|
||||||
|
--secret letterspace_api_key \
|
||||||
|
-p 3000:3000 \
|
||||||
|
sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Optimization Tips
|
||||||
|
|
||||||
|
### Reduce Build Time
|
||||||
|
|
||||||
|
1. **Use BuildKit cache mounts** (already enabled in Dockerfile)
|
||||||
|
2. **Leverage layer caching** - structure Dockerfile to cache dependencies
|
||||||
|
3. **Use `.dockerignore`** - exclude unnecessary files from build context
|
||||||
|
|
||||||
|
### Reduce Image Size
|
||||||
|
|
||||||
|
Current optimizations:
|
||||||
|
- Multi-stage build (builder artifacts not in final image)
|
||||||
|
- Production-only dependencies (`pnpm install --prod`)
|
||||||
|
- Minimal base image (`node:20.19.1-slim`)
|
||||||
|
- Only necessary build artifacts copied to runner
|
||||||
|
|
||||||
|
Image size breakdown:
|
||||||
|
```bash
|
||||||
|
docker images sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD Integration
|
||||||
|
|
||||||
|
### GitHub Actions (Automated)
|
||||||
|
|
||||||
|
This repository includes automated GitHub Actions workflows for building, scanning, and managing Docker images.
|
||||||
|
|
||||||
|
**Pre-configured workflows:**
|
||||||
|
- **Build & Push** (`.github/workflows/docker-build-push.yml`)
|
||||||
|
- Automatically builds and pushes to `ghcr.io/valknarxxx/sexy`
|
||||||
|
- Triggers on push to main/develop, version tags, and PRs
|
||||||
|
- Multi-platform builds (AMD64 + ARM64)
|
||||||
|
- Smart tagging: latest, branch names, semver, commit SHAs
|
||||||
|
|
||||||
|
- **Security Scan** (`.github/workflows/docker-scan.yml`)
|
||||||
|
- Daily vulnerability scans with Trivy
|
||||||
|
- Reports to GitHub Security tab
|
||||||
|
- Scans on every release
|
||||||
|
|
||||||
|
- **Cleanup** (`.github/workflows/cleanup-images.yml`)
|
||||||
|
- Weekly cleanup of old untagged images
|
||||||
|
- Keeps last 10 versions
|
||||||
|
|
||||||
|
**Using pre-built images:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull latest from GitHub Container Registry
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
|
||||||
|
# Pull specific version
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:v1.0.0
|
||||||
|
|
||||||
|
# Run the image
|
||||||
|
docker run -d -p 3000:3000 --env-file .env.production ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Triggering builds:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Push to main → builds 'latest' tag
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# Create version tag → builds semver tags
|
||||||
|
git tag v1.0.0 && git push origin v1.0.0
|
||||||
|
|
||||||
|
# Pull request → builds but doesn't push
|
||||||
|
```
|
||||||
|
|
||||||
|
See `.github/workflows/README.md` for detailed workflow documentation.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build Fails at Rust Installation
|
||||||
|
|
||||||
|
**Problem**: Rust installation fails or times out
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
- Check internet connectivity
|
||||||
|
- Use a Rust mirror if in restricted network
|
||||||
|
- Increase build timeout
|
||||||
|
|
||||||
|
### WASM Build Fails
|
||||||
|
|
||||||
|
**Problem**: `wasm-bindgen-cli` version mismatch
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```dockerfile
|
||||||
|
# In Dockerfile, pin wasm-bindgen-cli version
|
||||||
|
RUN cargo install wasm-bindgen-cli --version 0.2.103
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Exits Immediately
|
||||||
|
|
||||||
|
**Problem**: Container starts then exits
|
||||||
|
|
||||||
|
**Solution**: Check logs and verify:
|
||||||
|
```bash
|
||||||
|
docker logs sexy-pivoine-frontend
|
||||||
|
|
||||||
|
# Verify build output exists
|
||||||
|
docker run -it --rm sexy.pivoine.art:latest ls -la packages/frontend/build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Port Already in Use
|
||||||
|
|
||||||
|
**Problem**: Port 3000 already bound
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```bash
|
||||||
|
# Use different host port
|
||||||
|
docker run -d -p 8080:3000 sexy.pivoine.art:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
### Clean Up
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove stopped containers
|
||||||
|
docker container prune
|
||||||
|
|
||||||
|
# Remove unused images
|
||||||
|
docker image prune -a
|
||||||
|
|
||||||
|
# Remove build cache
|
||||||
|
docker builder prune
|
||||||
|
|
||||||
|
# Complete cleanup (use with caution)
|
||||||
|
docker system prune -a --volumes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Base Image
|
||||||
|
|
||||||
|
Regularly update the base Node.js image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull latest Node 20 LTS
|
||||||
|
docker pull node:20.19.1-slim
|
||||||
|
|
||||||
|
# Rebuild
|
||||||
|
docker build --pull -t sexy.pivoine.art:latest .
|
||||||
|
```
|
||||||
135
Dockerfile
Normal file
135
Dockerfile
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Base stage - shared dependencies
|
||||||
|
# ============================================================================
|
||||||
|
FROM node:20.19.1-slim AS base
|
||||||
|
|
||||||
|
# Enable corepack for pnpm
|
||||||
|
RUN corepack enable
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy workspace configuration
|
||||||
|
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Builder stage - compile application with Rust/WASM support
|
||||||
|
# ============================================================================
|
||||||
|
FROM base AS builder
|
||||||
|
|
||||||
|
# Install build dependencies for Rust and native modules
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
build-essential \
|
||||||
|
pkg-config \
|
||||||
|
libssl-dev \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Rust toolchain
|
||||||
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
|
||||||
|
--default-toolchain stable \
|
||||||
|
--profile minimal \
|
||||||
|
--target wasm32-unknown-unknown
|
||||||
|
|
||||||
|
# Add Rust to PATH
|
||||||
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
|
# Install wasm-bindgen-cli
|
||||||
|
RUN cargo install wasm-bindgen-cli
|
||||||
|
|
||||||
|
# Copy source files
|
||||||
|
COPY packages ./packages
|
||||||
|
|
||||||
|
# Install all dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Build packages in correct order with WASM support
|
||||||
|
# 1. Build buttplug WASM
|
||||||
|
RUN RUSTFLAGS='--cfg getrandom_backend="wasm_js" --cfg=web_sys_unstable_apis' \
|
||||||
|
pnpm --filter @sexy.pivoine.art/buttplug build:wasm
|
||||||
|
|
||||||
|
# 2. Build buttplug TypeScript
|
||||||
|
RUN pnpm --filter @sexy.pivoine.art/buttplug build
|
||||||
|
|
||||||
|
# 3. Build frontend
|
||||||
|
RUN pnpm --filter @sexy.pivoine.art/frontend build
|
||||||
|
|
||||||
|
# 4. Build Directus bundle
|
||||||
|
RUN pnpm --filter @sexy.pivoine.art/bundle build
|
||||||
|
|
||||||
|
# Prune dev dependencies for production
|
||||||
|
RUN pnpm install --prod --frozen-lockfile
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Runner stage - minimal production image
|
||||||
|
# ============================================================================
|
||||||
|
FROM node:20.19.1-slim AS runner
|
||||||
|
|
||||||
|
# Install dumb-init for proper signal handling
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
dumb-init \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN userdel -r node && \
|
||||||
|
groupadd -r -g 1000 node && \
|
||||||
|
useradd -r -u 1000 -g node -m -d /home/node -s /bin/bash node
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
|
||||||
|
# Copy production dependencies and built artifacts from builder
|
||||||
|
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder --chown=node:node /app/package.json ./package.json
|
||||||
|
COPY --from=builder --chown=node:node /app/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||||
|
COPY --from=builder --chown=node:node /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||||
|
|
||||||
|
# Create package directories
|
||||||
|
RUN mkdir -p packages/frontend packages/bundle packages/buttplug
|
||||||
|
|
||||||
|
# Copy frontend artifacts
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/frontend/build ./packages/frontend/build
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/frontend/node_modules ./packages/frontend/node_modules
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/frontend/package.json ./packages/frontend/package.json
|
||||||
|
|
||||||
|
# Copy bundle artifacts
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/bundle/dist ./packages/bundle/dist
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/bundle/node_modules ./packages/bundle/node_modules
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/bundle/package.json ./packages/bundle/package.json
|
||||||
|
|
||||||
|
# Copy buttplug artifacts
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/buttplug/dist ./packages/buttplug/dist
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/buttplug/node_modules ./packages/buttplug/node_modules
|
||||||
|
COPY --from=builder --chown=node:node /app/packages/buttplug/package.json ./packages/buttplug/package.json
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER node
|
||||||
|
|
||||||
|
# Environment variables (with defaults, override at runtime)
|
||||||
|
ENV NODE_ENV=production \
|
||||||
|
PORT=3000 \
|
||||||
|
HOST=0.0.0.0
|
||||||
|
|
||||||
|
# Runtime environment variables (will be passed at container start)
|
||||||
|
ENV PUBLIC_API_URL="" \
|
||||||
|
PUBLIC_URL="" \
|
||||||
|
PUBLIC_UMAMI_ID="" \
|
||||||
|
LETTERSPACE_API_URL="" \
|
||||||
|
LETTERSPACE_API_KEY="" \
|
||||||
|
LETTERSPACE_LIST_ID=""
|
||||||
|
|
||||||
|
# Expose application port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||||
|
CMD node -e "require('http').get('http://localhost:3000/', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||||
|
|
||||||
|
# Use dumb-init to handle signals properly
|
||||||
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["node", "packages/frontend/build/index.js"]
|
||||||
334
QUICKSTART.md
Normal file
334
QUICKSTART.md
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
# Quick Start Guide
|
||||||
|
|
||||||
|
Get sexy.pivoine.art running in under 5 minutes using pre-built Docker images.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker 20.10+
|
||||||
|
- Docker Compose 2.0+ (optional)
|
||||||
|
|
||||||
|
## Option 1: Docker Run (Fastest)
|
||||||
|
|
||||||
|
### Step 1: Pull the Image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create Environment File
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > .env.production << EOF
|
||||||
|
PUBLIC_API_URL=https://api.your-domain.com
|
||||||
|
PUBLIC_URL=https://your-domain.com
|
||||||
|
PUBLIC_UMAMI_ID=
|
||||||
|
LETTERSPACE_API_URL=
|
||||||
|
LETTERSPACE_API_KEY=
|
||||||
|
LETTERSPACE_LIST_ID=
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Run the Container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name sexy-pivoine \
|
||||||
|
-p 3000:3000 \
|
||||||
|
--env-file .env.production \
|
||||||
|
--restart unless-stopped \
|
||||||
|
ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if running
|
||||||
|
docker ps | grep sexy-pivoine
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
docker logs -f sexy-pivoine
|
||||||
|
|
||||||
|
# Test the application
|
||||||
|
curl http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
Your application is now running at `http://localhost:3000` 🎉
|
||||||
|
|
||||||
|
## Option 2: Docker Compose (Recommended)
|
||||||
|
|
||||||
|
### Step 1: Download docker-compose.production.yml
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -O https://raw.githubusercontent.com/valknarxxx/sexy/main/docker-compose.production.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you have the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/sexy.pivoine.art
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create Environment File
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
nano .env.production # Edit with your values
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Start Services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.production.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Monitor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View logs
|
||||||
|
docker-compose -f docker-compose.production.yml logs -f
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
docker-compose -f docker-compose.production.yml ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Your application is now running at `http://localhost:3000` 🎉
|
||||||
|
|
||||||
|
## Accessing Private Images
|
||||||
|
|
||||||
|
If the image is in a private registry:
|
||||||
|
|
||||||
|
### Step 1: Create GitHub Personal Access Token
|
||||||
|
|
||||||
|
1. Go to https://github.com/settings/tokens
|
||||||
|
2. Click "Generate new token (classic)"
|
||||||
|
3. Select scope: `read:packages`
|
||||||
|
4. Generate and copy the token
|
||||||
|
|
||||||
|
### Step 2: Login to GitHub Container Registry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo YOUR_GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Pull and Run
|
||||||
|
|
||||||
|
Now you can pull private images:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `PUBLIC_API_URL` | Directus API endpoint | `https://api.pivoine.art` |
|
||||||
|
| `PUBLIC_URL` | Frontend URL | `https://sexy.pivoine.art` |
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `PUBLIC_UMAMI_ID` | Analytics tracking ID | `abc-123-def` |
|
||||||
|
| `LETTERSPACE_API_URL` | Newsletter API | `https://api.letterspace.com/v1` |
|
||||||
|
| `LETTERSPACE_API_KEY` | Newsletter API key | `sk_live_...` |
|
||||||
|
| `LETTERSPACE_LIST_ID` | Mailing list ID | `list_abc123` |
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### View Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Follow logs (Docker Run)
|
||||||
|
docker logs -f sexy-pivoine
|
||||||
|
|
||||||
|
# Follow logs (Docker Compose)
|
||||||
|
docker-compose -f docker-compose.production.yml logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restart Container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker Run
|
||||||
|
docker restart sexy-pivoine
|
||||||
|
|
||||||
|
# Docker Compose
|
||||||
|
docker-compose -f docker-compose.production.yml restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stop Container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker Run
|
||||||
|
docker stop sexy-pivoine
|
||||||
|
|
||||||
|
# Docker Compose
|
||||||
|
docker-compose -f docker-compose.production.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update to Latest Version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker Run
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
docker stop sexy-pivoine
|
||||||
|
docker rm sexy-pivoine
|
||||||
|
# Then re-run the docker run command from Step 3
|
||||||
|
|
||||||
|
# Docker Compose
|
||||||
|
docker-compose -f docker-compose.production.yml pull
|
||||||
|
docker-compose -f docker-compose.production.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shell Access
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker Run
|
||||||
|
docker exec -it sexy-pivoine sh
|
||||||
|
|
||||||
|
# Docker Compose
|
||||||
|
docker-compose -f docker-compose.production.yml exec frontend sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Image Tags
|
||||||
|
|
||||||
|
| Tag | Description | Use Case |
|
||||||
|
|-----|-------------|----------|
|
||||||
|
| `latest` | Latest stable build from main | Production |
|
||||||
|
| `v1.0.0` | Specific version | Production (pinned) |
|
||||||
|
| `develop` | Latest from develop branch | Staging |
|
||||||
|
| `main-abc123` | Specific commit | Testing |
|
||||||
|
|
||||||
|
**Best Practice:** Use version tags in production for predictable deployments.
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
### 1. Use Version Tags
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Instead of :latest
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add Resource Limits
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name sexy-pivoine \
|
||||||
|
-p 3000:3000 \
|
||||||
|
--env-file .env.production \
|
||||||
|
--memory="2g" \
|
||||||
|
--cpus="2" \
|
||||||
|
--restart unless-stopped \
|
||||||
|
ghcr.io/valknarxxx/sexy:v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Use a Reverse Proxy
|
||||||
|
|
||||||
|
Example with nginx:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name sexy.pivoine.art;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Enable HTTPS
|
||||||
|
|
||||||
|
Use Certbot or similar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
certbot --nginx -d sexy.pivoine.art
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Check
|
||||||
|
|
||||||
|
The container includes a built-in health check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check container health
|
||||||
|
docker inspect --format='{{.State.Health.Status}}' sexy-pivoine
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible statuses:
|
||||||
|
- `starting` - Container just started
|
||||||
|
- `healthy` - Application is responding
|
||||||
|
- `unhealthy` - Application is not responding
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Container Exits Immediately
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
docker logs sexy-pivoine
|
||||||
|
|
||||||
|
# Common issues:
|
||||||
|
# - Missing environment variables
|
||||||
|
# - Port 3000 already in use
|
||||||
|
# - Invalid environment variable values
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cannot Pull Image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For private images, ensure you're logged in
|
||||||
|
docker login ghcr.io
|
||||||
|
|
||||||
|
# Check if image exists
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Port Already in Use
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use a different port
|
||||||
|
docker run -d -p 8080:3000 ghcr.io/valknarxxx/sexy:latest
|
||||||
|
|
||||||
|
# Or find what's using port 3000
|
||||||
|
lsof -i :3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Application Not Accessible
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if container is running
|
||||||
|
docker ps | grep sexy-pivoine
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
docker logs sexy-pivoine
|
||||||
|
|
||||||
|
# Verify port mapping
|
||||||
|
docker port sexy-pivoine
|
||||||
|
|
||||||
|
# Test from inside container
|
||||||
|
docker exec sexy-pivoine wget -O- http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- **Production setup:** See [DOCKER.md](DOCKER.md)
|
||||||
|
- **Development:** See [CLAUDE.md](CLAUDE.md)
|
||||||
|
- **CI/CD:** See [.github/workflows/README.md](.github/workflows/README.md)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Issues:** https://github.com/valknarxxx/sexy/issues
|
||||||
|
- **Discussions:** https://github.com/valknarxxx/sexy/discussions
|
||||||
|
- **Security:** Report privately via GitHub Security tab
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) file for details.
|
||||||
217
README.md
217
README.md
@@ -1,8 +1,213 @@
|
|||||||
# pornsuper
|
# sexy.pivoine.art
|
||||||
|
|
||||||
## Prerequisites
|
An adult content platform built with SvelteKit, Directus CMS, and hardware integration via Buttplug.io.
|
||||||
|
|
||||||
1. Install node 20.19.1
|
[](https://github.com/valknarxxx/sexy/actions/workflows/docker-build-push.yml)
|
||||||
2. `corepack enable`
|
[](https://github.com/valknarxxx/sexy/actions/workflows/docker-scan.yml)
|
||||||
3. `pnpm install`
|
|
||||||
4. `cargo install wasm-bindgen-cli`
|
## Quick Start
|
||||||
|
|
||||||
|
### Using Docker (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull and run the latest image
|
||||||
|
docker pull ghcr.io/valknarxxx/sexy:latest
|
||||||
|
docker run -d -p 3000:3000 \
|
||||||
|
-e PUBLIC_API_URL=https://api.your-domain.com \
|
||||||
|
-e PUBLIC_URL=https://your-domain.com \
|
||||||
|
ghcr.io/valknarxxx/sexy:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
See [QUICKSTART.md](QUICKSTART.md) for detailed instructions.
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
1. Install Node.js 20.19.1
|
||||||
|
2. Enable corepack: `corepack enable`
|
||||||
|
3. Install dependencies: `pnpm install`
|
||||||
|
4. Install Rust and wasm-bindgen: `cargo install wasm-bindgen-cli`
|
||||||
|
|
||||||
|
**Start development environment:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all services (Docker Compose + frontend)
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# Or start individually
|
||||||
|
pnpm dev:data # Start data services
|
||||||
|
pnpm dev:directus # Start Directus CMS
|
||||||
|
pnpm --filter @sexy.pivoine.art/frontend dev # Start frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
Access the application at `http://localhost:3000`
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
This is a pnpm monorepo with three packages:
|
||||||
|
|
||||||
|
- **`packages/frontend`** - SvelteKit application with Tailwind CSS 4
|
||||||
|
- **`packages/bundle`** - Directus extensions (endpoints, hooks, themes)
|
||||||
|
- **`packages/buttplug`** - Hardware control library (TypeScript + Rust/WASM)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **[QUICKSTART.md](QUICKSTART.md)** - Get running in 5 minutes with Docker
|
||||||
|
- **[DOCKER.md](DOCKER.md)** - Comprehensive Docker deployment guide
|
||||||
|
- **[CLAUDE.md](CLAUDE.md)** - Development guide and architecture
|
||||||
|
- **[.github/workflows/README.md](.github/workflows/README.md)** - CI/CD workflows
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Build All Packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm --filter @sexy.pivoine.art/buttplug build:wasm
|
||||||
|
pnpm --filter @sexy.pivoine.art/buttplug build
|
||||||
|
pnpm --filter @sexy.pivoine.art/frontend build
|
||||||
|
pnpm --filter @sexy.pivoine.art/bundle build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Docker Image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using the build script
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
# Or manually
|
||||||
|
docker build -t sexy.pivoine.art:latest .
|
||||||
|
|
||||||
|
# Multi-platform
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 -t sexy.pivoine.art:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Production with Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using docker-compose
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
# Edit .env.production with your values
|
||||||
|
docker-compose -f docker-compose.production.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production without Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build all packages
|
||||||
|
pnpm build:frontend
|
||||||
|
|
||||||
|
# Start the server
|
||||||
|
pnpm --filter @sexy.pivoine.art/frontend start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🎨 Modern SvelteKit frontend with Tailwind CSS 4
|
||||||
|
- 🗄️ Headless CMS powered by Directus
|
||||||
|
- 🎮 Hardware integration via Buttplug.io
|
||||||
|
- 🌐 Multi-platform support (AMD64/ARM64)
|
||||||
|
- 🔒 Session-based authentication
|
||||||
|
- 📱 Responsive design with bits-ui components
|
||||||
|
- 🌍 Internationalization support (i18n)
|
||||||
|
- 📊 Built-in analytics integration (Umami)
|
||||||
|
- 📧 Newsletter integration (Letterspace)
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **Framework:** SvelteKit 2
|
||||||
|
- **Styling:** Tailwind CSS 4
|
||||||
|
- **UI Components:** bits-ui, custom component library
|
||||||
|
- **Build Tool:** Vite
|
||||||
|
- **Deployment:** Node.js adapter
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **CMS:** Directus (headless)
|
||||||
|
- **Extensions:** Custom endpoints, hooks, and themes
|
||||||
|
- **Database:** PostgreSQL (via Directus)
|
||||||
|
|
||||||
|
### Hardware
|
||||||
|
- **Library:** Buttplug.io
|
||||||
|
- **Languages:** TypeScript + Rust (compiled to WASM)
|
||||||
|
- **Protocols:** WebBluetooth API
|
||||||
|
|
||||||
|
### DevOps
|
||||||
|
- **Containerization:** Docker + Docker Compose
|
||||||
|
- **CI/CD:** GitHub Actions
|
||||||
|
- **Registry:** GitHub Container Registry (GHCR)
|
||||||
|
- **Security:** Trivy vulnerability scanning
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `PUBLIC_API_URL` - Directus API endpoint
|
||||||
|
- `PUBLIC_URL` - Frontend application URL
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `PUBLIC_UMAMI_ID` - Umami analytics tracking ID
|
||||||
|
- `LETTERSPACE_API_URL` - Newsletter API endpoint
|
||||||
|
- `LETTERSPACE_API_KEY` - Newsletter API key
|
||||||
|
- `LETTERSPACE_LIST_ID` - Mailing list identifier
|
||||||
|
|
||||||
|
See [.env.production.example](.env.production.example) for full reference.
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. Make changes to code
|
||||||
|
2. Test locally with `pnpm dev`
|
||||||
|
3. Create a feature branch
|
||||||
|
4. Push and create PR (triggers CI build)
|
||||||
|
5. Merge to `main` (triggers production build)
|
||||||
|
6. Tag release: `git tag v1.0.0 && git push origin v1.0.0`
|
||||||
|
|
||||||
|
## CI/CD Pipeline
|
||||||
|
|
||||||
|
Automated workflows handle:
|
||||||
|
|
||||||
|
- ✅ Multi-platform Docker builds (AMD64 + ARM64)
|
||||||
|
- ✅ Automated publishing to GHCR
|
||||||
|
- ✅ Daily security vulnerability scans
|
||||||
|
- ✅ Weekly cleanup of old images
|
||||||
|
- ✅ Semantic versioning from git tags
|
||||||
|
|
||||||
|
Images are available at: `ghcr.io/valknarxxx/sexy`
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes
|
||||||
|
4. Ensure tests pass (when implemented)
|
||||||
|
5. Submit a pull request
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Docker images are scanned daily for vulnerabilities
|
||||||
|
- Security reports available in GitHub Security tab
|
||||||
|
- Report security issues privately via GitHub Security
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Issues:** [GitHub Issues](https://github.com/valknarxxx/sexy/issues)
|
||||||
|
- **Discussions:** [GitHub Discussions](https://github.com/valknarxxx/sexy/discussions)
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- Built with [SvelteKit](https://kit.svelte.dev/)
|
||||||
|
- Powered by [Directus](https://directus.io/)
|
||||||
|
- Hardware control via [Buttplug.io](https://buttplug.io/)
|
||||||
|
- UI components from [bits-ui](https://www.bits-ui.com/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note:** This is an adult content platform. Users must be 18+ or the age of majority in their jurisdiction.
|
||||||
|
|||||||
130
build.sh
Executable file
130
build.sh
Executable file
@@ -0,0 +1,130 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build script for sexy.pivoine.art Docker image
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
IMAGE_NAME="sexy.pivoine.art"
|
||||||
|
TAG="latest"
|
||||||
|
PUSH=false
|
||||||
|
PLATFORM=""
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-t|--tag)
|
||||||
|
TAG="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-n|--name)
|
||||||
|
IMAGE_NAME="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--push)
|
||||||
|
PUSH=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--platform)
|
||||||
|
PLATFORM="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: $0 [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -t, --tag TAG Set image tag (default: latest)"
|
||||||
|
echo " -n, --name NAME Set image name (default: sexy.pivoine.art)"
|
||||||
|
echo " -p, --push Push image after build"
|
||||||
|
echo " --platform PLATFORM Build for specific platform (e.g., linux/amd64,linux/arm64)"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 # Build with defaults"
|
||||||
|
echo " $0 -t v1.0.0 # Build with version tag"
|
||||||
|
echo " $0 --platform linux/amd64,linux/arm64 -p # Multi-platform build and push"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unknown option: $1${NC}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
FULL_IMAGE="${IMAGE_NAME}:${TAG}"
|
||||||
|
|
||||||
|
echo -e "${GREEN}=== Building Docker Image ===${NC}"
|
||||||
|
echo "Image: ${FULL_IMAGE}"
|
||||||
|
echo "Platform: ${PLATFORM:-default}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if Docker is running
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: Docker is not running${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
BUILD_CMD="docker build"
|
||||||
|
|
||||||
|
if [ -n "$PLATFORM" ]; then
|
||||||
|
# Multi-platform build requires buildx
|
||||||
|
echo -e "${YELLOW}Using buildx for multi-platform build${NC}"
|
||||||
|
BUILD_CMD="docker buildx build --platform ${PLATFORM}"
|
||||||
|
|
||||||
|
if [ "$PUSH" = true ]; then
|
||||||
|
BUILD_CMD="${BUILD_CMD} --push"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Regular build
|
||||||
|
if [ "$PUSH" = true ]; then
|
||||||
|
echo -e "${YELLOW}Note: --push only works with multi-platform builds. Use 'docker push' after build.${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute build
|
||||||
|
echo -e "${GREEN}Building...${NC}"
|
||||||
|
$BUILD_CMD -t "${FULL_IMAGE}" .
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✓ Build successful!${NC}"
|
||||||
|
echo "Image: ${FULL_IMAGE}"
|
||||||
|
|
||||||
|
# Show image size
|
||||||
|
if [ -z "$PLATFORM" ]; then
|
||||||
|
SIZE=$(docker images "${FULL_IMAGE}" --format "{{.Size}}")
|
||||||
|
echo "Size: ${SIZE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Push if requested and not multi-platform
|
||||||
|
if [ "$PUSH" = true ] && [ -z "$PLATFORM" ]; then
|
||||||
|
echo -e "${GREEN}Pushing image...${NC}"
|
||||||
|
docker push "${FULL_IMAGE}"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✓ Push successful!${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Push failed${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Next steps:${NC}"
|
||||||
|
echo "1. Run locally:"
|
||||||
|
echo " docker run -d -p 3000:3000 --env-file .env.production ${FULL_IMAGE}"
|
||||||
|
echo ""
|
||||||
|
echo "2. Run with docker-compose:"
|
||||||
|
echo " docker-compose -f docker-compose.production.yml up -d"
|
||||||
|
echo ""
|
||||||
|
echo "3. View logs:"
|
||||||
|
echo " docker logs -f <container-name>"
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Build failed${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
71
docker-compose.production.yml
Normal file
71
docker-compose.production.yml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
frontend:
|
||||||
|
# Use pre-built image from GitHub Container Registry
|
||||||
|
# To build locally instead, uncomment the 'build' section and comment out 'image'
|
||||||
|
image: ghcr.io/valknarxxx/sexy:latest
|
||||||
|
|
||||||
|
# Uncomment to build locally:
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: Dockerfile
|
||||||
|
# args:
|
||||||
|
# NODE_ENV: production
|
||||||
|
container_name: sexy-pivoine-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
# Application settings
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: 3000
|
||||||
|
HOST: 0.0.0.0
|
||||||
|
|
||||||
|
# Public environment variables
|
||||||
|
PUBLIC_API_URL: ${PUBLIC_API_URL:-http://localhost:8055}
|
||||||
|
PUBLIC_URL: ${PUBLIC_URL:-http://localhost:3000}
|
||||||
|
PUBLIC_UMAMI_ID: ${PUBLIC_UMAMI_ID:-}
|
||||||
|
|
||||||
|
# Letterspace integration
|
||||||
|
LETTERSPACE_API_URL: ${LETTERSPACE_API_URL:-}
|
||||||
|
LETTERSPACE_API_KEY: ${LETTERSPACE_API_KEY:-}
|
||||||
|
LETTERSPACE_LIST_ID: ${LETTERSPACE_LIST_ID:-}
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- sexy-network
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
# Resource limits
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '2'
|
||||||
|
memory: 2G
|
||||||
|
reservations:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 512M
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
sexy-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
# Example usage:
|
||||||
|
# 1. Create a .env file with your environment variables
|
||||||
|
# 2. Build: docker-compose -f docker-compose.production.yml build
|
||||||
|
# 3. Run: docker-compose -f docker-compose.production.yml up -d
|
||||||
|
# 4. Logs: docker-compose -f docker-compose.production.yml logs -f
|
||||||
|
# 5. Stop: docker-compose -f docker-compose.production.yml down
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
"packageManager": "pnpm@10.17.0+sha512.fce8a3dd29a4ed2ec566fb53efbb04d8c44a0f05bc6f24a73046910fb9c3ce7afa35a0980500668fa3573345bd644644fa98338fa168235c80f4aa17aa17fbef",
|
"packageManager": "pnpm@10.17.0+sha512.fce8a3dd29a4ed2ec566fb53efbb04d8c44a0f05bc6f24a73046910fb9c3ce7afa35a0980500668fa3573345bd644644fa98338fa168235c80f4aa17aa17fbef",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
|
"@parcel/watcher",
|
||||||
|
"es5-ext",
|
||||||
|
"esbuild",
|
||||||
"svelte-preprocess",
|
"svelte-preprocess",
|
||||||
"vue-demi"
|
"wasm-pack"
|
||||||
],
|
],
|
||||||
"ignoredBuiltDependencies": [
|
"ignoredBuiltDependencies": [
|
||||||
"@tailwindcss/oxide",
|
"@tailwindcss/oxide",
|
||||||
|
|||||||
@@ -40,8 +40,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@directus/sdk": "^20.0.3",
|
"@directus/sdk": "^20.0.3",
|
||||||
"@opentelemetry/api": "^1.9.0",
|
|
||||||
"@opentelemetry/auto-instrumentations-node": "^0.64.6",
|
|
||||||
"@sexy.pivoine.art/buttplug": "workspace:*",
|
"@sexy.pivoine.art/buttplug": "workspace:*",
|
||||||
"javascript-time-ago": "^2.5.11",
|
"javascript-time-ago": "^2.5.11",
|
||||||
"media-chrome": "^4.13.1",
|
"media-chrome": "^4.13.1",
|
||||||
|
|||||||
1527
pnpm-lock.yaml
generated
1527
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user