Consolidate containers, set up horizon

This commit is contained in:
myrmidex 2025-07-05 01:10:54 +02:00
parent 57d91f8790
commit 62c0938018
16 changed files with 404 additions and 8399 deletions

1
.gitignore vendored
View file

@ -17,6 +17,7 @@ npm-debug.log
yarn-error.log
/package-lock.json
/auth.json
/composer.lock
/.fleet
/.idea
/.nova

View file

@ -18,7 +18,14 @@ RUN apk add --no-cache \
libxml2-dev \
zip \
unzip \
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
autoconf \
gcc \
g++ \
make \
gettext \
&& 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
@ -33,10 +40,10 @@ COPY composer*.json ./
COPY . .
# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
RUN composer install --no-dev --optimize-autoloader --no-interaction --ignore-platform-reqs
# Copy production environment file and generate APP_KEY
COPY docker/.env.production .env
COPY docker/build/laravel.env .env
RUN php artisan key:generate
# Copy built frontend assets
@ -47,9 +54,11 @@ RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html/storage \
&& chmod -R 755 /var/www/html/bootstrap/cache
# Create entrypoint script
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Create entrypoint script and health check scripts
COPY docker/build/entrypoint.sh /entrypoint.sh
COPY docker/build/wait-for-db.php /docker/wait-for-db.php
COPY docker/build/wait-for-redis.php /docker/wait-for-redis.php
RUN chmod +x /entrypoint.sh /docker/wait-for-db.php /docker/wait-for-redis.php
EXPOSE 8000

View file

@ -8,7 +8,7 @@
class PublishArticle implements ShouldQueue
{
public string|null $queue = 'default';
public string|null $queue = 'lemmy-publish';
public int $delay = 300;
public int $tries = 3;
public int $backoff = 300;

View file

@ -0,0 +1,34 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\HorizonApplicationServiceProvider;
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
parent::boot();
// Horizon::routeSmsNotificationsTo('15556667777');
// Horizon::routeMailNotificationsTo('example@example.com');
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
}
/**
* Register the Horizon gate.
*
* This gate determines who can access Horizon in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewHorizon', function ($user = null) {
return true;
});
}
}

View file

@ -2,4 +2,5 @@
return [
App\Providers\AppServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
];

View file

@ -12,6 +12,7 @@
"php": "^8.2",
"inertiajs/inertia-laravel": "^2.0",
"laravel/framework": "^12.0",
"laravel/horizon": "^5.29",
"laravel/tinker": "^2.10.1",
"tightenco/ziggy": "^2.4"
},

8240
composer.lock generated

File diff suppressed because it is too large Load diff

213
config/horizon.php Normal file
View file

@ -0,0 +1,213 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Horizon Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Horizon will be accessible from. If this
| setting is null, Horizon will reside under the same domain as the
| application. Otherwise, this value will serve as the subdomain.
|
*/
'domain' => env('HORIZON_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Horizon Path
|--------------------------------------------------------------------------
|
| This is the URI path where Horizon will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => env('HORIZON_PATH', 'horizon'),
/*
|--------------------------------------------------------------------------
| Horizon Redis Connection
|--------------------------------------------------------------------------
|
| This is the name of the Redis connection where Horizon will store the
| meta information required for it to function. It includes the list
| of supervisors, failed jobs, job metrics, and other information.
|
*/
'use' => 'default',
/*
|--------------------------------------------------------------------------
| Horizon Redis Prefix
|--------------------------------------------------------------------------
|
| This prefix will be used when storing all Horizon data in Redis. You
| may modify the prefix when you are running multiple installations
| of Horizon on the same server so that they don't have problems.
|
*/
'prefix' => env(
'HORIZON_PREFIX',
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
),
/*
|--------------------------------------------------------------------------
| Horizon Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will get attached onto each Horizon route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => ['web'],
/*
|--------------------------------------------------------------------------
| Queue Wait Time Thresholds
|--------------------------------------------------------------------------
|
| This option allows you to configure when the LongWaitDetected event
| will be fired. Every connection / queue combination may have its
| own, unique threshold (in seconds) before this event is fired.
|
*/
'waits' => [
'redis:default' => 60,
],
/*
|--------------------------------------------------------------------------
| Job Trimming Times
|--------------------------------------------------------------------------
|
| Here you can configure for how long (in minutes) you desire Horizon to
| persist the recent and failed jobs. Typically, recent jobs are kept
| for one hour while all failed jobs are stored for an entire week.
|
*/
'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
/*
|--------------------------------------------------------------------------
| Silenced Jobs
|--------------------------------------------------------------------------
|
| Silencing a job will instruct Horizon to not place the job in the list
| of completed jobs within the Horizon dashboard. This setting may be
| used to fully remove any noisy jobs from the completed jobs list.
|
*/
'silenced' => [
// App\Jobs\ExampleJob::class,
],
/*
|--------------------------------------------------------------------------
| Metrics
|--------------------------------------------------------------------------
|
| Here you can configure how many snapshots should be kept to display in
| the metrics graph. This will get used in combination with Horizon's
| `horizon:snapshot` schedule to define how long to retain metrics.
|
*/
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
/*
|--------------------------------------------------------------------------
| Fast Termination
|--------------------------------------------------------------------------
|
| When this option is enabled, Horizon's "terminate" command will not
| wait on all of the workers to terminate unless the --wait option
| is provided. Fast termination can shorten deployment delay by
| allowing a new instance of Horizon to start while the last
| instance will continue to terminate each of its workers.
|
*/
'fast_termination' => false,
/*
|--------------------------------------------------------------------------
| Memory Limit (MB)
|--------------------------------------------------------------------------
|
| This value describes the maximum amount of memory the Horizon master
| supervisor may consume before it is terminated and restarted. For
| configuring these limits on your workers, see the next section.
|
*/
'memory_limit' => 64,
/*
|--------------------------------------------------------------------------
| Queue Worker Configuration
|--------------------------------------------------------------------------
|
| Here you may define the queue worker settings used by your application
| in all environments. These supervisors and settings handle all your
| queued jobs and will be provisioned by Horizon during deployment.
|
*/
'defaults' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default', 'lemmy-posts', 'lemmy-publish'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'maxProcesses' => 1,
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 128,
'tries' => 1,
'timeout' => 60,
'nice' => 0,
],
],
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
];

View file

@ -1,74 +0,0 @@
services:
laravel.test:
build:
context: './vendor/laravel/sail/runtimes/8.4'
dockerfile: Dockerfile
args:
WWWGROUP: '${WWWGROUP}'
image: 'sail-8.4/app'
extra_hosts:
- 'host.docker.internal:host-gateway'
ports:
- '${APP_PORT:-80}:80'
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
environment:
WWWUSER: '${WWWUSER}'
LARAVEL_SAIL: 1
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
IGNITION_LOCAL_SITES_PATH: '${PWD}'
volumes:
- '.:/var/www/html'
networks:
- sail
depends_on:
- mysql
scheduler:
build:
context: './vendor/laravel/sail/runtimes/8.4'
dockerfile: Dockerfile
args:
WWWGROUP: '${WWWGROUP}'
image: 'sail-8.4/app'
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
WWWUSER: '${WWWUSER}'
LARAVEL_SAIL: 1
volumes:
- '.:/var/www/html'
networks:
- sail
depends_on:
- mysql
command: php artisan schedule:work
mysql:
image: 'mysql/mysql-server:8.0'
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: '%'
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
- 'sail-mysql:/var/lib/mysql'
- './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
networks:
- sail
healthcheck:
test:
- CMD
- mysqladmin
- ping
- '-p${DB_PASSWORD}'
retries: 3
timeout: 5s
networks:
sail:
driver: bridge
volumes:
sail-mysql:
driver: local

10
docker/.env.example Normal file
View file

@ -0,0 +1,10 @@
# DATABASE SETTINGS
DB_DATABASE=lemmy_poster
DB_USERNAME=lemmy_user
DB_PASSWORD=StrongPassword123!
# LEMMY SETTINGS
LEMMY_INSTANCE=belgae.social
LEMMY_USERNAME=newsbot
LEMMY_PASSWORD=2Pb5Sypj2sjgwUuU5QJ78KZK
LEMMY_COMMUNITY=newsbottest2

View file

@ -0,0 +1,54 @@
#!/bin/sh
# Exit on any error
set -e
# Check required Lemmy environment variables
if [ -z "$LEMMY_INSTANCE" ] || [ -z "$LEMMY_USERNAME" ] || [ -z "$LEMMY_PASSWORD" ] || [ -z "$LEMMY_COMMUNITY" ]; then
echo "ERROR: Missing required Lemmy configuration variables:"
echo " LEMMY_INSTANCE=${LEMMY_INSTANCE:-'(not set)'}"
echo " LEMMY_USERNAME=${LEMMY_USERNAME:-'(not set)'}"
echo " LEMMY_PASSWORD=${LEMMY_PASSWORD:-'(not set)'}"
echo " LEMMY_COMMUNITY=${LEMMY_COMMUNITY:-'(not set)'}"
echo "Please set all required environment variables before starting the application."
exit 1
fi
# Wait for database to be ready
echo "Waiting for database connection..."
until php /docker/wait-for-db.php > /dev/null 2>&1; do
echo "Database not ready, waiting..."
sleep 5
done
echo "Database connection established."
# Wait for Redis to be ready
echo "Waiting for Redis connection..."
until php /docker/wait-for-redis.php > /dev/null 2>&1; do
echo "Redis not ready, waiting..."
sleep 2
done
echo "Redis connection established."
# Substitute environment variables in .env file
echo "Configuring environment variables..."
envsubst < .env > .env.tmp && mv .env.tmp .env
# Run migrations and initial setup
echo "Running database migrations..."
php artisan migrate --force
echo "Dispatching initial sync job..."
php artisan tinker --execute="App\\Jobs\\SyncChannelPostsJob::dispatchForLemmy();"
echo "Dispatching article refresh job..."
php artisan tinker --execute="App\\Jobs\\RefreshArticlesJob::dispatch();"
# Start all services in single container
echo "Starting web server, scheduler, and Horizon..."
php artisan schedule:work &
php artisan horizon &
php artisan serve --host=0.0.0.0 --port=8000 &
# Wait for any process to exit
wait

