mirror of
https://github.com/pelican-dev/panel.git
synced 2026-07-18 13:14:01 +03:00
Compare commits
1 Commits
server-use
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b5fd477d5 |
@@ -1,29 +0,0 @@
|
||||
**.DS_Store
|
||||
.env
|
||||
.devcontainer
|
||||
.dockerignore
|
||||
.editorconfig
|
||||
.git
|
||||
.github
|
||||
**.gitignore
|
||||
.php-cs-fixer.dist.php
|
||||
.prettierrc.json
|
||||
.vscode
|
||||
Dockerfile
|
||||
bounties.md
|
||||
compose.yml
|
||||
contributing.md
|
||||
contributor_license_agreement.md
|
||||
database/database.sqlite
|
||||
docker/README.md
|
||||
node_modules
|
||||
phpstan.neon
|
||||
phpunit.xml
|
||||
readme.md
|
||||
storage/debugbar/*.json
|
||||
storage/framework/cache/data/*
|
||||
storage/framework/sessions/*
|
||||
storage/framework/testing
|
||||
storage/framework/views/*.php
|
||||
storage/logs/*.log
|
||||
vendor
|
||||
29
.env.example
29
.env.example
@@ -1,6 +1,33 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_KEY=
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=http://panel.test
|
||||
APP_INSTALLED=false
|
||||
APP_LOCALE=en
|
||||
APP_INSTALLED=false
|
||||
|
||||
LOG_CHANNEL=daily
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
|
||||
CACHE_STORE=file
|
||||
QUEUE_CONNECTION=database
|
||||
SESSION_DRIVER=file
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=smtp.example.com
|
||||
MAIL_PORT=25
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS=no-reply@example.com
|
||||
MAIL_FROM_NAME="Pelican Admin"
|
||||
# Set this to your domain to prevent it defaulting to 'localhost', causing mail servers such as Gmail to reject your mail
|
||||
# MAIL_EHLO_DOMAIN=panel.example.com
|
||||
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
6
.eslintignore
Normal file
6
.eslintignore
Normal file
@@ -0,0 +1,6 @@
|
||||
public
|
||||
node_modules
|
||||
resources/views
|
||||
babel.config.js
|
||||
tailwind.config.js
|
||||
webpack.config.js
|
||||
52
.eslintrc.js
Normal file
52
.eslintrc.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: './',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
pragma: 'React',
|
||||
version: 'detect',
|
||||
},
|
||||
linkComponents: [
|
||||
{ name: 'Link', linkAttribute: 'to' },
|
||||
{ name: 'NavLink', linkAttribute: 'to' },
|
||||
],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
},
|
||||
plugins: ['react', 'react-hooks', 'prettier', '@typescript-eslint'],
|
||||
extends: [
|
||||
// 'standard',
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:jest-dom/recommended',
|
||||
],
|
||||
rules: {
|
||||
eqeqeq: 'error',
|
||||
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
|
||||
// TypeScript can infer this significantly better than eslint ever can.
|
||||
'react/prop-types': 0,
|
||||
'react/display-name': 0,
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'@typescript-eslint/no-non-null-assertion': 0,
|
||||
// 'react/no-unknown-property': ['error', { ignore: ['css'] }],
|
||||
// This setup is required to avoid a spam of errors when running eslint about React being
|
||||
// used before it is defined.
|
||||
//
|
||||
// @see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use
|
||||
'no-use-before-define': 0,
|
||||
'@typescript-eslint/no-use-before-define': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-expect-error': 'allow-with-description' }],
|
||||
},
|
||||
};
|
||||
9
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
9
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -33,6 +33,7 @@ body:
|
||||
attributes:
|
||||
label: Panel Version
|
||||
description: Version number of your Panel (latest is not a version)
|
||||
placeholder: 1.4.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -41,6 +42,7 @@ body:
|
||||
attributes:
|
||||
label: Wings Version
|
||||
description: Version number of your Wings (latest is not a version)
|
||||
placeholder: 1.4.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -64,9 +66,10 @@ body:
|
||||
label: Error Logs
|
||||
description: |
|
||||
Run the following command to collect logs on your system.
|
||||
Wings: `sudo wings diagnostics --hastebin-url=https://logs.pelican.dev`
|
||||
Panel: `tail -n 300 /var/www/pelican/storage/logs/laravel-$(date +%F).log | curl --data-binary @- https://logs.pelican.dev`
|
||||
placeholder: "https://logs.pelican.dev/c17f750e"
|
||||
|
||||
Wings: `sudo wings diagnostics`
|
||||
Panel: `tail -n 150 /var/www/pelican/storage/logs/laravel-$(date +%F).log | nc pelipaste.com 99`
|
||||
placeholder: "https://pelipaste.com/a1h6z"
|
||||
render: bash
|
||||
validations:
|
||||
required: false
|
||||
|
||||
76
.github/docker/README.md
vendored
Normal file
76
.github/docker/README.md
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Pelican Panel - Docker Image
|
||||
This is a ready to use docker image for the panel.
|
||||
|
||||
## Requirements
|
||||
This docker image requires some additional software to function. The software can either be provided in other containers (see the [docker-compose.yml](https://github.com/pelican-dev/panel/blob/develop/docker-compose.example.yml) as an example) or as existing instances.
|
||||
|
||||
A mysql database is required. We recommend the stock [MariaDB Image](https://hub.docker.com/_/mariadb/) image if you prefer to run it in a docker container. As a non-containerized option we recommend mariadb.
|
||||
|
||||
A caching software is required as well. We recommend the stock [Redis Image](https://hub.docker.com/_/redis/) image. You can choose any of the [supported options](#cache-drivers).
|
||||
|
||||
You can provide additional settings using a custom `.env` file or by setting the appropriate environment variables in the docker-compose file.
|
||||
|
||||
## Setup
|
||||
|
||||
Start the docker container and the required dependencies (either provide existing ones or start containers as well, see the [docker-compose.yml](https://github.com/pelican-dev/panel/blob/develop/docker-compose.example.yml) file as an example.
|
||||
|
||||
After the startup is complete you'll need to create a user.
|
||||
If you are running the docker container without docker-compose, use:
|
||||
```
|
||||
docker exec -it <container id> php artisan p:user:make
|
||||
```
|
||||
If you are using docker compose use
|
||||
```
|
||||
docker-compose exec panel php artisan p:user:make
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
There are multiple environment variables to configure the panel when not providing your own `.env` file, see the following table for details on each available option.
|
||||
|
||||
Note: If your `APP_URL` starts with `https://` you need to provide an `LE_EMAIL` as well so Certificates can be generated.
|
||||
|
||||
| Variable | Description | Required |
|
||||
|-------------------| ------------------------------------------------------------------------------ | -------- |
|
||||
| `APP_URL` | The URL the panel will be reachable with (including protocol) | yes |
|
||||
| `APP_TIMEZONE` | The timezone to use for the panel | yes |
|
||||
| `LE_EMAIL` | The email used for letsencrypt certificate generation | yes |
|
||||
| `DB_HOST` | The host of the mysql instance | yes |
|
||||
| `DB_PORT` | The port of the mysql instance | yes |
|
||||
| `DB_DATABASE` | The name of the mysql database | yes |
|
||||
| `DB_USERNAME` | The mysql user | yes |
|
||||
| `DB_PASSWORD` | The mysql password for the specified user | yes |
|
||||
| `CACHE_STORE` | The cache driver (see [Cache drivers](#cache-drivers) for detais) | yes |
|
||||
| `SESSION_DRIVER` | | yes |
|
||||
| `QUEUE_DRIVER` | | yes |
|
||||
| `REDIS_HOST` | The hostname or IP address of the redis database | yes |
|
||||
| `REDIS_PASSWORD` | The password used to secure the redis database | maybe |
|
||||
| `REDIS_PORT` | The port the redis database is using on the host | maybe |
|
||||
| `MAIL_DRIVER` | The email driver (see [Mail drivers](#mail-drivers) for details) | yes |
|
||||
| `MAIL_FROM` | The email that should be used as the sender email | yes |
|
||||
| `MAIL_HOST` | The host of your mail driver instance | maybe |
|
||||
| `MAIL_PORT` | The port of your mail driver instance | maybe |
|
||||
| `MAIL_USERNAME` | The username for your mail driver | maybe |
|
||||
| `MAIL_PASSWORD` | The password for your mail driver | maybe |
|
||||
|
||||
|
||||
### Cache drivers
|
||||
You can choose between different cache drivers depending on what you prefer.
|
||||
We recommend redis when using docker as it can be started in a container easily.
|
||||
|
||||
| Driver | Description | Required variables |
|
||||
| -------- | ------------------------------------ | ------------------------------------------------------ |
|
||||
| redis | host where redis is running | `REDIS_HOST` |
|
||||
| redis | port redis is running on | `REDIS_PORT` |
|
||||
| redis | redis database password | `REDIS_PASSWORD` |
|
||||
|
||||
### Mail drivers
|
||||
You can choose between different mail drivers according to your needs.
|
||||
Every driver requires `MAIL_FROM` to be set.
|
||||
|
||||
| Driver | Description | Required variables |
|
||||
| -------- | ------------------------------------ | ------------------------------------------------------------- |
|
||||
| mail | uses the installed php mail | |
|
||||
| mandrill | [Mandrill](http://www.mandrill.com/) | `MAIL_USERNAME` |
|
||||
| postmark | [Postmark](https://postmarkapp.com/) | `MAIL_USERNAME` |
|
||||
| mailgun | [Mailgun](https://www.mailgun.com/) | `MAIL_USERNAME`, `MAIL_HOST` |
|
||||
| smtp | Any SMTP server can be configured | `MAIL_USERNAME`, `MAIL_HOST`, `MAIL_PASSWORD`, `MAIL_PORT` |
|
||||
75
.github/docker/default.conf
vendored
Normal file
75
.github/docker/default.conf
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
# If using Ubuntu this file should be placed in:
|
||||
# /etc/nginx/sites-available/
|
||||
#
|
||||
# If using CentOS this file should be placed in:
|
||||
# /etc/nginx/conf.d/
|
||||
#
|
||||
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Pterodactyl®
|
||||
# Copyright © Dane Everitt <dane@daneeveritt.com> and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /app/public;
|
||||
index index.html index.htm index.php;
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
access_log off;
|
||||
error_log /var/log/nginx/panel.app-error.log error;
|
||||
|
||||
# allow larger file uploads and longer script runtimes
|
||||
client_max_body_size 100m;
|
||||
client_body_timeout 120s;
|
||||
|
||||
sendfile off;
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
# the fastcgi_pass path needs to be changed accordingly when using CentOS
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
fastcgi_intercept_errors off;
|
||||
fastcgi_buffer_size 16k;
|
||||
fastcgi_buffers 4 16k;
|
||||
fastcgi_connect_timeout 300;
|
||||
fastcgi_send_timeout 300;
|
||||
fastcgi_read_timeout 300;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
70
.github/docker/default_ssl.conf
vendored
Normal file
70
.github/docker/default_ssl.conf
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# If using Ubuntu this file should be placed in:
|
||||
# /etc/nginx/sites-available/
|
||||
#
|
||||
server {
|
||||
listen 80;
|
||||
server_name <domain>;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name <domain>;
|
||||
|
||||
root /app/public;
|
||||
index index.php;
|
||||
|
||||
access_log /var/log/nginx/panel.app-access.log;
|
||||
error_log /var/log/nginx/panel.app-error.log error;
|
||||
|
||||
# allow larger file uploads and longer script runtimes
|
||||
client_max_body_size 100m;
|
||||
client_body_timeout 120s;
|
||||
|
||||
sendfile off;
|
||||
|
||||
# strengthen ssl security
|
||||
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||
|
||||
# See the link below for more SSL information:
|
||||
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
|
||||
#
|
||||
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||
|
||||
# Add headers to serve security related headers
|
||||
add_header Strict-Transport-Security "max-age=15768000; preload;";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
add_header Content-Security-Policy "frame-ancestors 'self'";
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
fastcgi_intercept_errors off;
|
||||
fastcgi_buffer_size 16k;
|
||||
fastcgi_buffers 4 16k;
|
||||
fastcgi_connect_timeout 300;
|
||||
fastcgi_send_timeout 300;
|
||||
fastcgi_read_timeout 300;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
81
.github/docker/entrypoint.sh
vendored
Normal file
81
.github/docker/entrypoint.sh
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/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/
|
||||
|
||||
## check for .env file and generate app keys if missing
|
||||
if [ -f /app/var/.env ]; then
|
||||
echo "external vars exist."
|
||||
rm -rf /app/.env
|
||||
ln -s /app/var/.env /app/
|
||||
else
|
||||
echo "external vars don't exist."
|
||||
rm -rf /app/.env
|
||||
touch /app/var/.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
|
||||
else
|
||||
echo -e "APP_KEY exists in environment, using that."
|
||||
echo -e "APP_KEY=$APP_KEY" > /app/var/.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
|
||||
|
||||
if [[ -z $DB_PORT ]]; then
|
||||
echo -e "DB_PORT not specified, defaulting to 3306"
|
||||
DB_PORT=3306
|
||||
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
|
||||
|
||||
## start cronjobs for the queue
|
||||
echo -e "Starting cron jobs."
|
||||
crond -L /var/log/crond -l 5
|
||||
|
||||
echo -e "Starting supervisord."
|
||||
exec "$@"
|
||||
39
.github/docker/supervisord.conf
vendored
Normal file
39
.github/docker/supervisord.conf
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock ; path to your socket file
|
||||
|
||||
[supervisord]
|
||||
logfile=/var/log/supervisord/supervisord.log ; supervisord log file
|
||||
logfile_maxbytes=50MB ; maximum size of logfile before rotation
|
||||
logfile_backups=2 ; number of backed up logfiles
|
||||
loglevel=error ; info, debug, warn, trace
|
||||
pidfile=/var/run/supervisord.pid ; pidfile location
|
||||
nodaemon=false ; run supervisord as a daemon
|
||||
minfds=1024 ; number of startup file descriptors
|
||||
minprocs=200 ; number of process descriptors
|
||||
user=root ; default user
|
||||
childlogdir=/var/log/supervisord/ ; where child log files will live
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
|
||||
[program:php-fpm]
|
||||
command=/usr/local/sbin/php-fpm -F
|
||||
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
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:nginx]
|
||||
command=/usr/sbin/nginx -g 'daemon off;'
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=10
|
||||
stdout_events_enabled=true
|
||||
stderr_events_enabled=true
|
||||
16
.github/docker/www.conf
vendored
Normal file
16
.github/docker/www.conf
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
[www]
|
||||
|
||||
user = nginx
|
||||
group = nginx
|
||||
|
||||
listen = 127.0.0.1:9000
|
||||
listen.owner = nginx
|
||||
listen.group = nginx
|
||||
listen.mode = 0750
|
||||
|
||||
pm = ondemand
|
||||
pm.max_children = 9
|
||||
pm.process_idle_timeout = 10s
|
||||
pm.max_requests = 200
|
||||
|
||||
clear_env = no
|
||||
27
.github/workflows/build.yaml
vendored
27
.github/workflows/build.yaml
vendored
@@ -3,40 +3,31 @@ name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
ui:
|
||||
name: UI
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [22, 24]
|
||||
node-version: [18, 20]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install JS dependencies
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
run: yarn build:production
|
||||
|
||||
208
.github/workflows/ci.yaml
vendored
208
.github/workflows/ci.yaml
vendored
@@ -3,84 +3,20 @@ name: Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '**'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
sqlite:
|
||||
name: SQLite
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.3, 8.4, 8.5]
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: testing.sqlite
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
|
||||
- name: Create SQLite file
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/pest tests/Unit --parallel --do-not-fail-on-phpunit-warning
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
|
||||
mysql:
|
||||
name: MySQL
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.5]
|
||||
database: ["mysql:8.4", "mysql:9.6"]
|
||||
php: [8.2, 8.3]
|
||||
database: ["mysql:8"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -89,15 +25,26 @@ jobs:
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --tmpfs /var/lib/mysql:rw --health-cmd="mysqladmin ping" --health-interval=2s --health-timeout=5s --health-retries=30
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -105,7 +52,7 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -121,16 +68,16 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
- name: Unit tests
|
||||
run: vendor/bin/phpunit tests/Unit
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
run: vendor/bin/phpunit tests/Integration
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
@@ -139,10 +86,10 @@ jobs:
|
||||
name: MariaDB
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.5]
|
||||
database: ["mariadb:10.11", "mariadb:11.4"]
|
||||
php: [8.2, 8.3]
|
||||
database: ["mariadb:10.3", "mariadb:10.11", "mariadb:11.4"]
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
@@ -151,15 +98,26 @@ jobs:
|
||||
MYSQL_DATABASE: testing
|
||||
ports:
|
||||
- 3306
|
||||
options: --tmpfs /var/lib/mysql:rw --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=2s --health-timeout=5s --health-retries=30
|
||||
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: mariadb
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: root
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -167,7 +125,7 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -183,60 +141,44 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
- name: Unit tests
|
||||
run: vendor/bin/phpunit tests/Unit
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
run: vendor/bin/phpunit tests/Integration
|
||||
env:
|
||||
DB_PORT: ${{ job.services.database.ports[3306] }}
|
||||
DB_USERNAME: root
|
||||
|
||||
postgresql:
|
||||
name: PostgreSQL
|
||||
sqlite:
|
||||
name: SQLite
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.5]
|
||||
database: ["postgres:17", "postgres:18"]
|
||||
include:
|
||||
# postgres:17 stores data in /var/lib/postgresql/data, 18+ requires a
|
||||
# single mount at /var/lib/postgresql and errors on a data submount
|
||||
- database: "postgres:17"
|
||||
tmpfs: /var/lib/postgresql/data
|
||||
- database: "postgres:18"
|
||||
tmpfs: /var/lib/postgresql
|
||||
services:
|
||||
database:
|
||||
image: ${{ matrix.database }}
|
||||
env:
|
||||
POSTGRES_DB: testing
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--tmpfs ${{ matrix.tmpfs }}:rw
|
||||
--health-cmd pg_isready
|
||||
--health-interval 2s
|
||||
--health-timeout 5s
|
||||
--health-retries 30
|
||||
php: [8.2, 8.3]
|
||||
env:
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_DATABASE: testing
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: postgres
|
||||
APP_ENV: testing
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ThisIsARandomStringForTests12345
|
||||
APP_TIMEZONE: UTC
|
||||
APP_URL: http://localhost/
|
||||
CACHE_DRIVER: array
|
||||
MAIL_MAILER: array
|
||||
SESSION_DRIVER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: testing.sqlite
|
||||
GUZZLE_TIMEOUT: 60
|
||||
GUZZLE_CONNECT_TIMEOUT: 60
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
@@ -244,7 +186,7 @@ jobs:
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -260,10 +202,16 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
run: composer install --no-interaction --no-suggest --prefer-dist
|
||||
|
||||
- name: Run Migrations
|
||||
run: php artisan migrate --force --seed
|
||||
- name: Create SQLite file
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- name: Unit tests
|
||||
run: vendor/bin/phpunit tests/Unit
|
||||
env:
|
||||
DB_HOST: UNIT_NO_DB
|
||||
SKIP_MIGRATIONS: true
|
||||
|
||||
- name: Integration tests
|
||||
run: vendor/bin/pest tests/Integration --parallel --do-not-fail-on-phpunit-warning
|
||||
run: vendor/bin/phpunit tests/Integration
|
||||
|
||||
197
.github/workflows/docker-publish.yml
vendored
197
.github/workflows/docker-publish.yml
vendored
@@ -1,197 +0,0 @@
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-php-base:
|
||||
name: Build PHP base image on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
arch: amd64
|
||||
platform: linux/amd64
|
||||
- os: ubuntu-24.04-arm
|
||||
arch: arm64
|
||||
platform: linux/arm64
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build the base PHP image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.base
|
||||
push: false
|
||||
load: true
|
||||
platforms: ${{ matrix.platform }}
|
||||
tags: base-php:${{ matrix.arch }}
|
||||
cache-from: type=gha,scope=base-php${{ matrix.arch }}
|
||||
cache-to: type=gha,scope=base-php${{ matrix.arch }}
|
||||
|
||||
- name: Export image to file
|
||||
run: docker save -o base-php-${{ matrix.arch }}.tar base-php:${{ matrix.arch }}
|
||||
|
||||
- name: Push the docker build to the artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: base-php-${{ matrix.arch }}.tar
|
||||
path: base-php-${{ matrix.arch }}.tar
|
||||
retention-days: 7
|
||||
|
||||
|
||||
build-and-push:
|
||||
name: Build and Push ubuntu-24.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build-php-base
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
# Start a temp local registry because workflow can not pull from localy loaded images
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- 5000:5000
|
||||
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
|
||||
if: "!contains(github.ref, 'main') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||
steps:
|
||||
# Fetch the latest release tag from GitHub API before checkout and metadata so we can check out the correct ref and tag the image
|
||||
- name: Get latest release tag (scheduled)
|
||||
id: release_info
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
LATEST_TAG=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -w "\n%{http_code}" https://api.github.com/repos/${{ github.repository }}/releases/latest | tail -1 | head -1)
|
||||
HTTP_CODE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -w "%{http_code}" -o /dev/null https://api.github.com/repos/${{ github.repository }}/releases/latest)
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "Failed to fetch latest release (HTTP $HTTP_CODE)"
|
||||
exit 1
|
||||
fi
|
||||
LATEST_TAG=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name')
|
||||
if [ "$LATEST_TAG" = "null" ] || [ -z "$LATEST_TAG" ]; then
|
||||
echo "No release found"
|
||||
exit 1
|
||||
fi
|
||||
echo "version_tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT
|
||||
echo "schedule_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.release_info.outputs.schedule_tag || github.ref }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ (github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false) || github.event_name == 'schedule' }}
|
||||
type=raw,value=${{ steps.release_info.outputs.version_tag }},enable=${{ github.event_name == 'schedule' }}
|
||||
type=ref,event=tag
|
||||
type=ref,event=branch
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
# We Need to start it in host mode else it can't acces the local registry on port 5000
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get Build Information
|
||||
id: build_info
|
||||
run: |
|
||||
echo "version_tag=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
# Download the base PHP image AMD64
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: base-php-amd64.tar
|
||||
|
||||
# Download the base PHP image ARM64
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: base-php-arm64.tar
|
||||
|
||||
- name: Load base images into local registry
|
||||
run: |
|
||||
docker load -i base-php-amd64.tar
|
||||
docker load -i base-php-arm64.tar
|
||||
docker tag base-php:amd64 localhost:5000/base-php:amd64
|
||||
docker tag base-php:arm64 localhost:5000/base-php:arm64
|
||||
docker push localhost:5000/base-php:amd64
|
||||
docker push localhost:5000/base-php:arm64
|
||||
rm base-php-arm64.tar base-php-amd64.tar
|
||||
|
||||
- name: Update version in config/app.php (tag)
|
||||
if: "(github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'"
|
||||
run: |
|
||||
VERSION="${{ github.event_name == 'schedule' && steps.release_info.outputs.version_tag || steps.build_info.outputs.version_tag }}"
|
||||
if [ -z "$VERSION" ] || [ "$VERSION" = "refs/heads/main" ]; then
|
||||
echo "Invalid version tag, refusing to update config"
|
||||
exit 1
|
||||
fi
|
||||
sed -i "s|'version' => 'canary',|'version' => '$VERSION',|" config/app.php
|
||||
|
||||
- name: Build and Push (tag)
|
||||
uses: docker/build-push-action@v7
|
||||
if: "(github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'"
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
build-args: |
|
||||
VERSION=${{ github.event_name == 'schedule' && steps.release_info.outputs.version_tag || steps.build_info.outputs.version_tag }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=tagged${{ matrix.os }}
|
||||
cache-to: type=gha,scope=tagged${{ matrix.os }},mode=max
|
||||
|
||||
- name: Build and Push (main)
|
||||
uses: docker/build-push-action@v7
|
||||
if: "github.event_name == 'push' && contains(github.ref, 'main')"
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
build-args: |
|
||||
VERSION=dev-${{ steps.build_info.outputs.short_sha }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=${{ matrix.os }}
|
||||
cache-to: type=gha,scope=${{ matrix.os }},mode=max
|
||||
54
.github/workflows/download-translations.yml
vendored
54
.github/workflows/download-translations.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Download Crowdin Translations
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Every Sunday at midnight
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
download-translations:
|
||||
name: Download Translations from Crowdin
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download Translations from Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
export_only_approved: true
|
||||
push_translations: false
|
||||
create_pull_request: false
|
||||
crowdin_branch_name: main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Run Laravel Pint
|
||||
uses: aglipanci/laravel-pint-action@8eedec46a1856977c41667f9c66d5562fa3f9c08 # 2.4
|
||||
|
||||
# The Crowdin and Pint steps run in Docker as root, so new translation
|
||||
# files land owned by root. Reclaim them so the host-side git operations
|
||||
# in create-pull-request can manage the working tree.
|
||||
- name: Fix file ownership
|
||||
run: sudo chown -R "$(id -u):$(id -g)" "${{ github.workspace }}"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: update translations and format with pint'
|
||||
title: 'New Crowdin Translations'
|
||||
body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action), formatted by Laravel Pint.'
|
||||
branch: 'i18n_crowdin_translations'
|
||||
base: 'main'
|
||||
33
.github/workflows/lint.yaml
vendored
33
.github/workflows/lint.yaml
vendored
@@ -3,7 +3,7 @@ name: Lint
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
pint:
|
||||
@@ -11,12 +11,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.5"
|
||||
php-version: "8.3"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -25,38 +25,21 @@ jobs:
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts
|
||||
run: composer install --no-interaction --no-progress --prefer-dist
|
||||
|
||||
- name: Pint
|
||||
run: vendor/bin/pint --test
|
||||
phpstan:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
php: [8.3, 8.4, 8.5]
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get cache directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-${{ matrix.php }}-
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
php-version: "8.3"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
@@ -65,7 +48,7 @@ jobs:
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
|
||||
run: composer install --no-interaction --no-progress --prefer-dist
|
||||
|
||||
- name: PHPStan
|
||||
run: vendor/bin/phpstan --memory-limit=-1 --error-format=github
|
||||
run: vendor/bin/phpstan --memory-limit=-1
|
||||
|
||||
27
.github/workflows/release.yaml
vendored
27
.github/workflows/release.yaml
vendored
@@ -11,33 +11,22 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.5"
|
||||
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --no-interaction --no-suggest --no-progress --no-autoloader --no-scripts --no-dev
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install JS dependencies
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
run: yarn build:production
|
||||
|
||||
- name: Create release branch and bump version
|
||||
env:
|
||||
@@ -55,8 +44,8 @@ jobs:
|
||||
|
||||
- name: Create release archive
|
||||
run: |
|
||||
rm -rf node_modules vendor tests CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml shell.nix
|
||||
tar -czf panel.tar.gz * .env.example
|
||||
rm -rf node_modules tests CODE_OF_CONDUCT.md CONTRIBUTING.md flake.lock flake.nix phpunit.xml shell.nix
|
||||
tar -czf panel.tar.gz * .editorconfig .env.example .eslintignore .eslintrc.js .gitignore .prettierrc.json
|
||||
|
||||
- name: Create checksum
|
||||
run: |
|
||||
|
||||
32
.github/workflows/upload-sources.yml
vendored
32
.github/workflows/upload-sources.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Upload Crowdin Sources
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'lang/en/**'
|
||||
- 'crowdin.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upload-sources:
|
||||
name: Upload Sources to Crowdin
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Upload Sources to Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: false
|
||||
crowdin_branch_name: main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,9 +1,9 @@
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/storage/clockwork/*
|
||||
/vendor
|
||||
*.DS_Store*
|
||||
@@ -19,11 +19,10 @@ npm-debug.log
|
||||
yarn-error.log
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.vscode
|
||||
/.ddev
|
||||
|
||||
public/assets/manifest.json
|
||||
/database/*.sqlite*
|
||||
/database/*.sqlite
|
||||
filament-monaco-editor/
|
||||
_ide_helper*
|
||||
/.phpstorm.meta.php
|
||||
|
||||
52
.php-cs-fixer.dist.php
Normal file
52
.php-cs-fixer.dist.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use PhpCsFixer\Config;
|
||||
use PhpCsFixer\Finder;
|
||||
|
||||
$finder = (new Finder())
|
||||
->in(__DIR__)
|
||||
->exclude([
|
||||
'vendor',
|
||||
'node_modules',
|
||||
'storage',
|
||||
'bootstrap/cache',
|
||||
])
|
||||
->notName(['_ide_helper*']);
|
||||
|
||||
return (new Config())
|
||||
->setRiskyAllowed(true)
|
||||
->setFinder($finder)
|
||||
->setRules([
|
||||
'@Symfony' => true,
|
||||
'@PSR1' => true,
|
||||
'@PSR2' => true,
|
||||
'@PSR12' => true,
|
||||
'align_multiline_comment' => ['comment_type' => 'phpdocs_like'],
|
||||
'combine_consecutive_unsets' => true,
|
||||
'concat_space' => ['spacing' => 'one'],
|
||||
'heredoc_to_nowdoc' => true,
|
||||
'no_alias_functions' => true,
|
||||
'no_unreachable_default_argument_value' => true,
|
||||
'no_useless_return' => true,
|
||||
'ordered_imports' => [
|
||||
'sort_algorithm' => 'length',
|
||||
],
|
||||
'phpdoc_align' => [
|
||||
'align' => 'left',
|
||||
'tags' => [
|
||||
'param',
|
||||
'property',
|
||||
'return',
|
||||
'throws',
|
||||
'type',
|
||||
'var',
|
||||
],
|
||||
],
|
||||
'random_api_migration' => true,
|
||||
'ternary_to_null_coalescing' => true,
|
||||
'yoda_style' => [
|
||||
'equal' => false,
|
||||
'identical' => false,
|
||||
'less_and_greater' => false,
|
||||
],
|
||||
]);
|
||||
135
Dockerfile
135
Dockerfile
@@ -1,106 +1,41 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.13-labs
|
||||
# Pelican Production Dockerfile
|
||||
# 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
|
||||
|
||||
##
|
||||
# If you want to build this locally you want to run `docker build -f Dockerfile.dev .`
|
||||
##
|
||||
# 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 .
|
||||
|
||||
# ================================
|
||||
# Stage 1-1: Composer Install
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS composer
|
||||
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
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
# Copy bare minimum to install Composer dependencies
|
||||
COPY composer.json composer.lock ./
|
||||
|
||||
RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
|
||||
|
||||
# ================================
|
||||
# Stage 1-2: Yarn Install
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH node:22-alpine AS yarn
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy bare minimum to install Yarn dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
RUN yarn config set network-timeout 300000 \
|
||||
&& yarn install --frozen-lockfile
|
||||
|
||||
# ================================
|
||||
# Stage 2-1: Composer Optimize
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||
|
||||
# Copy full code to optimize autoload
|
||||
COPY --exclude=docker/ . ./
|
||||
|
||||
RUN composer dump-autoload --optimize
|
||||
|
||||
# ================================
|
||||
# Stage 2-2: Build Frontend Assets
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy full code
|
||||
COPY --exclude=docker/ . ./
|
||||
COPY --from=composer /build .
|
||||
|
||||
RUN yarn run build
|
||||
|
||||
# ================================
|
||||
# Stage 5: Build Final Application Image
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH localhost:5000/base-php:$TARGETARCH AS final
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
RUN apk add --no-cache \
|
||||
# packages for running the panel
|
||||
caddy ca-certificates supervisor supercronic fcgi \
|
||||
# required for installing plugins. Pulled from https://github.com/pelican-dev/panel/pull/2034
|
||||
zip unzip 7zip bzip2-dev yarn git
|
||||
|
||||
# Copy composer binary for runtime plugin dependency management
|
||||
COPY --from=composer /usr/local/bin/composer /usr/local/bin/composer
|
||||
COPY --chown=root:www-data --chmod=770 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=770 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /var/run/supervisord \
|
||||
# Symlinks for env, database, storage
|
||||
&& ln -s /pelican-data/.env /var/www/html/.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -s /pelican-data/storage /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage /var/www/html/storage/app/public \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R 770 /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/ /usr/local/etc/php-fpm.d/ /var/www/html/composer.json /var/www/html/composer.lock
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/crontabs/crontab
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD /bin/ash /healthcheck.sh
|
||||
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
|
||||
|
||||
VOLUME /pelican-data
|
||||
|
||||
USER www-data
|
||||
|
||||
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||
ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ]
|
||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# ================================
|
||||
# Stage 0: Build PHP Base Image
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.5-fpm-alpine
|
||||
|
||||
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
|
||||
RUN install-php-extensions bcmath gd intl zip pcntl pdo_mysql pdo_pgsql bz2
|
||||
|
||||
RUN rm /usr/local/bin/install-php-extensions
|
||||
112
Dockerfile.dev
112
Dockerfile.dev
@@ -1,112 +0,0 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.13-labs
|
||||
# Pelican Development Dockerfile
|
||||
|
||||
FROM --platform=$TARGETOS/$TARGETARCH php:8.5-fpm-alpine AS base
|
||||
|
||||
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
|
||||
|
||||
RUN install-php-extensions bcmath gd intl zip pcntl pdo_mysql pdo_pgsql bz2
|
||||
|
||||
RUN rm /usr/local/bin/install-php-extensions
|
||||
|
||||
# ================================
|
||||
# Stage 1-1: Composer Install
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH base AS composer
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
# Copy bare minimum to install Composer dependencies
|
||||
COPY composer.json composer.lock ./
|
||||
|
||||
RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts
|
||||
|
||||
# ================================
|
||||
# Stage 1-2: Yarn Install
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH node:22-alpine AS yarn
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy bare minimum to install Yarn dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
RUN yarn config set network-timeout 300000 \
|
||||
&& yarn install --frozen-lockfile
|
||||
|
||||
# ================================
|
||||
# Stage 2-1: Composer Optimize
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild
|
||||
|
||||
# Copy full code to optimize autoload
|
||||
COPY --exclude=docker/ . ./
|
||||
|
||||
RUN composer dump-autoload --optimize
|
||||
|
||||
# ================================
|
||||
# Stage 2-2: Build Frontend Assets
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy full code
|
||||
COPY --exclude=docker/ . ./
|
||||
COPY --from=composer /build .
|
||||
|
||||
RUN yarn run build
|
||||
|
||||
# ================================
|
||||
# Stage 5: Build Final Application Image
|
||||
# ================================
|
||||
FROM --platform=$TARGETOS/$TARGETARCH base AS final
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Install additional required libraries
|
||||
RUN apk add --no-cache \
|
||||
# packages for running the panel
|
||||
caddy ca-certificates supervisor supercronic fcgi coreutils \
|
||||
# required for installing plugins. Pulled from https://github.com/pelican-dev/panel/pull/2034
|
||||
zip unzip 7zip bzip2-dev yarn git
|
||||
|
||||
# Copy composer binary for runtime plugin dependency management
|
||||
COPY --from=composer /usr/local/bin/composer /usr/local/bin/composer
|
||||
COPY --chown=root:www-data --chmod=770 --from=composerbuild /build .
|
||||
COPY --chown=root:www-data --chmod=770 --from=yarnbuild /build/public ./public
|
||||
|
||||
# Create and remove directories
|
||||
RUN mkdir -p /pelican-data/storage /var/run/supervisord \
|
||||
# Symlinks for env, database, storage
|
||||
&& ln -s /pelican-data/.env /var/www/html/.env \
|
||||
&& ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \
|
||||
&& ln -s /pelican-data/storage /var/www/html/public/storage \
|
||||
&& ln -s /pelican-data/storage /var/www/html/storage/app/public \
|
||||
# Allow www-data write permissions where necessary
|
||||
&& chown -R www-data: /pelican-data .env ./storage ./bootstrap/cache /var/run/supervisord /var/www/html/public/storage \
|
||||
&& chmod -R 770 /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \
|
||||
&& chown -R www-data: /usr/local/etc/php/ /usr/local/etc/php-fpm.d/ /var/www/html/composer.json /var/www/html/composer.lock
|
||||
|
||||
# Configure Supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||
# Add Laravel scheduler to crontab
|
||||
COPY docker/crontab /etc/crontabs/crontab
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/healthcheck.sh /healthcheck.sh
|
||||
|
||||
HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD /bin/ash /healthcheck.sh
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
VOLUME /pelican-data
|
||||
|
||||
USER www-data
|
||||
|
||||
ENTRYPOINT [ "/bin/ash", "/entrypoint.sh" ]
|
||||
CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
class CacheCheck extends Check
|
||||
{
|
||||
protected ?string $driver = null;
|
||||
|
||||
public function driver(string $driver): self
|
||||
{
|
||||
$this->driver = $driver;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$driver = $this->driver ?? $this->defaultDriver();
|
||||
|
||||
$result = Result::make()->meta([
|
||||
'driver' => $driver,
|
||||
]);
|
||||
|
||||
try {
|
||||
return $this->canWriteValuesToCache($driver)
|
||||
? $result->ok(trans('admin/health.results.cache.ok'))
|
||||
: $result->failed(trans('admin/health.results.cache.failed_retrieve'));
|
||||
} catch (Exception $exception) {
|
||||
return $result->failed(trans('admin/health.results.cache.failed', ['error' => $exception->getMessage()]));
|
||||
}
|
||||
}
|
||||
|
||||
protected function defaultDriver(): ?string
|
||||
{
|
||||
return config('cache.default', 'file');
|
||||
}
|
||||
|
||||
protected function canWriteValuesToCache(?string $driver): bool
|
||||
{
|
||||
$expectedValue = Str::random(5);
|
||||
|
||||
$cacheName = "laravel-health:check-{$expectedValue}";
|
||||
|
||||
Cache::driver($driver)->put($cacheName, $expectedValue, 10);
|
||||
|
||||
$actualValue = Cache::driver($driver)->get($cacheName);
|
||||
|
||||
Cache::driver($driver)->forget($cacheName);
|
||||
|
||||
return $actualValue === $expectedValue;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
class DatabaseCheck extends Check
|
||||
{
|
||||
protected ?string $connectionName = null;
|
||||
|
||||
public function connectionName(string $connectionName): self
|
||||
{
|
||||
$this->connectionName = $connectionName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$connectionName = $this->connectionName ?? $this->getDefaultConnectionName();
|
||||
|
||||
$result = Result::make()->meta([
|
||||
'connection_name' => $connectionName,
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::connection($connectionName)->getPdo();
|
||||
|
||||
return $result->ok(trans('admin/health.results.database.ok'));
|
||||
} catch (Exception $exception) {
|
||||
return $result->failed(trans('admin/health.results.database.failed', ['error' => $exception->getMessage()]));
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDefaultConnectionName(): string
|
||||
{
|
||||
return config('database.default');
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
use function config;
|
||||
|
||||
class DebugModeCheck extends Check
|
||||
{
|
||||
protected bool $expected = false;
|
||||
|
||||
public function expectedToBe(bool $bool): self
|
||||
{
|
||||
$this->expected = $bool;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$actual = config('app.debug');
|
||||
|
||||
$result = Result::make()
|
||||
->meta([
|
||||
'actual' => $actual,
|
||||
'expected' => $this->expected,
|
||||
])
|
||||
->shortSummary($this->convertToWord($actual));
|
||||
|
||||
return $this->expected === $actual
|
||||
? $result->ok()
|
||||
: $result->failed(trans('admin/health.results.debugmode.failed', [
|
||||
'actual' => $this->convertToWord($actual),
|
||||
'expected' => $this->convertToWord($this->expected),
|
||||
]));
|
||||
}
|
||||
|
||||
protected function convertToWord(bool $boolean): string
|
||||
{
|
||||
return $boolean ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
class EnvironmentCheck extends Check
|
||||
{
|
||||
protected string $expectedEnvironment = 'production';
|
||||
|
||||
public function expectEnvironment(string $expectedEnvironment): self
|
||||
{
|
||||
$this->expectedEnvironment = $expectedEnvironment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$actualEnvironment = (string) App::environment();
|
||||
|
||||
$result = Result::make()
|
||||
->meta([
|
||||
'actual' => $actualEnvironment,
|
||||
'expected' => $this->expectedEnvironment,
|
||||
])
|
||||
->shortSummary($actualEnvironment);
|
||||
|
||||
return $this->expectedEnvironment === $actualEnvironment
|
||||
? $result->ok(trans('admin/health.results.environment.ok'))
|
||||
: $result->failed(trans('admin/health.results.environment.failed', [
|
||||
'actual' => $actualEnvironment,
|
||||
'expected' => $this->expectedEnvironment,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
use Spatie\Health\Enums\Status;
|
||||
|
||||
class NodeVersionsCheck extends Check
|
||||
{
|
||||
public function __construct(private SoftwareVersionService $versionService) {}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$all = Node::all();
|
||||
|
||||
if ($all->isEmpty()) {
|
||||
$result = Result::make()
|
||||
->notificationMessage(trans('admin/health.results.nodeversions.no_nodes_created'))
|
||||
->shortSummary(trans('admin/health.results.nodeversions.no_nodes'));
|
||||
$result->status = Status::skipped();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$outdated = $all
|
||||
->filter(fn (Node $node) => !isset($node->systemInformation()['exception']) && !$this->versionService->isLatestWings($node->systemInformation()['version']))
|
||||
->count();
|
||||
|
||||
$all = $all->count();
|
||||
$latestVersion = $this->versionService->latestWingsVersion();
|
||||
|
||||
$result = Result::make()
|
||||
->meta([
|
||||
'all' => $all,
|
||||
'outdated' => $outdated,
|
||||
'latestVersion' => $latestVersion,
|
||||
])
|
||||
->shortSummary($outdated === 0 ? trans('admin/health.results.nodeversions.all_up_to_date') : trans('admin/health.results.nodeversions.outdated', ['outdated' => $outdated, 'all' => $all]));
|
||||
|
||||
return $outdated === 0
|
||||
? $result->ok(trans('admin/health.results.nodeversions.ok'))
|
||||
: $result->failed(trans('admin/health.results.nodeversions.failed', ['outdated' => $outdated, 'all' => $all]));
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
class PanelVersionCheck extends Check
|
||||
{
|
||||
public function __construct(private SoftwareVersionService $versionService) {}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$isLatest = $this->versionService->isLatestPanel();
|
||||
$currentVersion = $this->versionService->currentPanelVersion();
|
||||
$latestVersion = $this->versionService->latestPanelVersion();
|
||||
|
||||
$result = Result::make()
|
||||
->meta([
|
||||
'isLatest' => $isLatest,
|
||||
'currentVersion' => $currentVersion,
|
||||
'latestVersion' => $latestVersion,
|
||||
])
|
||||
->shortSummary($isLatest ? trans('admin/health.results.panelversion.up_to_date') : trans('admin/health.results.panelversion.outdated'));
|
||||
|
||||
return $isLatest
|
||||
? $result->ok(trans('admin/health.results.panelversion.ok'))
|
||||
: $result->failed(trans('admin/health.results.panelversion.failed', [
|
||||
'currentVersion' => $currentVersion,
|
||||
'latestVersion' => $latestVersion,
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Composer\InstalledVersions;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Spatie\Health\Checks\Checks\ScheduleCheck as BaseCheck;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
class ScheduleCheck extends BaseCheck
|
||||
{
|
||||
public function run(): Result
|
||||
{
|
||||
$result = Result::make()->ok(trans('admin/health.results.schedule.ok'));
|
||||
|
||||
$lastHeartbeatTimestamp = cache()->store($this->cacheStoreName)->get($this->cacheKey);
|
||||
|
||||
if (!$lastHeartbeatTimestamp) {
|
||||
return $result->failed(trans('admin/health.results.schedule.failed_not_ran'));
|
||||
}
|
||||
|
||||
$latestHeartbeatAt = Carbon::createFromTimestamp($lastHeartbeatTimestamp);
|
||||
|
||||
$carbonVersion = InstalledVersions::getVersion('nesbot/carbon');
|
||||
|
||||
$minutesAgo = $latestHeartbeatAt->diffInMinutes();
|
||||
|
||||
if (version_compare($carbonVersion,
|
||||
'3.0.0', '<')) {
|
||||
$minutesAgo += 1;
|
||||
}
|
||||
|
||||
if ($minutesAgo > $this->heartbeatMaxAgeInMinutes) {
|
||||
return $result->failed(trans('admin/health.results.schedule.failed_last_ran', [
|
||||
'time' => $minutesAgo,
|
||||
]));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck as BaseCheck;
|
||||
|
||||
class UsedDiskSpaceCheck extends BaseCheck
|
||||
{
|
||||
protected function getDiskUsagePercentage(): int
|
||||
{
|
||||
$freeSpace = disk_free_space($this->filesystemName ?? '/');
|
||||
$totalSpace = disk_total_space($this->filesystemName ?? '/');
|
||||
|
||||
return 100 - ($freeSpace * 100 / $totalSpace);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Dev;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class GenerateTablerIconsEnum extends Command
|
||||
{
|
||||
protected $signature = 'dev:generate-tabler-icons-enum';
|
||||
|
||||
protected $description = 'Generate an enum for tabler icons based on the secondnetwork/blade-tabler-icons svgs';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$files = File::files(base_path('vendor/secondnetwork/blade-tabler-icons/resources/svg'));
|
||||
$files = array_filter($files, fn ($file) => $file->getExtension() === 'svg');
|
||||
|
||||
$enumContent = "<?php\n\n";
|
||||
$enumContent .= "namespace App\\Enums;\n\n";
|
||||
$enumContent .= "enum TablerIcon: string\n{\n";
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = pathinfo($file->getFilename(), PATHINFO_FILENAME);
|
||||
|
||||
// Letter V is duplicate, as "letter-v" and "letter-letter-v"
|
||||
if (str($filename)->contains('letter-letter')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filled icons exist with "-f" and "-filled", we only want the later
|
||||
if (str($filename)->endsWith('-f') && file_exists(base_path("vendor/secondnetwork/blade-tabler-icons/resources/svg/{$filename}illed.svg"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$caseName = str($filename)->title()->replace('-', '');
|
||||
$value = str($filename)->slug()->prepend('tabler-');
|
||||
|
||||
$enumContent .= " case $caseName = '$value';\n";
|
||||
}
|
||||
|
||||
$enumContent .= "}\n";
|
||||
|
||||
File::put(base_path('app/Enums/TablerIcon.php'), $enumContent);
|
||||
|
||||
$this->info('Enum generated');
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use App\Enums\EggFormat;
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class CheckEggUpdatesCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:egg:check-updates';
|
||||
|
||||
public function handle(EggExporterService $exporterService): void
|
||||
{
|
||||
$eggs = Egg::all();
|
||||
foreach ($eggs as $egg) {
|
||||
try {
|
||||
$this->check($egg, $exporterService);
|
||||
} catch (Exception $exception) {
|
||||
$this->error("$egg->name: Error ({$exception->getMessage()})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
private function check(Egg $egg, EggExporterService $exporterService): void
|
||||
{
|
||||
if (is_null($egg->update_url)) {
|
||||
$this->comment("$egg->name: Skipping (no update url set)");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$extension = strtolower(pathinfo(parse_url($egg->update_url, PHP_URL_PATH), PATHINFO_EXTENSION));
|
||||
|
||||
throw_if(empty($extension), new Exception('Unsupported file format.'));
|
||||
|
||||
$isYaml = in_array($extension, ['yaml', 'yml']);
|
||||
|
||||
$local = $isYaml
|
||||
? Yaml::parse($exporterService->handle($egg->id, EggFormat::YAML))
|
||||
: json_decode($exporterService->handle($egg->id, EggFormat::JSON), true);
|
||||
|
||||
$remote = Http::timeout(5)->connectTimeout(1)->get($egg->update_url);
|
||||
|
||||
throw_if($remote->failed(), new Exception("HTTP request returned status code {$remote->status()}"));
|
||||
|
||||
$remote = $remote->body();
|
||||
$remote = $isYaml ? Yaml::parse($remote) : json_decode($remote, true);
|
||||
|
||||
unset($local['exported_at'], $remote['exported_at']);
|
||||
|
||||
$localHash = md5(json_encode($local, JSON_THROW_ON_ERROR));
|
||||
$remoteHash = md5(json_encode($remote, JSON_THROW_ON_ERROR));
|
||||
|
||||
$status = $localHash === $remoteHash ? 'Up-to-date' : 'Found update';
|
||||
$this->{($localHash === $remoteHash) ? 'info' : 'warn'}("$egg->name: $status");
|
||||
|
||||
cache()->put("eggs.$egg->uuid.update", $localHash !== $remoteHash, now()->addHour());
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use App\Enums\EggFormat;
|
||||
use App\Exceptions\Service\InvalidFileUploadException;
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class NormalizeEggCommand extends Command
|
||||
{
|
||||
protected $description = 'Upgrades egg json/yaml to latest format as yaml';
|
||||
|
||||
protected $signature = 'p:egg:normalize {file} {--delete-original}';
|
||||
|
||||
public function __construct(
|
||||
private EggExporterService $exporter,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(EggImporterService $importer): int
|
||||
{
|
||||
$inputFile = $this->argument('file');
|
||||
$unparsed = file_get_contents($inputFile);
|
||||
if ($unparsed === false) {
|
||||
$this->error("Failed to read file: {$inputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$extension = strtolower(pathinfo($inputFile, PATHINFO_EXTENSION));
|
||||
|
||||
$this->info("Importing {$inputFile}");
|
||||
|
||||
$format = match ($extension) {
|
||||
'yaml', 'yml' => EggFormat::YAML,
|
||||
'json' => EggFormat::JSON,
|
||||
default => null,
|
||||
};
|
||||
if (is_null($format)) {
|
||||
$this->error(" -> unsupported extension {$extension} for {$inputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$eggArray = $importer->parse($unparsed, $format);
|
||||
} catch (InvalidFileUploadException) {
|
||||
$this->error(" -> unsupported file version, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (
|
||||
!array_key_exists('meta', $eggArray) ||
|
||||
!is_array($eggArray['meta'])
|
||||
) {
|
||||
$this->error(" -> does not contain existing meta or meta is not array, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (!array_key_exists('exported_at', $eggArray)) {
|
||||
$this->error(" -> does not contain existing exported_at, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (
|
||||
!array_key_exists('update_url', $eggArray['meta']) ||
|
||||
!is_string($eggArray['meta']['update_url'])
|
||||
) {
|
||||
$this->error(" -> does not contain existing meta.update_url or is not a string, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// We upgraded our in-memory array when we imported, so set to latest version before export
|
||||
$eggArray['meta']['version'] = Egg::EXPORT_VERSION;
|
||||
$eggArray['meta']['update_url'] = self::replaceExtension($eggArray['meta']['update_url']);
|
||||
self::fixVariableRules($eggArray);
|
||||
|
||||
$outputFile = self::replaceExtension($inputFile);
|
||||
|
||||
if ($outputFile === $inputFile && !$this->hasFileChanged($unparsed, $eggArray)) {
|
||||
$this->info(' -> no changes required');
|
||||
|
||||
return Command::SUCCESS;
|
||||
} else {
|
||||
$eggArray['exported_at'] = Carbon::now()->toAtomString();
|
||||
}
|
||||
|
||||
$this->info(" -> exporting to {$outputFile}");
|
||||
$yaml = $this->eggToYaml($eggArray);
|
||||
if (file_put_contents($outputFile, $yaml) === false) {
|
||||
$this->error(" -> failed to write output file: {$outputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($this->option('delete-original') && $outputFile !== $inputFile) {
|
||||
$this->info(' -> deleting input file as requested');
|
||||
if (!unlink($inputFile)) {
|
||||
$this->warn(" -> failed to delete original file: {$inputFile}");
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private static function replaceExtension(string $path): string
|
||||
{
|
||||
return preg_replace('/^(.*\.)(?:yml|json|yaml)$/', '$1yaml', $path) ?? $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private static function fixVariableRules(array &$eggArray): void
|
||||
{
|
||||
if (!array_key_exists('variables', $eggArray)) {
|
||||
return;
|
||||
}
|
||||
foreach ($eggArray['variables'] as &$var) {
|
||||
unset($var['field_type']);
|
||||
if (!array_key_exists('rules', $var)) {
|
||||
continue;
|
||||
}
|
||||
$var['rules'] = is_array($var['rules']) ? $var['rules'] : explode('|', $var['rules']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private function hasFileChanged(string $unparsed, array $eggArray): bool
|
||||
{
|
||||
$yaml = $this->eggToYaml($eggArray);
|
||||
|
||||
return $unparsed !== $yaml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private function eggToYaml(array $eggArray): string
|
||||
{
|
||||
return Yaml::dump($this->exporter->yamlExport($eggArray), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK | Yaml::DUMP_OBJECT_AS_MAP);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class UpdateEggIndexCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:egg:update-index';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
$data = Http::timeout(5)->connectTimeout(1)->get(config('panel.cdn.egg_index_url'))->throw()->json();
|
||||
} catch (Exception $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$index = [];
|
||||
foreach ($data['nests'] as $nest) {
|
||||
$nestName = $nest['nest_type'];
|
||||
|
||||
$this->info("Nest: $nestName");
|
||||
|
||||
$nestEggs = [];
|
||||
foreach ($nest['Eggs'] as $egg) {
|
||||
$eggName = $egg['egg']['name'];
|
||||
|
||||
$this->comment("Egg: $eggName");
|
||||
|
||||
$nestEggs[$egg['download_url']] = $eggName;
|
||||
}
|
||||
$index[$nestName] = $nestEggs;
|
||||
|
||||
$this->info('');
|
||||
}
|
||||
|
||||
cache()->forever('eggs.index', $index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,19 @@
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class AppSettingsCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
|
||||
protected $description = 'Configure basic environment settings for the Panel.';
|
||||
|
||||
protected $signature = 'p:environment:setup';
|
||||
protected $signature = 'p:environment:setup
|
||||
{--url= : The URL that this Panel is running on.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
@@ -20,13 +27,24 @@ class AppSettingsCommand extends Command
|
||||
|
||||
if (!config('app.key')) {
|
||||
$this->comment('Generating app key');
|
||||
$this->call('key:generate');
|
||||
Artisan::call('key:generate');
|
||||
}
|
||||
|
||||
$this->comment('Creating storage link');
|
||||
$this->call('storage:link');
|
||||
$this->variables['APP_TIMEZONE'] = 'UTC';
|
||||
|
||||
$this->comment('Caching components & icons');
|
||||
$this->call('filament:optimize');
|
||||
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
|
||||
'Application URL',
|
||||
config('app.url', 'https://example.com')
|
||||
);
|
||||
|
||||
// Make sure session cookies are set as "secure" when using HTTPS
|
||||
if (str_starts_with($this->variables['APP_URL'], 'https://')) {
|
||||
$this->variables['SESSION_SECURE_COOKIE'] = 'true';
|
||||
}
|
||||
|
||||
$this->comment('Writing variables to .env file');
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->info("Setup complete. Vist {$this->variables['APP_URL']}/installer to complete the installation");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
|
||||
class CacheSettingsCommand extends Command
|
||||
{
|
||||
@@ -23,10 +23,11 @@ class CacheSettingsCommand extends Command
|
||||
protected $signature = 'p:environment:cache
|
||||
{--driver= : The cache driver backend to use.}
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* CacheSettingsCommand constructor.
|
||||
*/
|
||||
@@ -52,6 +53,7 @@ class CacheSettingsCommand extends Command
|
||||
|
||||
if (config('queue.default') !== 'sync') {
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--use-redis' => true,
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use PDOException;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
|
||||
class DatabaseSettingsCommand extends Command
|
||||
{
|
||||
@@ -16,7 +15,6 @@ class DatabaseSettingsCommand extends Command
|
||||
'sqlite' => 'SQLite (recommended)',
|
||||
'mariadb' => 'MariaDB',
|
||||
'mysql' => 'MySQL',
|
||||
'pgsql' => 'PostgreSQL',
|
||||
];
|
||||
|
||||
protected $description = 'Configure database settings for the Panel.';
|
||||
@@ -24,12 +22,11 @@ class DatabaseSettingsCommand extends Command
|
||||
protected $signature = 'p:environment:database
|
||||
{--driver= : The database driver backend to use.}
|
||||
{--database= : The database to use.}
|
||||
{--host= : The connection address for the MySQL/ MariaDB/ PostgreSQL server.}
|
||||
{--port= : The connection port for the MySQL/ MariaDB/ PostgreSQL server.}
|
||||
{--username= : Username to use when connecting to the MySQL/ MariaDB/ PostgreSQL server.}
|
||||
{--password= : Password to use for the MySQL/ MariaDB/ PostgreSQL database.}';
|
||||
{--host= : The connection address for the MySQL/ MariaDB server.}
|
||||
{--port= : The connection port for the MySQL/ MariaDB server.}
|
||||
{--username= : Username to use when connecting to the MySQL/ MariaDB server.}
|
||||
{--password= : Password to use for the MySQL/ MariaDB database.}';
|
||||
|
||||
/** @var array<array-key, mixed> */
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
@@ -45,13 +42,6 @@ class DatabaseSettingsCommand extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->error('Changing the database driver will NOT move any database data!');
|
||||
$this->error('Please make sure you made a database backup first!');
|
||||
$this->error('After changing the driver you will have to manually move the old data to the new database.');
|
||||
if (!$this->confirm('Do you want to continue?')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$selected = config('database.default', 'sqlite');
|
||||
$this->variables['DB_CONNECTION'] = $this->option('driver') ?? $this->choice(
|
||||
'Database Driver',
|
||||
@@ -60,7 +50,7 @@ class DatabaseSettingsCommand extends Command
|
||||
);
|
||||
|
||||
if ($this->variables['DB_CONNECTION'] === 'mysql') {
|
||||
$this->output->note(trans('commands.database_settings.DB_HOST_note'));
|
||||
$this->output->note(__('commands.database_settings.DB_HOST_note'));
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.mysql.host', '127.0.0.1')
|
||||
@@ -76,7 +66,7 @@ class DatabaseSettingsCommand extends Command
|
||||
config('database.connections.mysql.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.mysql.username', 'pelican')
|
||||
@@ -85,7 +75,7 @@ class DatabaseSettingsCommand extends Command
|
||||
$askForMySQLPassword = true;
|
||||
if (!empty(config('database.connections.mysql.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.mysql.password');
|
||||
$askForMySQLPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
|
||||
$askForMySQLPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
|
||||
}
|
||||
|
||||
if ($askForMySQLPassword) {
|
||||
@@ -107,11 +97,11 @@ class DatabaseSettingsCommand extends Command
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (PDOException $exception) {
|
||||
} catch (\PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||
$this->output->error(__('commands.database_settings.DB_error_2'));
|
||||
|
||||
if ($this->confirm(trans('commands.database_settings.go_back'))) {
|
||||
if ($this->confirm(__('commands.database_settings.go_back'))) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
|
||||
return $this->handle();
|
||||
@@ -120,7 +110,7 @@ class DatabaseSettingsCommand extends Command
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'mariadb') {
|
||||
$this->output->note(trans('commands.database_settings.DB_HOST_note'));
|
||||
$this->output->note(__('commands.database_settings.DB_HOST_note'));
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.mariadb.host', '127.0.0.1')
|
||||
@@ -136,7 +126,7 @@ class DatabaseSettingsCommand extends Command
|
||||
config('database.connections.mariadb.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.mariadb.username', 'pelican')
|
||||
@@ -145,7 +135,7 @@ class DatabaseSettingsCommand extends Command
|
||||
$askForMariaDBPassword = true;
|
||||
if (!empty(config('database.connections.mariadb.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.mariadb.password');
|
||||
$askForMariaDBPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
|
||||
$askForMariaDBPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
|
||||
}
|
||||
|
||||
if ($askForMariaDBPassword) {
|
||||
@@ -167,73 +157,11 @@ class DatabaseSettingsCommand extends Command
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (PDOException $exception) {
|
||||
} catch (\PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||
$this->output->error(__('commands.database_settings.DB_error_2'));
|
||||
|
||||
if ($this->confirm(trans('commands.database_settings.go_back'))) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'pgsql') {
|
||||
$this->output->note(trans('commands.database_settings.DB_HOST_note'));
|
||||
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
|
||||
'Database Host',
|
||||
config('database.connections.pgsql.host', '127.0.0.1')
|
||||
);
|
||||
|
||||
$this->variables['DB_PORT'] = $this->option('port') ?? $this->ask(
|
||||
'Database Port',
|
||||
config('database.connections.pgsql.port', 5432)
|
||||
);
|
||||
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Name',
|
||||
config('database.connections.pgsql.database', 'panel')
|
||||
);
|
||||
|
||||
$this->output->note(trans('commands.database_settings.DB_USERNAME_note'));
|
||||
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
|
||||
'Database Username',
|
||||
config('database.connections.pgsql.username', 'pelican')
|
||||
);
|
||||
|
||||
$askForPgSQLPassword = true;
|
||||
if (!empty(config('database.connections.pgsql.password')) && $this->input->isInteractive()) {
|
||||
$this->variables['DB_PASSWORD'] = config('database.connections.pgsql.password');
|
||||
$askForPgSQLPassword = $this->confirm(trans('commands.database_settings.DB_PASSWORD_note'));
|
||||
}
|
||||
|
||||
if ($askForPgSQLPassword) {
|
||||
$this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password');
|
||||
}
|
||||
|
||||
try {
|
||||
// Test connection
|
||||
config()->set('database.connections._panel_command_test', [
|
||||
'driver' => 'pgsql',
|
||||
'host' => $this->variables['DB_HOST'],
|
||||
'port' => $this->variables['DB_PORT'],
|
||||
'database' => $this->variables['DB_DATABASE'],
|
||||
'username' => $this->variables['DB_USERNAME'],
|
||||
'password' => $this->variables['DB_PASSWORD'],
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
]);
|
||||
|
||||
$this->database->connection('_panel_command_test')->getPdo();
|
||||
} catch (PDOException $exception) {
|
||||
$this->output->error(sprintf('Unable to connect to the PostgreSQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
|
||||
$this->output->error(trans('commands.database_settings.DB_error_2'));
|
||||
|
||||
if ($this->confirm(trans('commands.database_settings.go_back'))) {
|
||||
if ($this->confirm(__('commands.database_settings.go_back'))) {
|
||||
$this->database->disconnect('_panel_command_test');
|
||||
|
||||
return $this->handle();
|
||||
@@ -244,7 +172,7 @@ class DatabaseSettingsCommand extends Command
|
||||
} elseif ($this->variables['DB_CONNECTION'] === 'sqlite') {
|
||||
$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
|
||||
'Database Path',
|
||||
(string) env('DB_DATABASE', 'database.sqlite')
|
||||
env('DB_DATABASE', 'database.sqlite')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Exceptions\PanelException;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
|
||||
class EmailSettingsCommand extends Command
|
||||
{
|
||||
@@ -23,13 +22,12 @@ class EmailSettingsCommand extends Command
|
||||
{--username=}
|
||||
{--password=}';
|
||||
|
||||
/** @var array<array-key, mixed> */
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*
|
||||
* @throws PanelException
|
||||
* @throws \App\Exceptions\PanelException
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
@@ -72,7 +70,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for SMTP driver.
|
||||
*/
|
||||
private function setupSmtpDriverVariables(): void
|
||||
private function setupSmtpDriverVariables()
|
||||
{
|
||||
$this->variables['MAIL_HOST'] = $this->option('host') ?? $this->ask(
|
||||
trans('command/messages.environment.mail.ask_smtp_host'),
|
||||
@@ -93,7 +91,7 @@ class EmailSettingsCommand extends Command
|
||||
trans('command/messages.environment.mail.ask_smtp_password')
|
||||
);
|
||||
|
||||
$this->variables['MAIL_SCHEME'] = $this->option('encryption') ?? $this->choice(
|
||||
$this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice(
|
||||
trans('command/messages.environment.mail.ask_encryption'),
|
||||
['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'],
|
||||
config('mail.mailers.smtp.encryption', 'tls')
|
||||
@@ -103,7 +101,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for mailgun driver.
|
||||
*/
|
||||
private function setupMailgunDriverVariables(): void
|
||||
private function setupMailgunDriverVariables()
|
||||
{
|
||||
$this->variables['MAILGUN_DOMAIN'] = $this->option('host') ?? $this->ask(
|
||||
trans('command/messages.environment.mail.ask_mailgun_domain'),
|
||||
@@ -124,7 +122,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for mandrill driver.
|
||||
*/
|
||||
private function setupMandrillDriverVariables(): void
|
||||
private function setupMandrillDriverVariables()
|
||||
{
|
||||
$this->variables['MANDRILL_SECRET'] = $this->option('password') ?? $this->ask(
|
||||
trans('command/messages.environment.mail.ask_mandrill_secret'),
|
||||
@@ -135,7 +133,7 @@ class EmailSettingsCommand extends Command
|
||||
/**
|
||||
* Handle variables for postmark driver.
|
||||
*/
|
||||
private function setupPostmarkDriverVariables(): void
|
||||
private function setupPostmarkDriverVariables()
|
||||
{
|
||||
$this->variables['MAIL_DRIVER'] = 'smtp';
|
||||
$this->variables['MAIL_HOST'] = 'smtp.postmarkapp.com';
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
|
||||
class QueueSettingsCommand extends Command
|
||||
{
|
||||
@@ -23,10 +23,11 @@ class QueueSettingsCommand extends Command
|
||||
protected $signature = 'p:environment:queue
|
||||
{--driver= : The queue driver backend to use.}
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* QueueSettingsCommand constructor.
|
||||
*/
|
||||
@@ -51,6 +52,7 @@ class QueueSettingsCommand extends Command
|
||||
$this->requestRedisSettings();
|
||||
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--use-redis' => true,
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -18,21 +18,10 @@ class QueueWorkerServiceCommand extends Command
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (@file_exists('/.dockerenv')) {
|
||||
$result = Process::run('supervisorctl restart queue-worker');
|
||||
if ($result->failed()) {
|
||||
$this->error('Error restarting service: ' . $result->errorOutput());
|
||||
|
||||
return;
|
||||
}
|
||||
$this->line('Queue worker service file updated successfully.');
|
||||
|
||||
return;
|
||||
}
|
||||
$serviceName = $this->option('service-name') ?? $this->ask('Queue worker service name', 'pelican-queue');
|
||||
$path = '/etc/systemd/system/' . $serviceName . '.service';
|
||||
|
||||
$fileExists = @file_exists($path);
|
||||
$fileExists = file_exists($path);
|
||||
if ($fileExists && !$this->option('overwrite') && !$this->confirm('The service file already exists. Do you want to overwrite it?')) {
|
||||
$this->line('Creation of queue worker service file aborted because service file already exists.');
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
|
||||
class RedisSetupCommand extends Command
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use RequestRedisSettingsTrait;
|
||||
|
||||
protected $description = 'Configure the Panel to use Redis as cache, queue and session driver.';
|
||||
|
||||
protected $signature = 'p:redis:setup
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
/**
|
||||
* RedisSetupCommand constructor.
|
||||
*/
|
||||
public function __construct(private Kernel $console)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->variables['CACHE_STORE'] = 'redis';
|
||||
$this->variables['QUEUE_CONNECTION'] = 'redis';
|
||||
$this->variables['SESSION_DRIVER'] = 'redis';
|
||||
|
||||
$this->requestRedisSettings();
|
||||
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
|
||||
$this->writeToEnvironment($this->variables);
|
||||
|
||||
$this->info($this->console->output());
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\Environment;
|
||||
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
use App\Traits\EnvironmentWriterTrait;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use App\Traits\Commands\EnvironmentWriterTrait;
|
||||
use App\Traits\Commands\RequestRedisSettingsTrait;
|
||||
|
||||
class SessionSettingsCommand extends Command
|
||||
{
|
||||
@@ -24,10 +24,11 @@ class SessionSettingsCommand extends Command
|
||||
protected $signature = 'p:environment:session
|
||||
{--driver= : The session driver backend to use.}
|
||||
{--redis-host= : Redis host to use for connections.}
|
||||
{--redis-user= : User used to connect to redis.}
|
||||
{--redis-pass= : Password used to connect to redis.}
|
||||
{--redis-port= : Port to connect to redis over.}';
|
||||
|
||||
protected array $variables = [];
|
||||
|
||||
/**
|
||||
* SessionSettingsCommand constructor.
|
||||
*/
|
||||
@@ -53,6 +54,7 @@ class SessionSettingsCommand extends Command
|
||||
|
||||
if (config('queue.default') !== 'sync') {
|
||||
$this->call('p:environment:queue-service', [
|
||||
'--use-redis' => true,
|
||||
'--overwrite' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class InfoCommand extends Command
|
||||
{
|
||||
@@ -10,8 +11,98 @@ class InfoCommand extends Command
|
||||
|
||||
protected $signature = 'p:info';
|
||||
|
||||
/**
|
||||
* InfoCommand constructor.
|
||||
*/
|
||||
public function __construct(private SoftwareVersionService $versionService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle execution of command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->call('about');
|
||||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', $this->versionService->versionData()['version']],
|
||||
['Latest Version', $this->versionService->getPanel()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Application Configuration');
|
||||
$this->table([], [
|
||||
['Environment', config('app.env') === 'production' ? config('app.env') : $this->formatText(config('app.env'), 'bg=red')],
|
||||
['Debug Mode', config('app.debug') ? $this->formatText('Yes', 'bg=red') : 'No'],
|
||||
['Application Name', config('app.name')],
|
||||
['Application URL', config('app.url')],
|
||||
['Installation Directory', base_path()],
|
||||
['Cache Driver', config('cache.default')],
|
||||
['Queue Driver', config('queue.default') === 'sync' ? $this->formatText(config('queue.default'), 'bg=red') : config('queue.default')],
|
||||
['Session Driver', config('session.driver')],
|
||||
['Filesystem Driver', config('filesystems.default')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Database Configuration');
|
||||
$driver = config('database.default');
|
||||
if ($driver === 'sqlite') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("database.connections.$driver.host")],
|
||||
['Port', config("database.connections.$driver.port")],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
['Username', config("database.connections.$driver.username")],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
$this->output->title('Email Configuration');
|
||||
$driver = config('mail.default');
|
||||
if ($driver === 'smtp') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("mail.mailers.$driver.host")],
|
||||
['Port', config("mail.mailers.$driver.port")],
|
||||
['Username', config("mail.mailers.$driver.username")],
|
||||
['Encryption', config("mail.mailers.$driver.encryption")],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
$this->output->title('Backup Configuration');
|
||||
$driver = config('backups.default');
|
||||
if ($driver === 's3') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Region', config("backups.disks.$driver.region")],
|
||||
['Bucket', config("backups.disks.$driver.bucket")],
|
||||
['Endpoint', config("backups.disks.$driver.endpoint")],
|
||||
['Use path style endpoint', config("backups.disks.$driver.use_path_style_endpoint") ? 'Yes' : 'No'],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
], 'compact');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format output in a Name: Value manner.
|
||||
*/
|
||||
private function formatText(string $value, string $opts = ''): string
|
||||
{
|
||||
return sprintf('<%s>%s</>', $opts, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands\Maintenance;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Carbon;
|
||||
use SplFileInfo;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
|
||||
|
||||
class CleanServiceBackupFilesCommand extends Command
|
||||
{
|
||||
@@ -33,10 +32,9 @@ class CleanServiceBackupFilesCommand extends Command
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
/** @var SplFileInfo[] */
|
||||
$files = $this->disk->files('services/.bak');
|
||||
|
||||
collect($files)->each(function ($file) {
|
||||
collect($files)->each(function (\SplFileInfo $file) {
|
||||
$lastModified = Carbon::createFromTimestamp($this->disk->lastModified($file->getPath()));
|
||||
if ($lastModified->diffInMinutes(Carbon::now()) > self::BACKUP_THRESHOLD_MINUTES) {
|
||||
$this->disk->delete($file->getPath());
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Console\Commands\Maintenance;
|
||||
use App\Models\Backup;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Console\Command;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PruneOrphanedBackupsCommand extends Command
|
||||
{
|
||||
@@ -16,7 +15,9 @@ class PruneOrphanedBackupsCommand extends Command
|
||||
public function handle(): void
|
||||
{
|
||||
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
|
||||
throw_if(!$since || !is_digit($since), new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.'));
|
||||
if (!$since || !is_digit($since)) {
|
||||
throw new \InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
|
||||
}
|
||||
|
||||
$query = Backup::query()
|
||||
->whereNull('completed_at')
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App\Console\Commands\Node;
|
||||
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Models\Node;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\Nodes\NodeCreationService;
|
||||
|
||||
class MakeNodeCommand extends Command
|
||||
{
|
||||
@@ -25,46 +24,52 @@ class MakeNodeCommand extends Command
|
||||
{--overallocateCpu= : Enter the amount of cpu to overallocate (% or -1 to overallocate the maximum).}
|
||||
{--uploadSize= : Enter the maximum upload filesize.}
|
||||
{--daemonListeningPort= : Enter the daemon listening port.}
|
||||
{--daemonConnectingPort= : Enter the daemon connecting port.}
|
||||
{--daemonSFTPPort= : Enter the daemon SFTP listening port.}
|
||||
{--daemonSFTPAlias= : Enter the daemon SFTP alias.}
|
||||
{--daemonBase= : Enter the base folder.}';
|
||||
|
||||
protected $description = 'Creates a new node on the system via the CLI.';
|
||||
|
||||
/**
|
||||
* MakeNodeCommand constructor.
|
||||
*/
|
||||
public function __construct(private NodeCreationService $creationService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the command execution process.
|
||||
*
|
||||
* @throws DataValidationException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$data['name'] = $this->option('name') ?? $this->ask(trans('commands.make_node.name'));
|
||||
$data['description'] = $this->option('description') ?? $this->ask(trans('commands.make_node.description'));
|
||||
$data['name'] = $this->option('name') ?? $this->ask(__('commands.make_node.name'));
|
||||
$data['description'] = $this->option('description') ?? $this->ask(__('commands.make_node.description'));
|
||||
$data['scheme'] = $this->option('scheme') ?? $this->anticipate(
|
||||
trans('commands.make_node.scheme'),
|
||||
__('commands.make_node.scheme'),
|
||||
['https', 'http'],
|
||||
'https'
|
||||
);
|
||||
|
||||
$data['fqdn'] = $this->option('fqdn') ?? $this->ask(trans('commands.make_node.fqdn'));
|
||||
$data['public'] = $this->option('public') ?? $this->confirm(trans('commands.make_node.public'), true);
|
||||
$data['behind_proxy'] = $this->option('proxy') ?? $this->confirm(trans('commands.make_node.behind_proxy'));
|
||||
$data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm(trans('commands.make_node.maintenance_mode'));
|
||||
$data['memory'] = $this->option('maxMemory') ?? $this->ask(trans('commands.make_node.memory'), '0');
|
||||
$data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask(trans('commands.make_node.memory_overallocate'), '-1');
|
||||
$data['disk'] = $this->option('maxDisk') ?? $this->ask(trans('commands.make_node.disk'), '0');
|
||||
$data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask(trans('commands.make_node.disk_overallocate'), '-1');
|
||||
$data['cpu'] = $this->option('maxCpu') ?? $this->ask(trans('commands.make_node.cpu'), '0');
|
||||
$data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(trans('commands.make_node.cpu_overallocate'), '-1');
|
||||
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(trans('commands.make_node.upload_size'), '256');
|
||||
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(trans('commands.make_node.daemonListen'), '8080');
|
||||
$data['daemon_connect'] = $this->option('daemonConnectingPort') ?? $this->ask(trans('commands.make_node.daemonConnect'), '8080');
|
||||
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(trans('commands.make_node.daemonSFTP'), '2022');
|
||||
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(trans('commands.make_node.daemonSFTPAlias'), '');
|
||||
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(trans('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
|
||||
$data['fqdn'] = $this->option('fqdn') ?? $this->ask(__('commands.make_node.fqdn'));
|
||||
$data['public'] = $this->option('public') ?? $this->confirm(__('commands.make_node.public'), true);
|
||||
$data['behind_proxy'] = $this->option('proxy') ?? $this->confirm(__('commands.make_node.behind_proxy'));
|
||||
$data['maintenance_mode'] = $this->option('maintenance') ?? $this->confirm(__('commands.make_node.maintenance_mode'));
|
||||
$data['memory'] = $this->option('maxMemory') ?? $this->ask(__('commands.make_node.memory'), '0');
|
||||
$data['memory_overallocate'] = $this->option('overallocateMemory') ?? $this->ask(__('commands.make_node.memory_overallocate'), '-1');
|
||||
$data['disk'] = $this->option('maxDisk') ?? $this->ask(__('commands.make_node.disk'), '0');
|
||||
$data['disk_overallocate'] = $this->option('overallocateDisk') ?? $this->ask(__('commands.make_node.disk_overallocate'), '-1');
|
||||
$data['cpu'] = $this->option('maxCpu') ?? $this->ask(__('commands.make_node.cpu'), '0');
|
||||
$data['cpu_overallocate'] = $this->option('overallocateCpu') ?? $this->ask(__('commands.make_node.cpu_overallocate'), '-1');
|
||||
$data['upload_size'] = $this->option('uploadSize') ?? $this->ask(__('commands.make_node.upload_size'), '256');
|
||||
$data['daemon_listen'] = $this->option('daemonListeningPort') ?? $this->ask(__('commands.make_node.daemonListen'), '8080');
|
||||
$data['daemon_sftp'] = $this->option('daemonSFTPPort') ?? $this->ask(__('commands.make_node.daemonSFTP'), '2022');
|
||||
$data['daemon_sftp_alias'] = $this->option('daemonSFTPAlias') ?? $this->ask(__('commands.make_node.daemonSFTPAlias'), '');
|
||||
$data['daemon_base'] = $this->option('daemonBase') ?? $this->ask(__('commands.make_node.daemonBase'), '/var/lib/pelican/volumes');
|
||||
|
||||
$node = Node::create($data);
|
||||
$this->line(trans('commands.make_node.success', ['name' => $data['name'], 'id' => $node->id]));
|
||||
$node = $this->creationService->handle($data);
|
||||
$this->line(__('commands.make_node.success', ['name' => $data['name'], 'id' => $node->id]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,16 +17,16 @@ class NodeConfigurationCommand extends Command
|
||||
{
|
||||
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
|
||||
|
||||
/** @var Node $node */
|
||||
/** @var \App\Models\Node $node */
|
||||
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
|
||||
$this->error(trans('commands.node_config.error_not_exist'));
|
||||
$this->error(__('commands.node_config.error_not_exist'));
|
||||
|
||||
exit(1);
|
||||
});
|
||||
|
||||
$format = $this->option('format');
|
||||
if (!in_array($format, ['yaml', 'yml', 'json'])) {
|
||||
$this->error(trans('commands.node_config.error_invalid_format'));
|
||||
$this->error(__('commands.node_config.error_invalid_format'));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use Illuminate\Foundation\Console\ConfigCacheCommand as BaseConfigCacheCommand;
|
||||
|
||||
class ConfigCacheCommand extends BaseConfigCacheCommand
|
||||
{
|
||||
/**
|
||||
* Prevent config from being cached
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->components->warn('Configuration caching has been disabled.');
|
||||
|
||||
$this->line(' Reason: This application uses dynamic plugins. Caching config');
|
||||
$this->line(' prevents /plugins/config/*.php files from being loaded correctly.');
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@ class KeyGenerateCommand extends BaseKeyGenerateCommand
|
||||
public function handle(): void
|
||||
{
|
||||
if (!empty(config('app.key')) && $this->input->isInteractive()) {
|
||||
$this->output->warning(trans('commands.key_generate.error_already_exist'));
|
||||
if (!$this->confirm(trans('commands.key_generate.understand'))) {
|
||||
$this->output->warning(__('commands.key_generate.error_already_exist'));
|
||||
if (!$this->confirm(__('commands.key_generate.understand'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->confirm(trans('commands.key_generate.continue'))) {
|
||||
if (!$this->confirm(__('commands.key_generate.continue'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use Illuminate\Foundation\Console\OptimizeCommand as BaseOptimizeCommand;
|
||||
|
||||
class OptimizeCommand extends BaseOptimizeCommand
|
||||
{
|
||||
/**
|
||||
* Prevent config from being cached
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function getOptimizeTasks()
|
||||
{
|
||||
return array_except(parent::getOptimizeTasks(), 'config');
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use App\Traits\Commands\RequiresDatabaseMigrations;
|
||||
use App\Console\RequiresDatabaseMigrations;
|
||||
use Illuminate\Database\Console\Seeds\SeedCommand as BaseSeedCommand;
|
||||
|
||||
class SeedCommand extends BaseSeedCommand
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands\Overrides;
|
||||
|
||||
use App\Traits\Commands\RequiresDatabaseMigrations;
|
||||
use App\Console\RequiresDatabaseMigrations;
|
||||
use Illuminate\Foundation\Console\UpCommand as BaseUpCommand;
|
||||
|
||||
class UpCommand extends BaseUpCommand
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ComposerPluginsCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:composer';
|
||||
|
||||
protected $description = 'Makes sure the needed composer packages for all installed plugins are available.';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
try {
|
||||
$pluginService->manageComposerPackages();
|
||||
} catch (Exception $exception) {
|
||||
report($exception);
|
||||
|
||||
$this->error($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DisablePluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:disable {id?}';
|
||||
|
||||
protected $description = 'Disables a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$plugin->canDisable()) {
|
||||
$this->error("Plugin can't be disabled!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginService->disablePlugin($plugin);
|
||||
|
||||
$this->info('Plugin disabled.');
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class InstallPluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:install {id?}';
|
||||
|
||||
protected $description = 'Installs a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($plugin->status !== PluginStatus::NotInstalled) {
|
||||
$this->error('Plugin is already installed!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pluginService->installPlugin($plugin);
|
||||
|
||||
$this->info('Plugin installed and enabled.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not install plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ListPluginsCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:list';
|
||||
|
||||
protected $description = 'List all installed plugins';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$plugins = Plugin::query()->get(['name', 'author', 'status', 'version', 'panels', 'category']);
|
||||
|
||||
if (count($plugins) < 1) {
|
||||
$this->warn('No plugins installed');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->table(['Name', 'Author', 'Status', 'Version', 'Panels', 'Category'], $plugins->toArray());
|
||||
|
||||
$this->output->newLine();
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Enums\PluginCategory;
|
||||
use App\Enums\PluginStatus;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MakePluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:make
|
||||
{--name=}
|
||||
{--author=}
|
||||
{--description=}
|
||||
{--category=}
|
||||
{--url=}
|
||||
{--updateUrl=}
|
||||
{--panels=}
|
||||
{--panelVersion=}';
|
||||
|
||||
protected $description = 'Create a new plugin';
|
||||
|
||||
public function __construct(private Filesystem $filesystem)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$name = $this->option('name') ?? $this->ask('Name');
|
||||
$name = preg_replace('/[^A-Za-z0-9 ]/', '', Str::ascii($name));
|
||||
|
||||
$id = Str::slug($name);
|
||||
|
||||
if ($this->filesystem->exists(plugin_path($id))) {
|
||||
$this->error('Plugin with that name already exists!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$author = $this->option('author') ?? $this->ask('Author', cache('plugin.author'));
|
||||
$author = preg_replace('/[^A-Za-z0-9 ]/', '', Str::ascii($author));
|
||||
cache()->forever('plugin.author', $author);
|
||||
|
||||
$namespace = Str::studly($author) . '\\' . Str::studly($name);
|
||||
$class = Str::studly($name . 'Plugin');
|
||||
|
||||
if (class_exists('\\' . $namespace . '\\' . $class)) {
|
||||
$this->error('Plugin class with that name already exists!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Creating Plugin "' . $name . '" (' . $id . ') by ' . $author);
|
||||
|
||||
$description = $this->option('description') ?? $this->ask('Description (can be empty)');
|
||||
|
||||
$category = $this->option('category') ?? $this->choice('Category', collect(PluginCategory::cases())->mapWithKeys(fn (PluginCategory $category) => [$category->value => $category->getLabel()])->toArray(), PluginCategory::Plugin->value);
|
||||
|
||||
if (!PluginCategory::tryFrom($category)) {
|
||||
$this->error('Unknown plugin category!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $this->option('url') ?? $this->ask('URL (can be empty)');
|
||||
$updateUrl = $this->option('updateUrl') ?? $this->ask('Update URL (can be empty)');
|
||||
|
||||
$panels = $this->option('panels');
|
||||
if (!$panels) {
|
||||
if ($this->confirm('Should the plugin be available on all panels?', true)) {
|
||||
$panels = null;
|
||||
} else {
|
||||
$panels = $this->choice('Panels (comma separated list)', [
|
||||
'admin' => 'Admin Area',
|
||||
'server' => 'Client Area',
|
||||
'app' => 'Server List',
|
||||
], multiple: true);
|
||||
}
|
||||
}
|
||||
$panels = is_string($panels) ? explode(',', $panels) : $panels;
|
||||
|
||||
$panelVersion = $this->option('panelVersion');
|
||||
if (!$panelVersion) {
|
||||
$panelVersion = $this->ask('Required panel version (leave empty for no constraint)', config('app.version') === 'canary' ? null : config('app.version'));
|
||||
|
||||
if ($panelVersion && $this->confirm("Should the version constraint be minimal instead of strict? ($panelVersion or higher instead of only $panelVersion)")) {
|
||||
$panelVersion = "^$panelVersion";
|
||||
}
|
||||
}
|
||||
|
||||
$composerPackages = null;
|
||||
// TODO: ask for composer packages?
|
||||
|
||||
// Create base directory
|
||||
$this->filesystem->makeDirectory(plugin_path($id));
|
||||
|
||||
// Write plugin.json
|
||||
$this->filesystem->put(plugin_path($id, 'plugin.json'), json_encode([
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
'author' => $author,
|
||||
'version' => '1.0.0',
|
||||
'description' => $description,
|
||||
'category' => $category,
|
||||
'url' => $url,
|
||||
'update_url' => $updateUrl,
|
||||
'namespace' => $namespace,
|
||||
'class' => $class,
|
||||
'panels' => $panels,
|
||||
'panel_version' => $panelVersion,
|
||||
'composer_packages' => $composerPackages,
|
||||
'meta' => [
|
||||
'status' => PluginStatus::Enabled,
|
||||
'status_message' => null,
|
||||
],
|
||||
], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
// Create src directory and create main class
|
||||
$this->filesystem->makeDirectory(plugin_path($id, 'src'));
|
||||
$this->filesystem->put(plugin_path($id, 'src', $class . '.php'), Str::replace(['$namespace$', '$class$', '$id$'], [$namespace, $class, $id], file_get_contents(__DIR__ . '/Plugin.stub')));
|
||||
|
||||
// Create Providers directory and create service provider
|
||||
$this->filesystem->makeDirectory(plugin_path($id, 'src', 'Providers'));
|
||||
$this->filesystem->put(plugin_path($id, 'src', 'Providers', $class . 'Provider.php'), Str::replace(['$namespace$', '$class$'], [$namespace, $class], file_get_contents(__DIR__ . '/PluginProvider.stub')));
|
||||
|
||||
// Create config directory and create config file
|
||||
$this->filesystem->makeDirectory(plugin_path($id, 'config'));
|
||||
$this->filesystem->put(plugin_path($id, 'config', $id . '.php'), Str::replace(['$name$'], [$name], file_get_contents(__DIR__ . '/PluginConfig.stub')));
|
||||
|
||||
$this->info('Plugin created.');
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace $namespace$;
|
||||
|
||||
use Filament\Contracts\Plugin;
|
||||
use Filament\Panel;
|
||||
|
||||
class $class$ implements Plugin
|
||||
{
|
||||
public function getId(): string
|
||||
{
|
||||
return '$id$';
|
||||
}
|
||||
|
||||
public function register(Panel $panel): void
|
||||
{
|
||||
// Allows you to use any configuration option that is available to the panel.
|
||||
// This includes registering resources, custom pages, themes, render hooks and more.
|
||||
}
|
||||
|
||||
public function boot(Panel $panel): void
|
||||
{
|
||||
// Is run only when the panel that the plugin is being registered to is actually in-use. It is executed by a middleware class.
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// Config values for $name$
|
||||
];
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace $namespace$\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class $class$Provider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Enums\PluginStatus;
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UninstallPluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:uninstall {id?} {--delete : Delete the plugin files}';
|
||||
|
||||
protected $description = 'Uninstalls a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($plugin->status === PluginStatus::NotInstalled) {
|
||||
$this->error('Plugin is not installed!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$deleteFiles = $this->option('delete');
|
||||
if ($this->input->isInteractive() && !$deleteFiles) {
|
||||
$deleteFiles = $this->confirm('Do you also want to delete the plugin files?');
|
||||
}
|
||||
|
||||
try {
|
||||
$pluginService->uninstallPlugin($plugin, $deleteFiles);
|
||||
|
||||
$this->info('Plugin uninstalled' . ($deleteFiles ? ' and files deleted' : '') . '.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not uninstall plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Plugin;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Services\Helpers\PluginService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdatePluginCommand extends Command
|
||||
{
|
||||
protected $signature = 'p:plugin:update {id?}';
|
||||
|
||||
protected $description = 'Updates a plugin';
|
||||
|
||||
public function handle(PluginService $pluginService): void
|
||||
{
|
||||
$id = $this->argument('id') ?? $this->choice('Plugin', Plugin::pluck('name', 'id')->toArray());
|
||||
|
||||
$plugin = Plugin::find(str($id)->lower()->toString());
|
||||
|
||||
if (!$plugin) {
|
||||
$this->error('Plugin does not exist!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$plugin->isUpdateAvailable()) {
|
||||
$this->error("Plugin doesn't need updating!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pluginService->updatePlugin($plugin);
|
||||
|
||||
$this->info('Plugin updated.');
|
||||
} catch (Exception $exception) {
|
||||
$this->error('Could not update plugin: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Console\Commands\Schedule;
|
||||
|
||||
use App\Models\Schedule;
|
||||
use App\Services\Schedules\ProcessScheduleService;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Schedule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Throwable;
|
||||
use App\Services\Schedules\ProcessScheduleService;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class ProcessRunnableCommand extends Command
|
||||
{
|
||||
@@ -14,18 +14,21 @@ class ProcessRunnableCommand extends Command
|
||||
|
||||
protected $description = 'Process schedules in the database and determine which are ready to run.';
|
||||
|
||||
public function handle(ProcessScheduleService $processScheduleService): int
|
||||
/**
|
||||
* Handle command execution.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$schedules = Schedule::query()
|
||||
->with('tasks')
|
||||
->whereRelation('server', fn (Builder $builder) => $builder->whereNull('status'))
|
||||
->where('is_active', true)
|
||||
->where('is_processing', false)
|
||||
->where('next_run_at', '<=', now('UTC')->toDateTimeString())
|
||||
->where('next_run_at', '<=', Carbon::now()->toDateTimeString())
|
||||
->get();
|
||||
|
||||
if ($schedules->count() < 1) {
|
||||
$this->line(trans('commands.schedule.process.no_tasks'));
|
||||
$this->line(__('commands.schedule.process.no_tasks'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -33,7 +36,7 @@ class ProcessRunnableCommand extends Command
|
||||
$bar = $this->output->createProgressBar(count($schedules));
|
||||
foreach ($schedules as $schedule) {
|
||||
$bar->clear();
|
||||
$this->processSchedule($processScheduleService, $schedule);
|
||||
$this->processSchedule($schedule);
|
||||
$bar->advance();
|
||||
$bar->display();
|
||||
}
|
||||
@@ -48,23 +51,23 @@ class ProcessRunnableCommand extends Command
|
||||
* never throw an exception out, otherwise you'll end up killing the entire run group causing
|
||||
* any other schedules to not process correctly.
|
||||
*/
|
||||
protected function processSchedule(ProcessScheduleService $processScheduleService, Schedule $schedule): void
|
||||
protected function processSchedule(Schedule $schedule)
|
||||
{
|
||||
if ($schedule->tasks->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$processScheduleService->handle($schedule);
|
||||
$this->getLaravel()->make(ProcessScheduleService::class)->handle($schedule);
|
||||
|
||||
$this->line(trans('command/messages.schedule.output_line', [
|
||||
'schedule' => $schedule->name,
|
||||
'id' => $schedule->id,
|
||||
]));
|
||||
} catch (Throwable $exception) {
|
||||
} catch (\Throwable|\Exception $exception) {
|
||||
logger()->error($exception, ['schedule_id' => $schedule->id]);
|
||||
|
||||
$this->error(trans('commands.schedule.process.error_message', ['schedules' => " #$schedule->id: " . $exception->getMessage()]));
|
||||
$this->error(__('commands.schedule.process.no_tasks') . " #$schedule->id: " . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
namespace App\Console\Commands\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Validation\Factory as ValidatorFactory;
|
||||
use App\Repositories\Daemon\DaemonPowerRepository;
|
||||
use App\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
|
||||
class BulkPowerActionCommand extends Command
|
||||
{
|
||||
@@ -19,13 +19,26 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
|
||||
|
||||
public function handle(DaemonServerRepository $serverRepository, ValidatorFactory $validator): void
|
||||
/**
|
||||
* BulkPowerActionCommand constructor.
|
||||
*/
|
||||
public function __construct(private DaemonPowerRepository $powerRepository, private ValidatorFactory $validator)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the bulk power request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$action = $this->argument('action');
|
||||
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
|
||||
$servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers'));
|
||||
|
||||
$validator = $validator->make([
|
||||
$validator = $this->validator->make([
|
||||
'action' => $action,
|
||||
'nodes' => $nodes,
|
||||
'servers' => $servers,
|
||||
@@ -51,17 +64,13 @@ class BulkPowerActionCommand extends Command
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar($count);
|
||||
|
||||
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $serverRepository, &$bar): mixed {
|
||||
$powerRepository = $this->powerRepository;
|
||||
$this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) {
|
||||
$bar->clear();
|
||||
|
||||
if (!$server instanceof Server) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$serverRepository->setServer($server)->power($action);
|
||||
} catch (Exception $exception) {
|
||||
$powerRepository->setServer($server)->send($action);
|
||||
} catch (DaemonConnectionException $exception) {
|
||||
$this->output->error(trans('command/messages.server.power.action_failed', [
|
||||
'name' => $server->name,
|
||||
'id' => $server->id,
|
||||
@@ -72,8 +81,6 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
$bar->advance();
|
||||
$bar->display();
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
$this->line('');
|
||||
@@ -81,9 +88,6 @@ class BulkPowerActionCommand extends Command
|
||||
|
||||
/**
|
||||
* Returns the query builder instance that will return the servers that should be affected.
|
||||
*
|
||||
* @param string[]|int[] $servers
|
||||
* @param string[]|int[] $nodes
|
||||
*/
|
||||
protected function getQueryBuilder(array $servers, array $nodes): Builder
|
||||
{
|
||||
|
||||
197
app/Console/Commands/UpgradeCommand.php
Normal file
197
app/Console/Commands/UpgradeCommand.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Console\Kernel;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
||||
class UpgradeCommand extends Command
|
||||
{
|
||||
protected const DEFAULT_URL = 'https://github.com/pelican-dev/panel/releases/%s/panel.tar.gz';
|
||||
|
||||
protected $signature = 'p:upgrade
|
||||
{--user= : The user that PHP runs under. All files will be owned by this user.}
|
||||
{--group= : The group that PHP runs under. All files will be owned by this group.}
|
||||
{--url= : The specific archive to download.}
|
||||
{--release= : A specific version to download from GitHub. Leave blank to use latest.}
|
||||
{--skip-download : If set no archive will be downloaded.}';
|
||||
|
||||
protected $description = 'Downloads a new archive from GitHub and then executes the normal upgrade commands.';
|
||||
|
||||
/**
|
||||
* Executes an upgrade command which will run through all of our standard
|
||||
* Panel commands and enable users to basically just download
|
||||
* the archive and execute this and be done.
|
||||
*
|
||||
* This places the application in maintenance mode as well while the commands
|
||||
* are being executed.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$skipDownload = $this->option('skip-download');
|
||||
if (!$skipDownload) {
|
||||
$this->output->warning(__('commands.upgrade.integrity'));
|
||||
$this->output->comment(__('commands.upgrade.source_url'));
|
||||
$this->line($this->getUrl());
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.4.0') < 0) {
|
||||
$this->error(__('commands.upgrade.php_version') . ' [' . PHP_VERSION . '].');
|
||||
}
|
||||
|
||||
$user = 'www-data';
|
||||
$group = 'www-data';
|
||||
if ($this->input->isInteractive()) {
|
||||
if (!$skipDownload) {
|
||||
$skipDownload = !$this->confirm(__('commands.upgrade.skipDownload'), true);
|
||||
}
|
||||
|
||||
if (is_null($this->option('user'))) {
|
||||
$userDetails = function_exists('posix_getpwuid') ? posix_getpwuid(fileowner('public')) : [];
|
||||
$user = $userDetails['name'] ?? 'www-data';
|
||||
|
||||
$message = __('commands.upgrade.webserver_user', ['user' => $user]);
|
||||
if (!$this->confirm($message, true)) {
|
||||
$user = $this->anticipate(
|
||||
__('commands.upgrade.name_webserver'),
|
||||
[
|
||||
'www-data',
|
||||
'nginx',
|
||||
'apache',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($this->option('group'))) {
|
||||
$groupDetails = function_exists('posix_getgrgid') ? posix_getgrgid(filegroup('public')) : [];
|
||||
$group = $groupDetails['name'] ?? 'www-data';
|
||||
|
||||
$message = __('commands.upgrade.group_webserver', ['group' => $user]);
|
||||
if (!$this->confirm($message, true)) {
|
||||
$group = $this->anticipate(
|
||||
__('commands.upgrade.group_webserver_question'),
|
||||
[
|
||||
'www-data',
|
||||
'nginx',
|
||||
'apache',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->confirm(__('commands.upgrade.are_your_sure'))) {
|
||||
$this->warn(__('commands.upgrade.terminated'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ini_set('output_buffering', '0');
|
||||
$bar = $this->output->createProgressBar($skipDownload ? 9 : 10);
|
||||
$bar->start();
|
||||
|
||||
if (!$skipDownload) {
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line("\$upgrader> curl -L \"{$this->getUrl()}\" | tar -xzv");
|
||||
$process = Process::fromShellCommandline("curl -L \"{$this->getUrl()}\" | tar -xzv");
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan down');
|
||||
$this->call('down');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> chmod -R 755 storage bootstrap/cache');
|
||||
$process = new Process(['chmod', '-R', '755', 'storage', 'bootstrap/cache']);
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
||||
});
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$command = ['composer', 'install', '--no-ansi'];
|
||||
if (config('app.env') === 'production' && !config('app.debug')) {
|
||||
$command[] = '--optimize-autoloader';
|
||||
$command[] = '--no-dev';
|
||||
}
|
||||
|
||||
$this->line('$upgrader> ' . implode(' ', $command));
|
||||
$process = new Process($command);
|
||||
$process->setTimeout(10 * 60);
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->line($buffer);
|
||||
});
|
||||
});
|
||||
|
||||
/** @var \Illuminate\Foundation\Application $app */
|
||||
$app = require __DIR__ . '/../../../bootstrap/app.php';
|
||||
/** @var \App\Console\Kernel $kernel */
|
||||
$kernel = $app->make(Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
$this->setLaravel($app);
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan view:clear');
|
||||
$this->call('view:clear');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan config:clear');
|
||||
$this->call('config:clear');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan migrate --force --seed');
|
||||
$this->call('migrate', ['--force' => true, '--seed' => true]);
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () use ($user, $group) {
|
||||
$this->line("\$upgrader> chown -R {$user}:{$group} *");
|
||||
$process = Process::fromShellCommandline("chown -R {$user}:{$group} *", $this->getLaravel()->basePath());
|
||||
$process->setTimeout(10 * 60);
|
||||
$process->run(function ($type, $buffer) {
|
||||
$this->{$type === Process::ERR ? 'error' : 'line'}($buffer);
|
||||
});
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan queue:restart');
|
||||
$this->call('queue:restart');
|
||||
});
|
||||
|
||||
$this->withProgress($bar, function () {
|
||||
$this->line('$upgrader> php artisan up');
|
||||
$this->call('up');
|
||||
});
|
||||
|
||||
$this->newLine(2);
|
||||
$this->info(__('commands.upgrade.success'));
|
||||
}
|
||||
|
||||
protected function withProgress(ProgressBar $bar, \Closure $callback)
|
||||
{
|
||||
$bar->clear();
|
||||
$callback();
|
||||
$bar->advance();
|
||||
$bar->display();
|
||||
}
|
||||
|
||||
protected function getUrl(): string
|
||||
{
|
||||
if ($this->option('url')) {
|
||||
return $this->option('url');
|
||||
}
|
||||
|
||||
return sprintf(self::DEFAULT_URL, $this->option('release') ? 'download/v' . $this->option('release') : 'latest/download');
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace App\Console\Commands\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Webmozart\Assert\Assert;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeleteUserCommand extends Command
|
||||
{
|
||||
@@ -35,7 +35,7 @@ class DeleteUserCommand extends Command
|
||||
if ($this->input->isInteractive()) {
|
||||
$tableValues = [];
|
||||
foreach ($results as $user) {
|
||||
$tableValues[] = [$user->id, $user->email, $user->username];
|
||||
$tableValues[] = [$user->id, $user->email, $user->name];
|
||||
}
|
||||
|
||||
$this->table(['User ID', 'Email', 'Name'], $tableValues);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Console\Commands\User;
|
||||
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
@@ -15,7 +14,7 @@ class DisableTwoFactorCommand extends Command
|
||||
/**
|
||||
* Handle command execution process.
|
||||
*
|
||||
* @throws DataValidationException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
@@ -25,12 +24,10 @@ class DisableTwoFactorCommand extends Command
|
||||
|
||||
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
|
||||
|
||||
$user = User::where('email', $email)->firstOrFail();
|
||||
$user->update([
|
||||
'mfa_app_secret' => null,
|
||||
'mfa_app_recovery_codes' => null,
|
||||
'mfa_email_enabled' => false,
|
||||
]);
|
||||
$user = User::query()->where('email', $email)->firstOrFail();
|
||||
$user->use_totp = false;
|
||||
$user->totp_secret = null;
|
||||
$user->save();
|
||||
|
||||
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
namespace App\Console\Commands\User;
|
||||
|
||||
use App\Exceptions\Model\DataValidationException;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MakeUserCommand extends Command
|
||||
@@ -26,7 +25,7 @@ class MakeUserCommand extends Command
|
||||
* Handle command request to create a new user.
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws DataValidationException
|
||||
* @throws \App\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
@@ -53,7 +52,7 @@ class MakeUserCommand extends Command
|
||||
['UUID', $user->uuid],
|
||||
['Email', $user->email],
|
||||
['Username', $user->username],
|
||||
['Admin', $user->isRootAdmin() ? 'Yes' : 'No'],
|
||||
['Admin', $user->root_admin ? 'Yes' : 'No'],
|
||||
]);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -2,19 +2,15 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\Egg\CheckEggUpdatesCommand;
|
||||
use App\Console\Commands\Egg\UpdateEggIndexCommand;
|
||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||
use App\Jobs\NodeStatistics;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Database\Console\PruneCommand;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use Spatie\Health\Commands\RunHealthChecksCommand;
|
||||
use Spatie\Health\Commands\ScheduleCheckHeartbeatCommand;
|
||||
use App\Console\Commands\Schedule\ProcessRunnableCommand;
|
||||
use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
|
||||
use App\Console\Commands\Maintenance\CleanServiceBackupFilesCommand;
|
||||
use App\Console\Commands\Maintenance\PruneImagesCommand;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
@@ -31,11 +27,8 @@ class Kernel extends ConsoleKernel
|
||||
*/
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
if (config('cache.default') === 'redis') {
|
||||
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags
|
||||
// This only needs to run when using redis. anything else throws an error.
|
||||
$schedule->command('cache:prune-stale-tags')->hourly();
|
||||
}
|
||||
// https://laravel.com/docs/10.x/upgrade#redis-cache-tags
|
||||
$schedule->command('cache:prune-stale-tags')->hourly();
|
||||
|
||||
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
||||
$schedule->command(ProcessRunnableCommand::class)->everyMinute()->withoutOverlapping();
|
||||
@@ -43,8 +36,7 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->command(CleanServiceBackupFilesCommand::class)->daily();
|
||||
$schedule->command(PruneImagesCommand::class)->daily();
|
||||
|
||||
$schedule->command(CheckEggUpdatesCommand::class)->daily();
|
||||
$schedule->command(UpdateEggIndexCommand::class)->daily();
|
||||
$schedule->job(new NodeStatistics())->everyFiveSeconds()->withoutOverlapping();
|
||||
|
||||
if (config('backups.prune_age')) {
|
||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be deleted.
|
||||
@@ -54,12 +46,5 @@ class Kernel extends ConsoleKernel
|
||||
if (config('activity.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily();
|
||||
}
|
||||
|
||||
if (config('panel.webhook.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [Webhook::class]])->daily();
|
||||
}
|
||||
|
||||
$schedule->command(ScheduleCheckHeartbeatCommand::class)->everyMinute();
|
||||
$schedule->command(RunHealthChecksCommand::class)->everyFiveMinutes();
|
||||
}
|
||||
}
|
||||
|
||||
55
app/Console/RequiresDatabaseMigrations.php
Normal file
55
app/Console/RequiresDatabaseMigrations.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
/**
|
||||
* @mixin \Illuminate\Console\Command
|
||||
*/
|
||||
trait RequiresDatabaseMigrations
|
||||
{
|
||||
/**
|
||||
* Checks if the migrations have finished running by comparing the last migration file.
|
||||
*/
|
||||
protected function hasCompletedMigrations(): bool
|
||||
{
|
||||
/** @var \Illuminate\Database\Migrations\Migrator $migrator */
|
||||
$migrator = $this->getLaravel()->make('migrator');
|
||||
|
||||
$files = $migrator->getMigrationFiles(database_path('migrations'));
|
||||
|
||||
if (!$migrator->repositoryExists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array_diff(array_keys($files), $migrator->getRepository()->getRan())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a massive error into the console to hopefully catch the users attention and get
|
||||
* them to properly run the migrations rather than ignoring all of the other previous
|
||||
* errors...
|
||||
*/
|
||||
protected function showMigrationWarning(): void
|
||||
{
|
||||
$this->getOutput()->writeln('<options=bold>
|
||||
| @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
|
||||
| |
|
||||
| Your database has not been properly migrated! |
|
||||
| |
|
||||
| @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |</>
|
||||
|
||||
You must run the following command to finish migrating your database:
|
||||
|
||||
<fg=green;options=bold>php artisan migrate --step --force</>
|
||||
|
||||
You will not be able to use the Panel as expected without fixing your
|
||||
database state by running the command above.
|
||||
');
|
||||
|
||||
$this->getOutput()->error('You must correct the error above before continuing.');
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace App\Contracts\Http;
|
||||
|
||||
use App\Enums\SubuserPermission;
|
||||
|
||||
interface ClientPermissionsRequest
|
||||
{
|
||||
/**
|
||||
* Returns the permission used to validate that the authenticated user may perform
|
||||
* this action against the given resource (server).
|
||||
* Returns the permissions string indicating which permission should be used to
|
||||
* validate that the authenticated user has permission to perform this action against
|
||||
* the given resource (server).
|
||||
*/
|
||||
public function permission(): SubuserPermission|string;
|
||||
public function permission(): string;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts\Plugins;
|
||||
|
||||
use Filament\Schemas\Components\Component;
|
||||
|
||||
interface HasPluginSettings
|
||||
{
|
||||
/**
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getSettingsForm(): array;
|
||||
|
||||
/**
|
||||
* @param array<mixed, mixed> $data
|
||||
*/
|
||||
public function saveSettings(array $data): void;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
interface Validatable
|
||||
{
|
||||
public function getValidator(): Validator;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function getRules(): array;
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public static function getRulesForField(string $field): array;
|
||||
|
||||
public function validate(): void;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Eloquent;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @extends Builder<TModel>
|
||||
*/
|
||||
class BackupQueryBuilder extends Builder
|
||||
{
|
||||
public function nonFailed(): self
|
||||
{
|
||||
$this->where(function (Builder $query) {
|
||||
$query
|
||||
->whereNull('completed_at')
|
||||
->orWhere('is_successful', true);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum BackupStatus: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case InProgress = 'in_progress';
|
||||
case Successful = 'successful';
|
||||
case Failed = 'failed';
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::InProgress => TablerIcon::CircleDashed,
|
||||
self::Successful => TablerIcon::CircleCheck,
|
||||
self::Failed => TablerIcon::CircleX,
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::InProgress => 'primary',
|
||||
self::Successful => 'success',
|
||||
self::Failed => 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('server/backup.backup_status.' . $this->value);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ConsoleWidgetPosition: string
|
||||
{
|
||||
case Top = 'top';
|
||||
case AboveConsole = 'above_console';
|
||||
case BelowConsole = 'below_console';
|
||||
case Bottom = 'bottom';
|
||||
}
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
enum ContainerStatus: string
|
||||
{
|
||||
// Docker Based
|
||||
case Created = 'created';
|
||||
@@ -24,24 +19,24 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
// HTTP Based
|
||||
case Missing = 'missing';
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
public function icon(): string
|
||||
{
|
||||
return match ($this) {
|
||||
|
||||
self::Created => TablerIcon::HeartPlus,
|
||||
self::Starting => TablerIcon::HeartUp,
|
||||
self::Running => TablerIcon::Heartbeat,
|
||||
self::Restarting => TablerIcon::HeartBolt,
|
||||
self::Exited => TablerIcon::HeartExclamation,
|
||||
self::Paused => TablerIcon::HeartPause,
|
||||
self::Dead, self::Offline => TablerIcon::HeartX,
|
||||
self::Removing => TablerIcon::HeartDown,
|
||||
self::Missing => TablerIcon::HeartSearch,
|
||||
self::Stopping => TablerIcon::HeartMinus,
|
||||
self::Created => 'tabler-heart-plus',
|
||||
self::Starting => 'tabler-heart-up',
|
||||
self::Running => 'tabler-heartbeat',
|
||||
self::Restarting => 'tabler-heart-bolt',
|
||||
self::Exited => 'tabler-heart-exclamation',
|
||||
self::Paused => 'tabler-heart-pause',
|
||||
self::Dead, self::Offline => 'tabler-heart-x',
|
||||
self::Removing => 'tabler-heart-down',
|
||||
self::Missing => 'tabler-heart-search',
|
||||
self::Stopping => 'tabler-heart-minus',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string
|
||||
public function color(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Created => 'primary',
|
||||
@@ -54,51 +49,7 @@ enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
|
||||
self::Removing => 'warning',
|
||||
self::Missing => 'danger',
|
||||
self::Stopping => 'warning',
|
||||
self::Offline => 'danger',
|
||||
self::Offline => 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('server/console.status.' . $this->value);
|
||||
}
|
||||
|
||||
public function isOffline(): bool
|
||||
{
|
||||
return in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isStartingOrRunning(): bool
|
||||
{
|
||||
return in_array($this, [ContainerStatus::Starting, ContainerStatus::Running]);
|
||||
}
|
||||
|
||||
public function isStartingOrStopping(): bool
|
||||
{
|
||||
return in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]);
|
||||
}
|
||||
|
||||
public function isStartable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isRestartable(): bool
|
||||
{
|
||||
if ($this->isStartable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isStoppable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline, ContainerStatus::Missing]);
|
||||
}
|
||||
|
||||
public function isKillable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited, ContainerStatus::Missing]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum CustomRenderHooks: string
|
||||
{
|
||||
case FooterStart = 'pelican::footer.start';
|
||||
case FooterEnd = 'pelican::footer.end';
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum CustomizationKey: string
|
||||
{
|
||||
case ConsoleRows = 'console_rows';
|
||||
case ConsoleFont = 'console_font';
|
||||
case ConsoleFontSize = 'console_font_size';
|
||||
case ConsoleGraphPeriod = 'console_graph_period';
|
||||
case TopNavigation = 'top_navigation';
|
||||
case DashboardLayout = 'dashboard_layout';
|
||||
|
||||
case ButtonStyle = 'button_style';
|
||||
case RedirectToAdmin = 'redirect_to_admin';
|
||||
|
||||
public function getDefaultValue(): string|int|bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::ConsoleRows => 30,
|
||||
self::ConsoleFont => 'monospace',
|
||||
self::ConsoleFontSize => 14,
|
||||
self::ConsoleGraphPeriod => 30,
|
||||
self::TopNavigation => config('panel.filament.default-navigation', 'sidebar'),
|
||||
self::DashboardLayout => 'grid',
|
||||
self::ButtonStyle => true,
|
||||
self::RedirectToAdmin => false,
|
||||
};
|
||||
}
|
||||
|
||||
/** @return array<string, string|int|bool> */
|
||||
public static function getDefaultCustomization(): array
|
||||
{
|
||||
$default = [];
|
||||
|
||||
foreach (self::cases() as $key) {
|
||||
$default[$key->value] = $key->getDefaultValue();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum EditorLanguages: string implements HasLabel
|
||||
{
|
||||
case plaintext = 'plaintext';
|
||||
case abap = 'abap';
|
||||
case apex = 'apex';
|
||||
case azcali = 'azcali';
|
||||
case bat = 'bat';
|
||||
case bicep = 'bicep';
|
||||
case cameligo = 'cameligo';
|
||||
case clojure = 'clojure';
|
||||
case coffeescript = 'coffeescript';
|
||||
case c = 'c';
|
||||
case cpp = 'cpp';
|
||||
case csharp = 'csharp';
|
||||
case csp = 'csp';
|
||||
case css = 'css';
|
||||
case cypher = 'cypher';
|
||||
case dart = 'dart';
|
||||
case dockerfile = 'dockerfile';
|
||||
case ecl = 'ecl';
|
||||
case elixir = 'elixir';
|
||||
case flow9 = 'flow9';
|
||||
case fsharp = 'fsharp';
|
||||
case go = 'go';
|
||||
case graphql = 'graphql';
|
||||
case handlebars = 'handlebars';
|
||||
case hcl = 'hcl';
|
||||
case html = 'html';
|
||||
case ini = 'ini';
|
||||
case java = 'java';
|
||||
case javascript = 'javascript';
|
||||
case julia = 'julia';
|
||||
case json = 'json';
|
||||
case kotlin = 'kotlin';
|
||||
case less = 'less';
|
||||
case lexon = 'lexon';
|
||||
case lua = 'lua';
|
||||
case liquid = 'liquid';
|
||||
case m3 = 'm3';
|
||||
case markdown = 'markdown';
|
||||
case mdx = 'mdx';
|
||||
case mips = 'mips';
|
||||
case msdax = 'msdax';
|
||||
case mysql = 'mysql';
|
||||
case objectivec = 'objective-c';
|
||||
case pascal = 'pascal';
|
||||
case pascaligo = 'pascaligo';
|
||||
case perl = 'perl';
|
||||
case pgsql = 'pgsql';
|
||||
case php = 'php';
|
||||
case pla = 'pla';
|
||||
case postiats = 'postiats';
|
||||
case powerquery = 'powerquery';
|
||||
case powershell = 'powershell';
|
||||
case proto = 'proto';
|
||||
case pug = 'pug';
|
||||
case python = 'python';
|
||||
case qsharp = 'qsharp';
|
||||
case r = 'r';
|
||||
case razor = 'razor';
|
||||
case redis = 'redis';
|
||||
case redshift = 'redshift';
|
||||
case restructuredtext = 'restructuredtext';
|
||||
case ruby = 'ruby';
|
||||
case rust = 'rust';
|
||||
case sb = 'sb';
|
||||
case scala = 'scala';
|
||||
case scheme = 'scheme';
|
||||
case scss = 'scss';
|
||||
case shell = 'shell';
|
||||
case sol = 'sol';
|
||||
case aes = 'aes';
|
||||
case sparql = 'sparql';
|
||||
case sql = 'sql';
|
||||
case st = 'st';
|
||||
case swift = 'swift';
|
||||
case systemverilog = 'systemverilog';
|
||||
case verilog = 'verilog';
|
||||
case tcl = 'tcl';
|
||||
case twig = 'twig';
|
||||
case typescript = 'typescript';
|
||||
case typespec = 'typespec';
|
||||
case vb = 'vb';
|
||||
case wgsl = 'wgsl';
|
||||
case xml = 'xml';
|
||||
case yaml = 'yaml';
|
||||
|
||||
public static function fromWithAlias(string $match): self
|
||||
{
|
||||
return match ($match) {
|
||||
'h' => self::c,
|
||||
|
||||
'cc', 'hpp' => self::cpp,
|
||||
|
||||
'cs' => self::csharp,
|
||||
|
||||
'class' => self::java,
|
||||
|
||||
'htm' => self::html,
|
||||
|
||||
'js', 'mjs', 'cjs' => self::javascript,
|
||||
|
||||
'kt', 'kts' => self::kotlin,
|
||||
|
||||
'md' => self::markdown,
|
||||
|
||||
'm' => self::objectivec,
|
||||
|
||||
'pl', 'pm' => self::perl,
|
||||
|
||||
'php3', 'php4', 'php5', 'phtml' => self::php,
|
||||
|
||||
'py', 'pyc', 'pyo', 'pyi' => self::python,
|
||||
|
||||
'rdata', 'rds' => self::r,
|
||||
|
||||
'rb', 'erb' => self::ruby,
|
||||
|
||||
'sc' => self::scala,
|
||||
|
||||
'sh', 'zsh' => self::shell,
|
||||
|
||||
'ts', 'tsx' => self::typescript,
|
||||
|
||||
'yml' => self::yaml,
|
||||
|
||||
default => self::tryFrom($match) ?? self::plaintext,
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum EggFormat: string
|
||||
{
|
||||
case YAML = 'yaml';
|
||||
case JSON = 'json';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum HeaderActionPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum HeaderWidgetPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum NodeJwtScope: string
|
||||
{
|
||||
case BackupDownload = 'backup-download';
|
||||
case FileDownload = 'file-download';
|
||||
case FileUpload = 'file-upload';
|
||||
case ServerTransfer = 'transfer';
|
||||
case Websocket = 'websocket';
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum PluginCategory: string implements HasIcon, HasLabel
|
||||
{
|
||||
case Plugin = 'plugin';
|
||||
case Theme = 'theme';
|
||||
case Language = 'language';
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::Plugin => TablerIcon::Package,
|
||||
self::Theme => TablerIcon::Palette,
|
||||
self::Language => TablerIcon::Language,
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('admin/plugin.category_enum.' . $this->value);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum PluginStatus: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case NotInstalled = 'not_installed';
|
||||
case Disabled = 'disabled';
|
||||
case Enabled = 'enabled';
|
||||
case Errored = 'errored';
|
||||
case Incompatible = 'incompatible';
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
{
|
||||
return match ($this) {
|
||||
self::NotInstalled => TablerIcon::HeartOff,
|
||||
self::Disabled => TablerIcon::HeartX,
|
||||
self::Enabled => TablerIcon::HeartCheck,
|
||||
self::Errored => TablerIcon::HeartBroken,
|
||||
self::Incompatible => TablerIcon::HeartCancel,
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::NotInstalled => 'gray',
|
||||
self::Disabled => 'warning',
|
||||
self::Enabled => 'success',
|
||||
self::Errored => 'danger',
|
||||
self::Incompatible => 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('admin/plugin.status_enum.' . $this->value);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* A basic resource throttler for individual servers. This is applied in addition
|
||||
* to existing rate limits and allows the code to slow down speedy users that might
|
||||
* be creating resources a little too quickly for comfort. This throttle generally
|
||||
* only applies to creation flows, and not general view/edit/delete flows.
|
||||
*/
|
||||
enum ResourceLimit: string
|
||||
{
|
||||
case Websocket = 'websocket';
|
||||
case AllocationCreate = 'allocation-create';
|
||||
case BackupRestore = 'backup-restore';
|
||||
case DatabaseCreate = 'database-create';
|
||||
case ScheduleCreate = 'schedule-create';
|
||||
case SubuserCreate = 'subuser-create';
|
||||
case FilePull = 'file-pull';
|
||||
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return "api.client:server-resource:{$this->name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a middleware that will throttle the specific resource by server. This
|
||||
* throttle applies to any user making changes to that resource on the specific
|
||||
* server, it is NOT per-user.
|
||||
*/
|
||||
public function middleware(): string
|
||||
{
|
||||
return ThrottleRequests::using($this->throttleKey());
|
||||
}
|
||||
|
||||
public function limit(): Limit
|
||||
{
|
||||
return match ($this) {
|
||||
self::Websocket => Limit::perMinutes(config('http.rate_limit.websocket_period'), config('http.rate_limit.websocket')),
|
||||
self::BackupRestore => Limit::perMinutes(config('http.rate_limit.backup_restore_period'), config('http.rate_limit.backup_restore')),
|
||||
self::DatabaseCreate => Limit::perMinutes(config('http.rate_limit.database_create_period'), config('http.rate_limit.database_create')),
|
||||
self::SubuserCreate => Limit::perMinutes(config('http.rate_limit.subuser_create_period'), config('http.rate_limit.subuser_create')),
|
||||
self::FilePull => Limit::perMinutes(config('http.rate_limit.file_pull_period'), config('http.rate_limit.file_pull')),
|
||||
default => Limit::perMinutes(config('http.rate_limit.default_period'), config('http.rate_limit.default')),
|
||||
};
|
||||
}
|
||||
|
||||
public static function boot(): void
|
||||
{
|
||||
foreach (self::cases() as $case) {
|
||||
RateLimiter::for($case->throttleKey(), function (Request $request) use ($case) {
|
||||
Assert::isInstanceOf($server = $request->route()->parameter('server'), Server::class);
|
||||
|
||||
return $case->limit()->by($server->uuid);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum RolePermissionModels: string
|
||||
{
|
||||
case ApiKey = 'apiKey';
|
||||
case Allocation = 'allocation';
|
||||
case BackupHost = 'backupHost';
|
||||
case DatabaseHost = 'databaseHost';
|
||||
case Database = 'database';
|
||||
case Egg = 'egg';
|
||||
case Mount = 'mount';
|
||||
case Node = 'node';
|
||||
case Role = 'role';
|
||||
case Server = 'server';
|
||||
case User = 'user';
|
||||
case Webhook = 'webhook';
|
||||
|
||||
public function viewAny(): string
|
||||
{
|
||||
return RolePermissionPrefixes::ViewAny->value . ' ' . $this->value;
|
||||
}
|
||||
|
||||
public function view(): string
|
||||
{
|
||||
return RolePermissionPrefixes::View->value . ' ' . $this->value;
|
||||
}
|
||||
|
||||
public function create(): string
|
||||
{
|
||||
return RolePermissionPrefixes::Create->value . ' ' . $this->value;
|
||||
}
|
||||
|
||||
public function update(): string
|
||||
{
|
||||
return RolePermissionPrefixes::Update->value . ' ' . $this->value;
|
||||
}
|
||||
|
||||
public function delete(): string
|
||||
{
|
||||
return RolePermissionPrefixes::Delete->value . ' ' . $this->value;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum RolePermissionPrefixes: string
|
||||
{
|
||||
case ViewAny = 'viewList';
|
||||
case View = 'view';
|
||||
case Create = 'create';
|
||||
case Update = 'update';
|
||||
case Delete = 'delete';
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum ScheduleStatus: string implements HasColor, HasLabel
|
||||
{
|
||||
case Inactive = 'inactive';
|
||||
case Processing = 'processing';
|
||||
case Active = 'active';
|
||||
|
||||
public function getColor(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Inactive => 'danger',
|
||||
self::Processing => 'warning',
|
||||
self::Active => 'success',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return trans('server/schedule.schedule_status.' . $this->value);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Models\Server;
|
||||
|
||||
enum ServerResourceType: string
|
||||
{
|
||||
case Uptime = 'uptime';
|
||||
case CPU = 'cpu_absolute';
|
||||
case Memory = 'memory_bytes';
|
||||
case Disk = 'disk_bytes';
|
||||
|
||||
case CPULimit = 'cpu';
|
||||
case MemoryLimit = 'memory';
|
||||
case DiskLimit = 'disk';
|
||||
|
||||
/**
|
||||
* @return int resource amount in bytes
|
||||
*/
|
||||
public function getResourceAmount(Server $server): int
|
||||
{
|
||||
if ($this->isLimit()) {
|
||||
$resourceAmount = $server->{$this->value} ?? 0;
|
||||
|
||||
if (!$this->isPercentage()) {
|
||||
// Our limits are entered as MiB/ MB so we need to convert them to bytes
|
||||
$resourceAmount *= config('panel.use_binary_prefix') ? 1024 * 1024 : 1000 * 1000;
|
||||
}
|
||||
|
||||
return $resourceAmount;
|
||||
}
|
||||
|
||||
return $server->retrieveResources()[$this->value] ?? 0;
|
||||
}
|
||||
|
||||
public function isLimit(): bool
|
||||
{
|
||||
return $this === ServerResourceType::CPULimit || $this === ServerResourceType::MemoryLimit || $this === ServerResourceType::DiskLimit;
|
||||
}
|
||||
|
||||
public function isTime(): bool
|
||||
{
|
||||
return $this === ServerResourceType::Uptime;
|
||||
}
|
||||
|
||||
public function isPercentage(): bool
|
||||
{
|
||||
return $this === ServerResourceType::CPU || $this === ServerResourceType::CPULimit;
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,31 @@
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
enum ServerState: string
|
||||
{
|
||||
case Normal = 'normal';
|
||||
case Installing = 'installing';
|
||||
case InstallFailed = 'install_failed';
|
||||
case ReinstallFailed = 'reinstall_failed';
|
||||
case Suspended = 'suspended';
|
||||
case RestoringBackup = 'restoring_backup';
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
public function icon(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Installing => TablerIcon::HeartBolt,
|
||||
self::InstallFailed, self::ReinstallFailed => TablerIcon::HeartX,
|
||||
self::Suspended => TablerIcon::HeartCancel,
|
||||
self::RestoringBackup => TablerIcon::HeartUp,
|
||||
self::Normal => 'tabler-heart',
|
||||
self::Installing => 'tabler-heart-bolt',
|
||||
self::InstallFailed => 'tabler-heart-x',
|
||||
self::ReinstallFailed => 'tabler-heart-x',
|
||||
self::Suspended => 'tabler-heart-cancel',
|
||||
self::RestoringBackup => 'tabler-heart-up',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string
|
||||
public function color(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Normal => 'primary',
|
||||
self::Installing => 'primary',
|
||||
self::InstallFailed => 'danger',
|
||||
self::ReinstallFailed => 'danger',
|
||||
@@ -35,9 +34,4 @@ enum ServerState: string implements HasColor, HasIcon, HasLabel
|
||||
self::RestoringBackup => 'primary',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return str($this->value)->headline();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ServerUserSettingKey: string
|
||||
{
|
||||
case ManualBackupNotifications = 'manual_backup_notifications';
|
||||
case ScheduledBackupNotifications = 'scheduled_backup_notifications';
|
||||
|
||||
/**
|
||||
* The default value for users without an explicit setting.
|
||||
*/
|
||||
public function getDefaultValue(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::ManualBackupNotifications, self::ScheduledBackupNotifications => false,
|
||||
};
|
||||
}
|
||||
|
||||
/** @return array<string, bool> */
|
||||
public static function getDefaultSettings(): array
|
||||
{
|
||||
$default = [];
|
||||
|
||||
foreach (self::cases() as $key) {
|
||||
$default[$key->value] = $key->getDefaultValue();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum StartupVariableType: string
|
||||
{
|
||||
case Text = 'text';
|
||||
case Number = 'number';
|
||||
case Select = 'select';
|
||||
case Toggle = 'toggle';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum StepPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use BackedEnum;
|
||||
|
||||
enum SubuserPermission: string
|
||||
{
|
||||
case WebsocketConnect = 'websocket.connect';
|
||||
|
||||
case ControlConsole = 'control.console';
|
||||
case ControlStart = 'control.start';
|
||||
case ControlStop = 'control.stop';
|
||||
case ControlRestart = 'control.restart';
|
||||
|
||||
case FileRead = 'file.read';
|
||||
case FileReadContent = 'file.read-content';
|
||||
case FileCreate = 'file.create';
|
||||
case FileUpdate = 'file.update';
|
||||
case FileDelete = 'file.delete';
|
||||
case FileArchive = 'file.archive';
|
||||
case FileSftp = 'file.sftp';
|
||||
|
||||
case BackupRead = 'backup.read';
|
||||
case BackupCreate = 'backup.create';
|
||||
case BackupDelete = 'backup.delete';
|
||||
case BackupDownload = 'backup.download';
|
||||
case BackupRestore = 'backup.restore';
|
||||
|
||||
case ScheduleRead = 'schedule.read';
|
||||
case ScheduleCreate = 'schedule.create';
|
||||
case ScheduleUpdate = 'schedule.update';
|
||||
case ScheduleDelete = 'schedule.delete';
|
||||
|
||||
case UserRead = 'user.read';
|
||||
case UserCreate = 'user.create';
|
||||
case UserUpdate = 'user.update';
|
||||
case UserDelete = 'user.delete';
|
||||
|
||||
case DatabaseRead = 'database.read';
|
||||
case DatabaseCreate = 'database.create';
|
||||
case DatabaseUpdate = 'database.update';
|
||||
case DatabaseDelete = 'database.delete';
|
||||
case DatabaseViewPassword = 'database.view-password';
|
||||
|
||||
case AllocationRead = 'allocation.read';
|
||||
case AllocationCreate = 'allocation.create';
|
||||
case AllocationUpdate = 'allocation.update';
|
||||
case AllocationDelete = 'allocation.delete';
|
||||
|
||||
case ActivityRead = 'activity.read';
|
||||
|
||||
case MountRead = 'mount.read';
|
||||
case MountUpdate = 'mount.update';
|
||||
|
||||
case StartupRead = 'startup.read';
|
||||
case StartupUpdate = 'startup.update';
|
||||
case StartupDockerImage = 'startup.docker-image';
|
||||
|
||||
case SettingsRename = 'settings.rename';
|
||||
case SettingsDescription = 'settings.description';
|
||||
case SettingsReinstall = 'settings.reinstall';
|
||||
case SettingsChangeIcon = 'settings.change-icon';
|
||||
|
||||
/** @return string[] */
|
||||
public function split(): array
|
||||
{
|
||||
return explode('.', $this->value, 2);
|
||||
}
|
||||
|
||||
public function isHidden(): bool
|
||||
{
|
||||
return $this === self::WebsocketConnect;
|
||||
}
|
||||
|
||||
public function getIcon(): ?BackedEnum
|
||||
{
|
||||
[$group, $permission] = $this->split();
|
||||
|
||||
return match ($group) {
|
||||
'control' => TablerIcon::Terminal2,
|
||||
'user' => TablerIcon::Users,
|
||||
'file' => TablerIcon::Files,
|
||||
'backup' => TablerIcon::FileZip,
|
||||
'allocation' => TablerIcon::Network,
|
||||
'startup' => TablerIcon::PlayerPlay,
|
||||
'database' => TablerIcon::Database,
|
||||
'schedule' => TablerIcon::Clock,
|
||||
'settings' => TablerIcon::Settings,
|
||||
'activity' => TablerIcon::Stack,
|
||||
'mount' => TablerIcon::LayersLinked,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum SuspendAction: string
|
||||
{
|
||||
case Suspend = 'suspend';
|
||||
case Unsuspend = 'unsuspend';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum TabPosition: string
|
||||
{
|
||||
case Before = 'before';
|
||||
case After = 'after';
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user