Skip to content

Commit

Permalink
Merge pull request #313 from pelican-dev/issue/311
Browse files Browse the repository at this point in the history
Docker
  • Loading branch information
lancepioch authored Sep 27, 2024
2 parents aab3817 + 6117282 commit a067419
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 140 deletions.
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
node_modules
vendor
database/database.sqlite
storage/debugbar/*.json
storage/logs/*.log
storage/framework/cache/data/*
storage/framework/sessions/*
storage/framework/testing
storage/framework/views/*.php
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ APP_KEY=
APP_TIMEZONE=UTC
APP_URL=http://panel.test
APP_LOCALE=en
APP_INSTALLED=false

LOG_CHANNEL=daily
LOG_STACK=single
Expand Down
83 changes: 30 additions & 53 deletions .github/docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,81 +1,58 @@
#!/bin/ash -e
cd /app

mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php8/ \
&& chmod 777 /var/log/panel/logs/ \
&& ln -s /app/storage/logs/ /var/log/panel/
#mkdir -p /var/log/supervisord/ /var/log/php8/ \

## check for .env file and generate app keys if missing
if [ -f /app/var/.env ]; then
if [ -f /pelican-data/.env ]; then
echo "external vars exist."
rm -rf /app/.env
ln -s /app/var/.env /app/
rm -rf /var/www/html/.env
else
echo "external vars don't exist."
rm -rf /app/.env
touch /app/var/.env
rm -rf /var/www/html/.env
touch /pelican-data/.env

## manually generate a key because key generate --force fails
if [ -z $APP_KEY ]; then
echo -e "Generating key."
APP_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
echo -e "Generated app key: $APP_KEY"
echo -e "APP_KEY=$APP_KEY" > /app/var/.env
echo -e "APP_KEY=$APP_KEY" > /pelican-data/.env
else
echo -e "APP_KEY exists in environment, using that."
echo -e "APP_KEY=$APP_KEY" > /app/var/.env
echo -e "APP_KEY=$APP_KEY" > /pelican-data/.env
fi

ln -s /app/var/.env /app/
fi

echo "Checking if https is required."
if [ -f /etc/nginx/http.d/panel.conf ]; then
echo "Using nginx config already in place."
if [ $LE_EMAIL ]; then
echo "Checking for cert update"
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
else
echo "No letsencrypt email is set"
fi
else
echo "Checking if letsencrypt email is set."
if [ -z $LE_EMAIL ]; then
echo "No letsencrypt email is set using http config."
cp .github/docker/default.conf /etc/nginx/http.d/panel.conf
else
echo "writing ssl config"
cp .github/docker/default_ssl.conf /etc/nginx/http.d/panel.conf
echo "updating ssl config for domain"
sed -i "s|<domain>|$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf
echo "generating certs"
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
fi
echo "Removing the default nginx config"
rm -rf /etc/nginx/http.d/default.conf
fi
mkdir /pelican-data/database
ln -s /pelican-data/.env /var/www/html/
ln -s /pelican-data/database/database.sqlite /var/www/html/database/

if [[ -z $DB_PORT ]]; then
echo -e "DB_PORT not specified, defaulting to 3306"
DB_PORT=3306
if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then
echo "Generating APP_KEY..."
php artisan key:generate --force
else
echo "APP_KEY is already set."
fi

## check for DB up before starting the panel
echo "Checking database status."
until nc -z -v -w30 $DB_HOST $DB_PORT
do
echo "Waiting for database connection..."
# wait for 1 seconds before check again
sleep 1
done

## make sure the db is set up
echo -e "Migrating and Seeding D.B"
php artisan migrate --seed --force
echo -e "Migrating Database"
php artisan migrate --force

## start cronjobs for the queue
echo -e "Starting cron jobs."
crond -L /var/log/crond -l 5

echo -e "Starting supervisord."
export SUPERVISORD_CADDY=false

## disable caddy if SKIP_CADDY is set
if [[ -z $SKIP_CADDY ]]; then
echo "Starting PHP-FPM and Caddy"
export SUPERVISORD_CADDY=true
else
echo "Starting PHP-FPM only"
fi

chown -R www-data:www-data . /pelican-data/.env /pelican-data/database

echo "Starting Supervisord"
exec "$@"
12 changes: 6 additions & 6 deletions .github/docker/supervisord.conf
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ autostart=true
autorestart=true

[program:queue-worker]
command=/usr/local/bin/php /app/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
user=nginx
command=/usr/local/bin/php /var/www/html/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
user=www-data
autostart=true
autorestart=true

[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
autostart=true
autorestart=true
[program:caddy]
command=caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
autostart=%(ENV_SUPERVISORD_CADDY)s
autorestart=%(ENV_SUPERVISORD_CADDY)s
priority=10
stdout_events_enabled=true
stderr_events_enabled=true
11 changes: 11 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
email {$ADMIN_EMAIL}
}

{$APP_URL} {
root * /var/www/html/public
encode gzip

php_fastcgi 127.0.0.1:9000
file_server
}
91 changes: 54 additions & 37 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,41 +1,58 @@
# Stage 0:
# Build the assets that are needed for the frontend. This build stage is then discarded
# since we won't need NodeJS anymore in the future. This Docker image ships a final production
# level distribution
FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine
WORKDIR /app
COPY . ./
RUN yarn install --frozen-lockfile \
&& yarn run build:production
# Pelican Production Dockerfile

FROM node:20-alpine AS yarn
#FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn

WORKDIR /build

# Stage 1:
# Build the actual container with all of the needed PHP dependencies that will run the application.
FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine
WORKDIR /app
COPY . ./
COPY --from=0 /app/public/assets ./public/assets
RUN apk add --no-cache --update ca-certificates dcron curl git supervisor tar unzip nginx libpng-dev libxml2-dev libzip-dev icu-dev certbot certbot-nginx \
&& docker-php-ext-configure zip \
&& docker-php-ext-install bcmath gd intl pdo_mysql zip \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
&& cp .env.example .env \
&& mkdir -p bootstrap/cache/ storage/logs storage/framework/sessions storage/framework/views storage/framework/cache \
&& chmod 777 -R bootstrap storage \
&& composer install --no-dev --optimize-autoloader \
&& rm -rf .env bootstrap/cache/*.php \
&& mkdir -p /app/storage/logs/ \
&& chown -R nginx:nginx .

RUN rm /usr/local/etc/php-fpm.conf \
&& echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /var/spool/cron/crontabs/root \
&& echo "0 23 * * * certbot renew --nginx --quiet" >> /var/spool/cron/crontabs/root \
&& sed -i s/ssl_session_cache/#ssl_session_cache/g /etc/nginx/nginx.conf \
&& mkdir -p /var/run/php /var/run/nginx

COPY .github/docker/default.conf /etc/nginx/http.d/default.conf
COPY .github/docker/www.conf /usr/local/etc/php-fpm.conf
COPY .github/docker/supervisord.conf /etc/supervisord.conf

EXPOSE 80 443

RUN yarn install --frozen-lockfile && yarn run build:production

FROM php:8.3-fpm-alpine
# FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine

COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

WORKDIR /var/www/html

# Install dependencies
RUN apk update && apk add --no-cache \
libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev icu-dev \
zip unzip curl \
caddy ca-certificates supervisor \
&& docker-php-ext-install bcmath gd intl zip opcache pcntl posix pdo_mysql

# Copy the Caddyfile to the container
COPY Caddyfile /etc/caddy/Caddyfile

# Copy the application code to the container
COPY . .

COPY --from=yarn /build/public/assets ./public/assets

RUN touch .env

RUN composer install --no-dev --optimize-autoloader

# Set file permissions
RUN chmod -R 755 /var/www/html/storage \
&& chmod -R 755 /var/www/html/bootstrap/cache

# Add scheduler to cron
RUN echo "* * * * * php /var/www/html/artisan schedule:run >> /dev/null 2>&1" | crontab -u www-data -

## supervisord config and log dir
RUN cp .github/docker/supervisord.conf /etc/supervisord.conf && \
mkdir /var/log/supervisord/

HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/up || exit 1

EXPOSE 80:2019
EXPOSE 443

VOLUME /pelican-data

ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ]
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
20 changes: 16 additions & 4 deletions app/Filament/Pages/Installer/PanelInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Filament\Pages\Installer\Steps\EnvironmentStep;
use App\Filament\Pages\Installer\Steps\RedisStep;
use App\Filament\Pages\Installer\Steps\RequirementsStep;
use App\Models\User;
use App\Services\Users\UserCreationService;
use App\Traits\CheckMigrationsTrait;
use App\Traits\EnvironmentWriterTrait;
Expand Down Expand Up @@ -43,12 +44,23 @@ public function getMaxWidth(): MaxWidth|string
return MaxWidth::SevenExtraLarge;
}

public function mount()
public static function show(): bool
{
if (is_installed()) {
abort(404);
if (User::count() <= 0) {
return true;
}

if (config('panel.client_features.installer.enabled')) {
return true;
}

return false;
}

public function mount()
{
abort_unless(self::show(), 404);

$this->form->fill();
}

Expand Down Expand Up @@ -122,7 +134,7 @@ public function submit()
$user = app(UserCreationService::class)->handle($userData);

// Install setup complete
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
$this->writeToEnvironment(['APP_INSTALLER' => 'false']);

$this->rememberData();

Expand Down
4 changes: 2 additions & 2 deletions app/Filament/Pages/Installer/Steps/AdminUserStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public static function make(): Step
->label('Admin E-Mail')
->required()
->email()
->default('admin@example.com'),
->placeholder('admin@example.com'),
TextInput::make('user.username')
->label('Admin Username')
->required()
->default('admin'),
->placeholder('admin'),
TextInput::make('user.password')
->label('Admin Password')
->required()
Expand Down
4 changes: 2 additions & 2 deletions app/Filament/Pages/Installer/Steps/EnvironmentStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class EnvironmentStep
];

public const QUEUE_DRIVERS = [
'sync' => 'Sync',
'database' => 'Database',
'redis' => 'Redis',
'sync' => 'Synchronous',
];

public const DATABASE_DRIVERS = [
Expand Down Expand Up @@ -76,7 +76,7 @@ public static function make(): Step
ToggleButtons::make('env.QUEUE_CONNECTION')
->label('Queue Driver')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The driver used for handling queues. We recommend "Database".')
->hintIconTooltip('The driver used for handling queues. We recommend "Sync" or "Database".')
->required()
->inline()
->options(self::QUEUE_DRIVERS)
Expand Down
7 changes: 6 additions & 1 deletion app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Controllers\Auth;

use App\Filament\Pages\Installer\PanelInstaller;
use Carbon\CarbonImmutable;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
Expand All @@ -17,8 +18,12 @@ class LoginController extends AbstractLoginController
* base authentication view component. React will take over at this point and
* turn the login area into an SPA.
*/
public function index(): View
public function index()
{
if (PanelInstaller::show()) {
return redirect('/installer');
}

return view('templates/auth.core');
}

Expand Down
31 changes: 0 additions & 31 deletions app/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,3 @@ function is_ip(?string $address): bool
return $address !== null && filter_var($address, FILTER_VALIDATE_IP) !== false;
}
}

if (!function_exists('object_get_strict')) {
/**
* Get an object using dot notation. An object key with a value of null is still considered valid
* and will not trigger the response of a default value (unlike object_get).
*/
function object_get_strict(object $object, ?string $key, mixed $default = null): mixed
{
if (is_null($key) || trim($key) == '') {
return $object;
}

foreach (explode('.', $key) as $segment) {
if (!is_object($object) || !property_exists($object, $segment)) {
return value($default);
}

$object = $object->{$segment};
}

return $object;
}
}

if (!function_exists('is_installed')) {
function is_installed(): bool
{
// This defaults to true so existing panels count as "installed"
return env('APP_INSTALLED', true);
}
}
Loading

0 comments on commit a067419

Please sign in to comment.