View file

@ -1,7 +1,7 @@
APP_NAME="Lemmy Poster"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
@ -22,11 +22,11 @@ LOG_LEVEL=error
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=lemmy_poster
DB_USERNAME=lemmy_user
DB_PASSWORD=your-password
DB_DATABASE=$DB_DATABASE
DB_USERNAME=$DB_USERNAME
DB_PASSWORD=$DB_PASSWORD
SESSION_DRIVER=database
SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
@ -34,12 +34,12 @@ SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
QUEUE_CONNECTION=redis
CACHE_STORE=database
CACHE_STORE=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

View file

@ -0,0 +1,14 @@
#!/usr/bin/env php
<?php
try {
$pdo = new PDO(
'mysql:host=mysql;port=3306;dbname=' . getenv('DB_DATABASE'),
getenv('DB_USERNAME'),
getenv('DB_PASSWORD')
);
echo 'Connected';
exit(0);
} catch (Exception $e) {
exit(1);
}

View file

@ -0,0 +1,11 @@
#!/usr/bin/env php
<?php
try {
$redis = new Redis();
$redis->connect('redis', 6379);
echo 'Connected';
exit(0);
} catch (Exception $e) {
exit(1);
}

40
docker/docker-compose.yml Normal file
View file

@ -0,0 +1,40 @@
services:
app:
image: 192.168.178.152:50114/lemmy-poster:v0.2.0
ports:
- "8000:8000"
environment:
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- LEMMY_INSTANCE=${LEMMY_INSTANCE}
- LEMMY_USERNAME=${LEMMY_USERNAME}
- LEMMY_PASSWORD=${LEMMY_PASSWORD}
- LEMMY_COMMUNITY=${LEMMY_COMMUNITY}
depends_on:
- mysql
- redis
volumes:
- storage_data:/var/www/html/storage/app
restart: unless-stopped
mysql:
image: mysql:8.0
command: --host-cache-size=0 --innodb-use-native-aio=0 --sql-mode=STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
environment:
- MYSQL_DATABASE=${DB_DATABASE}
- MYSQL_USER=${DB_USERNAME}
- MYSQL_PASSWORD=${DB_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
- TZ=UTC
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
mysql_data:
storage_data:

View file

@ -1,69 +0,0 @@
#!/bin/sh
# Exit on any error
set -e
# Check required Lemmy environment variables
if [ -z "$LEMMY_INSTANCE" ] || [ -z "$LEMMY_USERNAME" ] || [ -z "$LEMMY_PASSWORD" ] || [ -z "$LEMMY_COMMUNITY" ]; then
echo "ERROR: Missing required Lemmy configuration variables:"
echo " LEMMY_INSTANCE=${LEMMY_INSTANCE:-'(not set)'}"
echo " LEMMY_USERNAME=${LEMMY_USERNAME:-'(not set)'}"
echo " LEMMY_PASSWORD=${LEMMY_PASSWORD:-'(not set)'}"
echo " LEMMY_COMMUNITY=${LEMMY_COMMUNITY:-'(not set)'}"
echo "Please set all required environment variables before starting the application."
exit 1
fi
# Wait for database to be ready
echo "Waiting for database connection..."
until php -r "
try {
\$pdo = new PDO('mysql:host=mysql;port=3306;dbname=' . getenv('DB_DATABASE'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'));
echo 'Connected';
exit(0);
} catch (Exception \$e) {
exit(1);
}
" > /dev/null 2>&1; do
echo "Database not ready, waiting..."
sleep 5
done
echo "Database connection established."
# Run migrations on first start (web container only)
if [ "$1" = "web" ]; then
echo "Running database migrations..."
php artisan migrate --force
elif [ "$1" = "queue" ]; then
echo "Waiting for migrations to complete..."
# Wait for all migrations to actually finish running
until php artisan migrate:status 2>/dev/null | grep -c "Ran" | grep -q "7"; do
echo "Migrations still running, waiting..."
sleep 3
done
echo "All migrations completed."
echo "Waiting for database to stabilize..."
sleep 5
echo "Dispatching initial sync job..."
php artisan tinker --execute="App\\Jobs\\SyncChannelPostsJob::dispatchForLemmy();"
echo "Dispatching article refresh job..."
php artisan tinker --execute="App\\Jobs\\RefreshArticlesJob::dispatch();"
fi
# Execute the command based on the argument
case "$1" in
"web")
echo "Starting web server..."
exec php artisan serve --host=0.0.0.0 --port=8000
;;
"queue")
echo "Starting queue worker..."
exec php artisan queue:work --tries=3 --queue=lemmy-posts,default
;;
*)
echo "Usage: $0 {web|queue}"
exit 1
;;
esac