73 - Fix prod environment
This commit is contained in:
parent
4e0f0bb072
commit
b6290c0f8d
8 changed files with 291 additions and 490 deletions
127
Dockerfile
Normal file
127
Dockerfile
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
# Production Dockerfile with FrankenPHP
|
||||||
|
FROM dunglas/frankenphp:latest-php8.3-alpine
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
git \
|
||||||
|
mysql-client
|
||||||
|
|
||||||
|
# Install PHP extensions
|
||||||
|
RUN install-php-extensions \
|
||||||
|
pdo_mysql \
|
||||||
|
opcache \
|
||||||
|
zip \
|
||||||
|
gd \
|
||||||
|
intl \
|
||||||
|
bcmath \
|
||||||
|
redis \
|
||||||
|
pcntl
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Set fixed production environment variables
|
||||||
|
ENV APP_ENV=production \
|
||||||
|
APP_DEBUG=false \
|
||||||
|
DB_CONNECTION=mysql \
|
||||||
|
DB_HOST=db \
|
||||||
|
DB_PORT=3306 \
|
||||||
|
SESSION_DRIVER=redis \
|
||||||
|
CACHE_STORE=redis \
|
||||||
|
QUEUE_CONNECTION=redis \
|
||||||
|
LOG_CHANNEL=stack \
|
||||||
|
LOG_LEVEL=error
|
||||||
|
|
||||||
|
# Copy application code first
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Install PHP dependencies (production only)
|
||||||
|
RUN composer install --no-dev --no-interaction --optimize-autoloader
|
||||||
|
|
||||||
|
# Install ALL Node dependencies (including dev for building)
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Build frontend assets
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Remove node_modules after build to save space
|
||||||
|
RUN rm -rf node_modules
|
||||||
|
|
||||||
|
# Laravel optimizations
|
||||||
|
RUN php artisan config:cache \
|
||||||
|
&& php artisan route:cache \
|
||||||
|
&& php artisan view:cache \
|
||||||
|
&& composer dump-autoload --optimize
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache
|
||||||
|
|
||||||
|
# Configure Caddy
|
||||||
|
RUN cat > /etc/caddy/Caddyfile <<EOF
|
||||||
|
{
|
||||||
|
frankenphp
|
||||||
|
order php_server before file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
:8000 {
|
||||||
|
root * /app/public
|
||||||
|
|
||||||
|
php_server {
|
||||||
|
index index.php
|
||||||
|
}
|
||||||
|
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
file_server
|
||||||
|
|
||||||
|
header {
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:8000/up || exit 1
|
||||||
|
|
||||||
|
# Create startup script for production
|
||||||
|
RUN cat > /start-prod.sh <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Wait for database to be ready
|
||||||
|
echo "Waiting for database..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if mysqladmin ping -h "$DB_HOST" -u "$DB_USERNAME" -p"$DB_PASSWORD" --silent 2>/dev/null; then
|
||||||
|
echo "Database is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for database... ($i/30)"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
echo "Running migrations..."
|
||||||
|
php artisan migrate --force || echo "Migrations failed or already up-to-date"
|
||||||
|
|
||||||
|
# Start Horizon in the background
|
||||||
|
php artisan horizon &
|
||||||
|
|
||||||
|
# Start FrankenPHP
|
||||||
|
exec frankenphp run --config /etc/caddy/Caddyfile
|
||||||
|
EOF
|
||||||
|
|
||||||
|
RUN chmod +x /start-prod.sh
|
||||||
|
|
||||||
|
# Start with our script
|
||||||
|
CMD ["/start-prod.sh"]
|
||||||
299
README.md
299
README.md
|
|
@ -1,219 +1,128 @@
|
||||||
# Fedi Feed Router (FFR) v1.0.0
|
# FFR (Feed to Fediverse Router)
|
||||||
|
|
||||||
<div align="center">
|
A Laravel-based application for routing RSS/Atom feeds to Fediverse platforms like Lemmy. Built with Laravel, Livewire, and FrankenPHP for a modern, single-container deployment.
|
||||||
<img src="backend/public/images/ffr-logo-600.png" alt="FFR Logo" width="200">
|
|
||||||
|
|
||||||
**A minimal working version — limited to two hardcoded sources, designed for self-hosters.**
|
## Features
|
||||||
*Future versions will expand configurability and support.*
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
- **Feed aggregation** - Fetch articles from multiple RSS/Atom feeds
|
||||||
|
- **Fediverse publishing** - Automatically post to Lemmy communities
|
||||||
|
- **Route configuration** - Map feeds to specific channels with keywords
|
||||||
|
- **Approval workflow** - Optional manual approval before publishing
|
||||||
|
- **Queue processing** - Background job handling with Laravel Horizon
|
||||||
|
- **Single container deployment** - Simplified hosting with FrankenPHP
|
||||||
|
|
||||||
## 🔰 Project Overview
|
## Self-hosting
|
||||||
|
|
||||||
**One-liner:** FFR routes content from RSS/Atom feeds to the fediverse based on keyword matching.
|
The production image is available at `codeberg.org/lvl0/ffr:latest`.
|
||||||
|
|
||||||
FFR is a self-hosted tool that monitors RSS/Atom feeds, filters articles based on keywords, and automatically publishes matching content to fediverse platforms like Lemmy. This v1.0.0 release provides a working foundation with two hardcoded news sources (CBC and BBC), designed specifically for self-hosters who want a simple, privacy-first solution without SaaS dependencies.
|
### docker-compose.yml
|
||||||
|
|
||||||
## ⚙️ Features
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: codeberg.org/lvl0/ffr:latest
|
||||||
|
container_name: ffr_app
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
APP_KEY: "${APP_KEY}"
|
||||||
|
APP_URL: "${APP_URL}"
|
||||||
|
DB_DATABASE: "${DB_DATABASE}"
|
||||||
|
DB_USERNAME: "${DB_USERNAME}"
|
||||||
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
volumes:
|
||||||
|
- app_storage:/app/storage
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/up"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
Current v1.0.0 features:
|
db:
|
||||||
- ✅ Fetches articles from two hardcoded RSS feeds (CBC News, BBC News)
|
image: mariadb:11
|
||||||
- ✅ Keyword-based content filtering and matching
|
container_name: ffr_db
|
||||||
- ✅ Automatic posting to Lemmy communities
|
restart: always
|
||||||
- ✅ Web dashboard for monitoring and management
|
environment:
|
||||||
- ✅ Docker-based deployment for easy self-hosting
|
MYSQL_DATABASE: "${DB_DATABASE}"
|
||||||
- ✅ Privacy-first design with no external dependencies
|
MYSQL_USER: "${DB_USERNAME}"
|
||||||
|
MYSQL_PASSWORD: "${DB_PASSWORD}"
|
||||||
|
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
Limitations (to be addressed in future versions):
|
redis:
|
||||||
- Feed sources are currently hardcoded (not user-configurable)
|
image: redis:7-alpine
|
||||||
- Only supports Lemmy as target platform
|
container_name: ffr_redis
|
||||||
- Basic keyword matching (no regex or complex rules yet)
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
## 🚀 Installation
|
volumes:
|
||||||
|
db_data:
|
||||||
### Quick Start with Docker
|
redis_data:
|
||||||
|
app_storage:
|
||||||
1. **Clone the repository:**
|
|
||||||
```bash
|
|
||||||
git clone https://codeberg.org/lvl0/ffr.git
|
|
||||||
cd ffr
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create environment file:**
|
|
||||||
```bash
|
|
||||||
cp docker/production/.env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Configure your environment variables:**
|
|
||||||
```env
|
|
||||||
# Required variables only
|
|
||||||
APP_URL=http://your-domain.com:8000
|
|
||||||
DB_PASSWORD=your-secure-db-password
|
|
||||||
DB_ROOT_PASSWORD=your-secure-root-password
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Start the application:**
|
|
||||||
```bash
|
|
||||||
docker-compose -f docker/production/docker-compose.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
The application will be available at `http://localhost:8000`
|
|
||||||
|
|
||||||
### System Requirements
|
|
||||||
|
|
||||||
- Docker and Docker Compose (or Podman)
|
|
||||||
- 2GB RAM minimum
|
|
||||||
- 10GB disk space
|
|
||||||
- Linux/macOS/Windows with WSL2
|
|
||||||
|
|
||||||
## 🕹️ Usage
|
|
||||||
|
|
||||||
### Web Interface
|
|
||||||
|
|
||||||
Access the dashboard at `http://localhost:8000` to:
|
|
||||||
- View fetched articles
|
|
||||||
- Monitor posting queue
|
|
||||||
- Check system logs
|
|
||||||
- Manage keywords (coming in v2.0)
|
|
||||||
|
|
||||||
### Manual Commands
|
|
||||||
|
|
||||||
Trigger article refresh manually:
|
|
||||||
```bash
|
|
||||||
docker compose exec app php artisan article:refresh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
View application logs:
|
### Environment Variables
|
||||||
```bash
|
|
||||||
docker compose logs -f app
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scheduled Tasks
|
| Variable | Required | Description |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `APP_KEY` | Yes | Encryption key. Generate with: `echo "base64:$(openssl rand -base64 32)"` |
|
||||||
|
| `APP_URL` | Yes | Your domain (e.g., `https://ffr.example.com`) |
|
||||||
|
| `DB_DATABASE` | Yes | Database name |
|
||||||
|
| `DB_USERNAME` | Yes | Database user |
|
||||||
|
| `DB_PASSWORD` | Yes | Database password |
|
||||||
|
| `DB_ROOT_PASSWORD` | Yes | MariaDB root password |
|
||||||
|
|
||||||
The application automatically:
|
## Development
|
||||||
- Fetches new articles every hour
|
|
||||||
- Publishes matching articles every 5 minutes
|
|
||||||
- Syncs with Lemmy communities every 10 minutes
|
|
||||||
|
|
||||||
## 📜 Logging & Debugging
|
### NixOS / Nix
|
||||||
|
|
||||||
**Log locations:**
|
|
||||||
- Application logs: Available in web dashboard under "Logs" section
|
|
||||||
- Docker logs: `docker compose logs -f app`
|
|
||||||
- Laravel logs: Inside container at `/var/www/html/backend/storage/logs/`
|
|
||||||
|
|
||||||
**Debug mode:**
|
|
||||||
To enable debug mode for troubleshooting, add to your `.env`:
|
|
||||||
```env
|
|
||||||
APP_DEBUG=true
|
|
||||||
```
|
|
||||||
⚠️ Remember to disable debug mode in production!
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
We welcome contributions! Here's how you can help:
|
|
||||||
|
|
||||||
1. **Report bugs:** Open an issue describing the problem
|
|
||||||
2. **Suggest features:** Create an issue with your idea
|
|
||||||
3. **Submit PRs:** Fork, create a feature branch, and submit a pull request
|
|
||||||
4. **Improve docs:** Documentation improvements are always appreciated
|
|
||||||
|
|
||||||
For development setup, see the [Development Setup](#development-setup) section below.
|
|
||||||
|
|
||||||
## 📘 License
|
|
||||||
|
|
||||||
This project is licensed under the GNU Affero General Public License v3.0 (AGPLv3).
|
|
||||||
See [LICENSE](LICENSE) file for details.
|
|
||||||
|
|
||||||
## 🧭 Roadmap
|
|
||||||
|
|
||||||
### v1.0.0 (Current Release)
|
|
||||||
- ✅ Basic feed fetching from hardcoded sources
|
|
||||||
- ✅ Keyword filtering
|
|
||||||
- ✅ Lemmy posting
|
|
||||||
- ✅ Web dashboard
|
|
||||||
- ✅ Docker deployment
|
|
||||||
|
|
||||||
### v2.0.0 (Planned)
|
|
||||||
- [ ] User-configurable feed sources
|
|
||||||
- [ ] Advanced filtering rules (regex, boolean logic)
|
|
||||||
- [ ] Support for Mastodon and other ActivityPub platforms
|
|
||||||
- [ ] API for external integrations
|
|
||||||
- [ ] Multi-user support with permissions
|
|
||||||
|
|
||||||
### v3.0.0 (Future)
|
|
||||||
- [ ] Machine learning-based content categorization
|
|
||||||
- [ ] Feed discovery and recommendations
|
|
||||||
- [ ] Scheduled posting with optimal timing
|
|
||||||
- [ ] Analytics and insights dashboard
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development Setup
|
|
||||||
|
|
||||||
For contributors and developers who want to work on FFR:
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Podman and podman-compose (or Docker)
|
|
||||||
- Git
|
|
||||||
- PHP 8.2+ (for local development)
|
|
||||||
- Node.js 18+ (for frontend development)
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
1. **Clone and start the development environment:**
|
|
||||||
```bash
|
|
||||||
git clone https://codeberg.org/lvl0/ffr.git
|
|
||||||
cd ffr
|
|
||||||
./docker/dev/podman/start-dev.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Access the development environment:**
|
|
||||||
- Web interface: http://localhost:8000
|
|
||||||
- Vite dev server: http://localhost:5173
|
|
||||||
- Database: localhost:3307
|
|
||||||
- Redis: localhost:6380
|
|
||||||
|
|
||||||
### Development Commands
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run tests with coverage
|
git clone https://codeberg.org/lvl0/ffr.git
|
||||||
podman-compose -f docker/dev/podman/docker-compose.yml exec app bash -c "cd backend && XDEBUG_MODE=coverage php artisan test --coverage-html=coverage-report"
|
cd ffr
|
||||||
|
nix-shell
|
||||||
# Execute artisan commands
|
|
||||||
podman-compose -f docker/dev/podman/docker-compose.yml exec app php artisan migrate
|
|
||||||
podman-compose -f docker/dev/podman/docker-compose.yml exec app php artisan tinker
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
podman-compose -f docker/dev/podman/docker-compose.yml logs -f
|
|
||||||
|
|
||||||
# Access container shell
|
|
||||||
podman-compose -f docker/dev/podman/docker-compose.yml exec app bash
|
|
||||||
|
|
||||||
# Stop environment
|
|
||||||
podman-compose -f docker/dev/podman/docker-compose.yml down
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development Features
|
The shell will display available commands and optionally start the containers for you.
|
||||||
|
|
||||||
- **Hot reload:** Vite automatically reloads frontend changes
|
#### Available Commands
|
||||||
- **Pre-seeded database:** Sample data for immediate testing
|
|
||||||
- **Laravel Horizon:** Queue monitoring dashboard
|
|
||||||
- **Xdebug:** Configured for debugging and code coverage
|
|
||||||
- **Redis:** For caching, sessions, and queues
|
|
||||||
|
|
||||||
---
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `dev-up` | Start development environment |
|
||||||
|
| `dev-down` | Stop development environment |
|
||||||
|
| `dev-restart` | Restart containers |
|
||||||
|
| `dev-logs` | Follow app logs |
|
||||||
|
| `dev-logs-db` | Follow database logs |
|
||||||
|
| `dev-shell` | Enter app container |
|
||||||
|
| `dev-artisan <cmd>` | Run artisan commands |
|
||||||
|
| `prod-build [tag]` | Build and push prod image (default: latest) |
|
||||||
|
|
||||||
|
#### Services
|
||||||
|
|
||||||
|
| Service | URL |
|
||||||
|
|---------|-----|
|
||||||
|
| App | http://localhost:8000 |
|
||||||
|
| Vite | http://localhost:5173 |
|
||||||
|
| MariaDB | localhost:3307 |
|
||||||
|
| Redis | localhost:6380 |
|
||||||
|
|
||||||
|
### Other Platforms
|
||||||
|
|
||||||
|
Contributions welcome for development setup instructions on other platforms.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is open-source software licensed under the [AGPL-3.0 license](LICENSE).
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For help and support:
|
For issues and questions, please use [Codeberg Issues](https://codeberg.org/lvl0/ffr/issues).
|
||||||
- 💬 Open a [Discussion](https://codeberg.org/lvl0/ffr/discussions)
|
|
||||||
- 🐛 Report [Issues](https://codeberg.org/lvl0/ffr/issues)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
Built with ❤️ for the self-hosting community
|
|
||||||
</div>
|
|
||||||
|
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
# Multi-stage build for FFR Laravel application
|
|
||||||
FROM node:22-alpine AS frontend-builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy frontend package files
|
|
||||||
COPY frontend/package*.json ./
|
|
||||||
|
|
||||||
# Install Node dependencies
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Copy frontend source
|
|
||||||
COPY frontend/ ./
|
|
||||||
|
|
||||||
# Build frontend assets
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# PHP runtime stage
|
|
||||||
FROM php:8.4-fpm-alpine
|
|
||||||
|
|
||||||
# Install system dependencies
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
libpng-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
zip \
|
|
||||||
unzip \
|
|
||||||
oniguruma-dev \
|
|
||||||
mysql-client \
|
|
||||||
nginx \
|
|
||||||
supervisor \
|
|
||||||
autoconf \
|
|
||||||
gcc \
|
|
||||||
g++ \
|
|
||||||
make
|
|
||||||
|
|
||||||
# Install PHP extensions
|
|
||||||
RUN docker-php-ext-install \
|
|
||||||
pdo_mysql \
|
|
||||||
mbstring \
|
|
||||||
exif \
|
|
||||||
pcntl \
|
|
||||||
bcmath \
|
|
||||||
gd \
|
|
||||||
&& pecl install redis \
|
|
||||||
&& docker-php-ext-enable redis
|
|
||||||
|
|
||||||
# Install Composer
|
|
||||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /var/www/html
|
|
||||||
|
|
||||||
# Copy application code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Install PHP dependencies in backend directory
|
|
||||||
WORKDIR /var/www/html/backend
|
|
||||||
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
|
||||||
|
|
||||||
# Copy built frontend assets from builder stage to frontend dist
|
|
||||||
COPY --from=frontend-builder /app/dist/ /var/www/html/frontend/dist/
|
|
||||||
|
|
||||||
# Back to main directory
|
|
||||||
WORKDIR /var/www/html
|
|
||||||
|
|
||||||
# Copy nginx and supervisor configurations
|
|
||||||
COPY docker/production/nginx.conf /etc/nginx/http.d/default.conf
|
|
||||||
COPY docker/production/supervisord.conf /etc/supervisord.conf
|
|
||||||
COPY docker/production/start-app.sh /usr/local/bin/start-app
|
|
||||||
|
|
||||||
# Set proper permissions
|
|
||||||
RUN chown -R www-data:www-data /var/www/html \
|
|
||||||
&& chmod -R 755 /var/www/html/backend/storage \
|
|
||||||
&& chmod -R 755 /var/www/html/backend/bootstrap/cache \
|
|
||||||
&& chmod +x /usr/local/bin/start-app
|
|
||||||
|
|
||||||
# Expose port 80 for nginx
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# Health check
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
||||||
CMD cd /var/www/html/backend && php artisan --version || exit 1
|
|
||||||
|
|
||||||
# Start the application
|
|
||||||
CMD ["/usr/local/bin/start-app"]
|
|
||||||
|
|
@ -1,23 +1,28 @@
|
||||||
|
# ===================
|
||||||
|
# FFR Production Services
|
||||||
|
# ===================
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: codeberg.org/lvl0/ffr:v1.0.0-rc1
|
build:
|
||||||
container_name: ffr-app
|
context: ../..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: codeberg.org/lvl0/ffr:latest
|
||||||
|
container_name: ffr_app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
|
||||||
- APP_URL=${APP_URL}
|
|
||||||
- DB_CONNECTION=mysql
|
|
||||||
- DB_HOST=db
|
|
||||||
- DB_PORT=3306
|
|
||||||
- DB_DATABASE=ffr
|
|
||||||
- DB_USERNAME=ffr_user
|
|
||||||
- DB_PASSWORD=${DB_PASSWORD}
|
|
||||||
- REDIS_HOST=redis
|
|
||||||
- REDIS_PORT=6379
|
|
||||||
- CACHE_DRIVER=redis
|
|
||||||
- SESSION_DRIVER=redis
|
|
||||||
- QUEUE_CONNECTION=redis
|
|
||||||
ports:
|
ports:
|
||||||
- "8000:80"
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
APP_NAME: "${APP_NAME:-FFR}"
|
||||||
|
APP_KEY: "${APP_KEY}"
|
||||||
|
APP_URL: "${APP_URL}"
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 3306
|
||||||
|
DB_DATABASE: "${DB_DATABASE:-ffr}"
|
||||||
|
DB_USERNAME: "${DB_USERNAME:-ffr}"
|
||||||
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
@ -27,28 +32,28 @@ services:
|
||||||
- ffr-network
|
- ffr-network
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: docker.io/library/mysql:8.4
|
image: mariadb:11
|
||||||
container_name: ffr-db
|
container_name: ffr_db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_DATABASE=ffr
|
MYSQL_DATABASE: "${DB_DATABASE:-ffr}"
|
||||||
- MYSQL_USER=ffr_user
|
MYSQL_USER: "${DB_USERNAME:-ffr}"
|
||||||
- MYSQL_PASSWORD=${DB_PASSWORD}
|
MYSQL_PASSWORD: "${DB_PASSWORD}"
|
||||||
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "ffr_user", "-p${DB_PASSWORD}"]
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
interval: 3s
|
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
networks:
|
networks:
|
||||||
- ffr-network
|
- ffr-network
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: docker.io/library/redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: ffr-redis
|
container_name: ffr_redis
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
|
|
@ -61,6 +66,4 @@ networks:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db_data:
|
db_data:
|
||||||
driver: local
|
|
||||||
redis_data:
|
redis_data:
|
||||||
driver: local
|
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
# Serve static React build files
|
|
||||||
root /var/www/html/frontend/dist;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# API requests to Laravel backend
|
|
||||||
location /api/ {
|
|
||||||
root /var/www/html/backend/public;
|
|
||||||
try_files /index.php =404;
|
|
||||||
|
|
||||||
fastcgi_pass 127.0.0.1:9000;
|
|
||||||
fastcgi_index index.php;
|
|
||||||
fastcgi_param SCRIPT_FILENAME /var/www/html/backend/public/index.php;
|
|
||||||
include fastcgi_params;
|
|
||||||
|
|
||||||
# Increase timeouts for long-running requests
|
|
||||||
fastcgi_read_timeout 300;
|
|
||||||
fastcgi_send_timeout 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Serve Laravel public assets (images, etc.)
|
|
||||||
location /images/ {
|
|
||||||
alias /var/www/html/backend/public/images/;
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
|
|
||||||
# React app - catch all routes
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Static assets with far-future expiry
|
|
||||||
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|map)$ {
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
access_log off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN";
|
|
||||||
add_header X-Content-Type-Options "nosniff";
|
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
|
||||||
|
|
||||||
# Deny access to hidden files
|
|
||||||
location ~ /\.ht {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Deny access to sensitive files
|
|
||||||
location ~ /\.(env|git) {
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /favicon.ico {
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /robots.txt {
|
|
||||||
access_log off;
|
|
||||||
log_not_found off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Gzip compression
|
|
||||||
gzip on;
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_min_length 1024;
|
|
||||||
gzip_proxied any;
|
|
||||||
gzip_comp_level 6;
|
|
||||||
gzip_types
|
|
||||||
text/plain
|
|
||||||
text/css
|
|
||||||
text/xml
|
|
||||||
text/javascript
|
|
||||||
application/javascript
|
|
||||||
application/xml+rss
|
|
||||||
application/json;
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Create .env file if it doesn't exist
|
|
||||||
if [ ! -f /var/www/html/backend/.env ]; then
|
|
||||||
cp /var/www/html/backend/.env.example /var/www/html/backend/.env 2>/dev/null || touch /var/www/html/backend/.env
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for database to be ready using PHP
|
|
||||||
echo "Waiting for database..."
|
|
||||||
until php -r "
|
|
||||||
\$host = getenv('DB_HOST') ?: 'db';
|
|
||||||
\$user = getenv('DB_USERNAME') ?: 'ffr_user';
|
|
||||||
\$pass = getenv('DB_PASSWORD');
|
|
||||||
\$db = getenv('DB_DATABASE') ?: 'ffr';
|
|
||||||
try {
|
|
||||||
\$pdo = new PDO(\"mysql:host=\$host;dbname=\$db\", \$user, \$pass, [
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false
|
|
||||||
]);
|
|
||||||
echo 'Database ready';
|
|
||||||
exit(0);
|
|
||||||
} catch (Exception \$e) {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
" 2>/dev/null; do
|
|
||||||
echo "Database not ready, waiting..."
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
echo "Database connection established!"
|
|
||||||
|
|
||||||
# Generate app key if not set
|
|
||||||
if ! grep -q "APP_KEY=base64:" /var/www/html/backend/.env; then
|
|
||||||
cd /var/www/html/backend && php artisan key:generate --force
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Laravel optimizations for production
|
|
||||||
cd /var/www/html/backend
|
|
||||||
php artisan config:cache
|
|
||||||
php artisan route:cache
|
|
||||||
php artisan view:cache
|
|
||||||
|
|
||||||
# Run migrations
|
|
||||||
cd /var/www/html/backend
|
|
||||||
php artisan migrate --force
|
|
||||||
|
|
||||||
# Run all seeders (same as dev)
|
|
||||||
cd /var/www/html/backend
|
|
||||||
php artisan db:seed --force
|
|
||||||
|
|
||||||
# Start supervisor to manage nginx and php-fpm
|
|
||||||
supervisord -c /etc/supervisord.conf
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
[supervisord]
|
|
||||||
nodaemon=true
|
|
||||||
user=root
|
|
||||||
logfile=/var/log/supervisord.log
|
|
||||||
pidfile=/var/run/supervisord.pid
|
|
||||||
|
|
||||||
[program:nginx]
|
|
||||||
command=nginx -g "daemon off;"
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
priority=10
|
|
||||||
|
|
||||||
[program:php-fpm]
|
|
||||||
command=php-fpm --nodaemonize
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
priority=10
|
|
||||||
|
|
||||||
[program:horizon]
|
|
||||||
command=php /var/www/html/backend/artisan horizon
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
priority=20
|
|
||||||
|
|
||||||
[program:scheduler]
|
|
||||||
command=sh -c "while true; do php /var/www/html/backend/artisan schedule:run --verbose --no-interaction; sleep 60; done"
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
priority=20
|
|
||||||
31
shell.nix
31
shell.nix
|
|
@ -3,8 +3,8 @@
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
# PHP and tools
|
# PHP and tools
|
||||||
php84
|
php83
|
||||||
php84Packages.composer
|
php83Packages.composer
|
||||||
|
|
||||||
# Node.js and npm
|
# Node.js and npm
|
||||||
nodejs_22
|
nodejs_22
|
||||||
|
|
@ -81,6 +81,32 @@ pkgs.mkShell {
|
||||||
podman-compose -f $COMPOSE_FILE exec app php artisan "$@"
|
podman-compose -f $COMPOSE_FILE exec app php artisan "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ===================
|
||||||
|
# PROD COMMANDS
|
||||||
|
# ===================
|
||||||
|
prod-build() {
|
||||||
|
local tag="''${1:-latest}"
|
||||||
|
local image="codeberg.org/lvl0/ffr:$tag"
|
||||||
|
|
||||||
|
echo "Building production image: $image"
|
||||||
|
if ! podman build -t "$image" -f Dockerfile .; then
|
||||||
|
echo "Build failed!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Pushing to registry..."
|
||||||
|
if ! podman push "$image"; then
|
||||||
|
echo ""
|
||||||
|
echo "Push failed! You may need to login first:"
|
||||||
|
echo " podman login codeberg.org"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done! Image pushed: $image"
|
||||||
|
}
|
||||||
|
|
||||||
# ===================
|
# ===================
|
||||||
# WELCOME MESSAGE
|
# WELCOME MESSAGE
|
||||||
# ===================
|
# ===================
|
||||||
|
|
@ -99,6 +125,7 @@ pkgs.mkShell {
|
||||||
echo " dev-logs-db Tail database logs"
|
echo " dev-logs-db Tail database logs"
|
||||||
echo " dev-shell Shell into app container"
|
echo " dev-shell Shell into app container"
|
||||||
echo " dev-artisan <cmd> Run artisan command"
|
echo " dev-artisan <cmd> Run artisan command"
|
||||||
|
echo " prod-build [tag] Build and push prod image (default: latest)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Services:"
|
echo "Services:"
|
||||||
echo " app Laravel + Vite http://localhost:8000"
|
echo " app Laravel + Vite http://localhost:8000"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue