Fix compose issues

This commit is contained in:
myrmidex 2025-06-30 21:28:15 +02:00
parent a7d62c1b0f
commit 87bfefd949
12 changed files with 227 additions and 69 deletions

44
.dockerignore Normal file
View file

@ -0,0 +1,44 @@
# Development files
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment files
.env
.env.*
!.env.production
# Development tools
.git/
.gitignore
README.md
docker-compose.yml
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Laravel development
storage/logs/*
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/testing/*
storage/framework/views/*
bootstrap/cache/*
# Testing
tests/
phpunit.xml
.phpunit.result.cache
# Build artifacts
vendor/
build/
dist/

1
.gitignore vendored
View file

@ -9,7 +9,6 @@
/vendor
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
Homestead.json

View file

@ -35,6 +35,10 @@ COPY . .
# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Copy production environment file and generate APP_KEY
COPY docker/.env.production .env
RUN php artisan key:generate
# Copy built frontend assets
COPY --from=frontend-builder /app/public/build /var/www/html/public/build

View file

@ -23,15 +23,9 @@ ### Docker Compose
ports:
- "8000:8000"
environment:
- APP_ENV=production
- APP_KEY=${APP_KEY}
- DB_CONNECTION=mysql
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=lemmy_poster
- DB_USERNAME=lemmy_user
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- QUEUE_CONNECTION=database
- LEMMY_INSTANCE=${LEMMY_INSTANCE}
- LEMMY_USERNAME=${LEMMY_USERNAME}
- LEMMY_PASSWORD=${LEMMY_PASSWORD}
@ -46,15 +40,9 @@ ### Docker Compose
image: your-registry/lemmy-poster:latest
command: ["queue"]
environment:
- APP_ENV=production
- APP_KEY=${APP_KEY}
- DB_CONNECTION=mysql
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=lemmy_poster
- DB_USERNAME=lemmy_user
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- QUEUE_CONNECTION=database
- LEMMY_INSTANCE=${LEMMY_INSTANCE}
- LEMMY_USERNAME=${LEMMY_USERNAME}
- LEMMY_PASSWORD=${LEMMY_PASSWORD}
@ -68,10 +56,10 @@ ### Docker Compose
mysql:
image: mysql:8.0
environment:
- MYSQL_DATABASE=lemmy_poster
- MYSQL_USER=lemmy_user
- MYSQL_DATABASE=${DB_DATABASE}
- MYSQL_USER=${DB_USERNAME}
- MYSQL_PASSWORD=${DB_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
@ -86,25 +74,38 @@ ### Environment Variables
Create a `.env` file with:
```env
APP_KEY=your-app-key-here
DB_PASSWORD=your-db-password
DB_ROOT_PASSWORD=your-root-password
# Database Settings
DB_DATABASE=lemmy_poster
DB_USERNAME=lemmy_user
DB_PASSWORD=your-password
# Lemmy Settings
LEMMY_INSTANCE=your-lemmy-instance.com
LEMMY_USERNAME=your-lemmy-username
LEMMY_PASSWORD=your-lemmy-password
LEMMY_COMMUNITY=your-target-community
```
Generate the APP_KEY:
```bash
echo "base64:$(openssl rand -base64 32)"
```
### Deployment
1. Build and push the image
1. Build and push the image to your registry
2. Copy the docker-compose.yml to your server
3. Create the .env file with your environment variables
4. Run: `docker-compose up -d`
The application will automatically:
- Wait for the database to be ready
- Run database migrations on first startup
- Start the queue worker after migrations complete
- Handle race conditions between web and queue containers
The web interface will be available on port 8000, ready for CloudFlare tunnel configuration.
### Architecture
The application uses a multi-container setup:
- **app-web**: Serves the Laravel web interface and handles HTTP requests
- **app-queue**: Processes background jobs (article fetching, Lemmy posting)
- **mysql**: Database storage for articles, logs, and application data
Both app containers use the same Docker image but with different commands (`web` or `queue`). Environment variables are passed from your `.env` file to configure database access and Lemmy integration.

View file

@ -56,7 +56,12 @@ private function syncLemmyChannelPosts(): void
private function getAuthToken(LemmyApiService $api): string
{
return Cache::remember('lemmy_jwt_token', 3600, function () use ($api) {
$cachedToken = Cache::get('lemmy_jwt_token');
if ($cachedToken) {
return $cachedToken;
}
$username = config('lemmy.username');
$password = config('lemmy.password');
@ -70,7 +75,8 @@ private function getAuthToken(LemmyApiService $api): string
throw new PlatformAuthException(PlatformEnum::LEMMY, 'Login failed');
}
Cache::put('lemmy_jwt_token', $token, 3600);
return $token;
});
}
}

View file

@ -5,12 +5,8 @@
use App\Events\ExceptionLogged;
use App\Events\ExceptionOccurred;
use App\Models\Log;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class LogExceptionToDatabase implements ShouldQueue
class LogExceptionToDatabase
{
use InteractsWithQueue;
public function handle(ExceptionOccurred $event): void
{

View file

@ -58,7 +58,12 @@ public function publish(Article $article, array $extractedData): ArticlePublicat
private function getAuthToken(): string
{
return Cache::remember('lemmy_jwt_token', 3600, function () {
$cachedToken = Cache::get('lemmy_jwt_token');
if ($cachedToken) {
return $cachedToken;
}
$username = config('lemmy.username');
$password = config('lemmy.password');
@ -72,15 +77,24 @@ private function getAuthToken(): string
throw new PlatformAuthException(PlatformEnum::LEMMY, 'Login failed');
}
Cache::put('lemmy_jwt_token', $token, 3600);
return $token;
});
}
private function getCommunityId(): int
{
return Cache::remember("lemmy_community_id_{$this->community}", 3600, function () {
return $this->api->getCommunityId($this->community);
});
$cacheKey = "lemmy_community_id_{$this->community}";
$cachedId = Cache::get($cacheKey);
if ($cachedId) {
return $cachedId;
}
$communityId = $this->api->getCommunityId($this->community);
Cache::put($cacheKey, $communityId, 3600);
return $communityId;
}
private function createPublicationRecord(Article $article, array $postData, int $communityId): ArticlePublication

View file

@ -11,6 +11,21 @@ public static function validate(Article $article): Article
logger('Checking keywords for article: ' . $article->id);
$articleData = ArticleFetcher::fetchArticleData($article);
if (!isset($articleData['full_article'])) {
logger()->warning('Article data missing full_article key', [
'article_id' => $article->id,
'url' => $article->url
]);
$article->update([
'is_valid' => false,
'validated_at' => now(),
]);
return $article->refresh();
}
$validationResult = self::validateByKeywords($articleData['full_article']);
$article->update([

View file

@ -6,11 +6,10 @@ class BelgaHomepageParser
{
public static function extractArticleUrls(string $html): array
{
preg_match_all('/href="https:\/\/www\.belganewsagency\.eu\/([a-z0-9-]+)"/', $html, $matches);
preg_match_all('/href="(https:\/\/www\.belganewsagency\.eu\/[a-z0-9-]+)"/', $html, $matches);
$urls = collect($matches[0] ?? [])
$urls = collect($matches[1] ?? [])
->unique()
->map(fn ($url) => str_replace('href="', '', str_replace('"', '', $url)))
->toArray();
return $urls;

59
docker/.env.production Normal file
View file

@ -0,0 +1,59 @@
APP_NAME="Lemmy Poster"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=error
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=lemmy_poster
DB_USERNAME=lemmy_user
DB_PASSWORD=your-password
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
# LEMMY SETTINGS
LEMMY_INSTANCE=
LEMMY_USERNAME=
LEMMY_PASSWORD=
LEMMY_COMMUNITY=

View file

@ -15,14 +15,35 @@ if [ -z "$LEMMY_INSTANCE" ] || [ -z "$LEMMY_USERNAME" ] || [ -z "$LEMMY_PASSWORD
fi
# Wait for database to be ready
until php artisan tinker --execute="DB::connection()->getPdo();" > /dev/null 2>&1; do
echo "Waiting for database connection..."
sleep 2
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
fi
# Execute the command based on the argument

View file

@ -37,7 +37,7 @@
} else {
logger()->debug('No unpublished valid articles found for Lemmy publishing');
}
})->everyFifteenMinutes()->name('publish-to-lemmy');
})->everyFiveMinutes()->name('publish-to-lemmy');
Schedule::call(function () {
$communityId = config('lemmy.community_id');