diff --git a/.github/docker/Caddyfile b/.github/docker/Caddyfile new file mode 100644 index 00000000000..3ac87b587cd --- /dev/null +++ b/.github/docker/Caddyfile @@ -0,0 +1,30 @@ +:8080 { + root * /var/www/pterodactyl/public/ + file_server + + header { + -Server + -X-Powered-By + Referrer-Policy "same-origin" + X-Frame-Options "deny" + X-XSS-Protection "1; mode=block" + X-Content-Type-Options "nosniff" + } + + encode gzip zstd + + php_fastcgi 127.0.0.1:9000 { + trusted_proxies 172.20.0.0/16 + } + + @startsWithDot { + path \/\. + not path .well-known + } + rewrite @startsWithDot /index.php{uri} + + @phpRewrite { + not file favicon.ico + } + try_files @phpRewrite {path} {path}/ /index.php?{query} +} diff --git a/.github/docker/README.md b/.github/docker/README.md deleted file mode 100644 index 5cf97de4429..00000000000 --- a/.github/docker/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Pterodactyl 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/pterodactyl/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/pterodactyl/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 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_DRIVER` | 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` | diff --git a/.github/docker/default.conf b/.github/docker/default.conf deleted file mode 100644 index b6105e5fa7b..00000000000 --- a/.github/docker/default.conf +++ /dev/null @@ -1,51 +0,0 @@ -# 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/ -# -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/pterodactyl.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; - } -} diff --git a/.github/docker/default_ssl.conf b/.github/docker/default_ssl.conf deleted file mode 100644 index 9ec5c10db19..00000000000 --- a/.github/docker/default_ssl.conf +++ /dev/null @@ -1,70 +0,0 @@ -# If using Ubuntu this file should be placed in: -# /etc/nginx/sites-available/ -# -server { - listen 80; - server_name ; - return 301 https://$server_name$request_uri; -} - -server { - listen 443 ssl http2; - server_name ; - - root /app/public; - index index.php; - - access_log /var/log/nginx/pterodactyl.app-access.log; - error_log /var/log/nginx/pterodactyl.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//fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live//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; - } -} \ No newline at end of file diff --git a/.github/docker/entrypoint.sh b/.github/docker/entrypoint.sh deleted file mode 100644 index d3df9c15052..00000000000 --- a/.github/docker/entrypoint.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/ash -e -cd /app - -mkdir -p /var/log/panel/logs/ /var/log/supervisord/ /var/log/nginx/ /var/log/php7/ \ - && chmod 777 /var/log/panel/logs/ \ - && ln -s /var/log/panel/logs/ /app/storage/logs/ - -## 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||$(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 "$@" diff --git a/.github/docker/php-fpm.conf b/.github/docker/php-fpm.conf new file mode 100644 index 00000000000..9812e6610b7 --- /dev/null +++ b/.github/docker/php-fpm.conf @@ -0,0 +1,21 @@ +[global] +error_log = /dev/stderr +daemonize = no + +[www] +user = nobody +group = nobody + +listen = 127.0.0.1:9000 + +pm = dynamic +pm.start_servers = 4 +pm.min_spare_servers = 4 +pm.max_spare_servers = 16 +pm.max_children = 64 +pm.max_requests = 256 + +clear_env = no +catch_workers_output = yes + +decorate_workers_output = no diff --git a/.github/docker/supervisord.conf b/.github/docker/supervisord.conf index da6823aeb74..be659165be5 100644 --- a/.github/docker/supervisord.conf +++ b/.github/docker/supervisord.conf @@ -1,39 +1,57 @@ +[supervisord] +logfile=/dev/stdout +logfile_maxbytes=0 +loglevel=info +minfds=1024 +minprocs=200 +nodaemon=true +pidfile=/dev/null + [unix_http_server] -file=/tmp/supervisor.sock ; path to your socket file +file=/tmp/supervisor.sock -[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 +[supervisorctl] +serverurl=unix:///tmp/supervisor.sock [rpcinterface:supervisor] -supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface +supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface -[supervisorctl] -serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket +[program:caddy] +command=/usr/local/bin/caddy run --config /etc/caddy/Caddyfile +autostart=true +autorestart=true +priority=10 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 [program:php-fpm] -command=/usr/local/sbin/php-fpm -F +command=/usr/sbin/php-fpm --nodaemonize autostart=true autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=LOG_CHANNEL="stderr" [program:queue-worker] -command=/usr/local/bin/php /app/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3 -user=nginx +command=/usr/bin/php /var/www/pterodactyl/artisan queue:work --queue=standard --sleep=3 --tries=3 autostart=true autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=LOG_CHANNEL="stderr" -[program:nginx] -command=/usr/sbin/nginx -g 'daemon off;' +[program:yacron] +command=/usr/local/bin/yacron -c /etc/yacron.yaml autostart=true autorestart=true -priority=10 -stdout_events_enabled=true -stderr_events_enabled=true \ No newline at end of file +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=LOG_CHANNEL="stderr" diff --git a/.github/docker/www.conf b/.github/docker/www.conf deleted file mode 100644 index c0c17903f3c..00000000000 --- a/.github/docker/www.conf +++ /dev/null @@ -1,16 +0,0 @@ -[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 \ No newline at end of file diff --git a/.github/docker/yacron.yaml b/.github/docker/yacron.yaml new file mode 100644 index 00000000000..c0dc75ecd9a --- /dev/null +++ b/.github/docker/yacron.yaml @@ -0,0 +1,8 @@ +jobs: + - name: scheduler + command: + - /usr/bin/php + - /var/www/pterodactyl/artisan + - schedule:run + schedule: "* * * * *" + utc: true diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 0e1a2b77e8a..65cef226d3f 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -1,4 +1,4 @@ -name: Publish Docker Image +name: Docker on: push: @@ -8,16 +8,16 @@ on: jobs: push: - name: Push Image to GitHub Packages + name: Push runs-on: ubuntu-20.04 # Always run against a tag, even if the commit into the tag has [docker skip] # within the commit message. if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))" steps: - - name: Code Checkout + - name: Code checkout uses: actions/checkout@v3 - - name: Docker Metadata + - name: Docker metadata uses: docker/metadata-action@v4 id: docker_meta with: @@ -26,10 +26,10 @@ jobs: - name: Setup QEMU uses: docker/setup-qemu-action@v2 - - name: Setup Docker Buildx + - name: Setup Docker buildx uses: docker/setup-buildx-action@v2 - - name: Docker Login + - name: Docker login uses: docker/login-action@v2 with: registry: ghcr.io @@ -37,24 +37,24 @@ jobs: password: ${{ secrets.REGISTRY_TOKEN }} - name: Release production build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 if: "contains(github.ref, 'release/v')" with: context: . - file: ./Dockerfile + file: ./Containerfile push: true platforms: linux/amd64,linux/arm64 tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} - name: Release development build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 if: "contains(github.ref, 'develop')" with: context: . - file: ./Dockerfile + file: ./Containerfile push: ${{ github.event_name != 'pull_request' }} - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 #,linux/arm64 tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/ci.yaml b/.github/workflows/laravel.yaml similarity index 70% rename from .github/workflows/ci.yaml rename to .github/workflows/laravel.yaml index 28ce63e4a79..56a597b4801 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/laravel.yaml @@ -1,4 +1,4 @@ -name: Tests +name: Laravel on: push: @@ -11,8 +11,58 @@ on: - "1.0-develop" jobs: + analysis: + name: Static Analysis + runs-on: ubuntu-20.04 + env: + APP_ENV: testing + APP_DEBUG: "true" + APP_KEY: SomeRandomString3232RandomString + CACHE_DRIVER: array + MAIL_MAILER: array + SESSION_DRIVER: array + QUEUE_CONNECTION: sync + steps: + - name: Code checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: bcmath, cli, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: composer install --no-interaction --no-progress --no-suggest --prefer-dist + + - name: Analyze + run: vendor/bin/phpstan analyse + + lint: + name: Lint + runs-on: ubuntu-20.04 + steps: + - name: Code checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + 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-progress --no-suggest --prefer-dist + + - name: PHP CS Fixer + run: vendor/bin/php-cs-fixer fix --dry-run --diff + mysql: - name: MySQL + name: Tests (MySQL) runs-on: ubuntu-20.04 strategy: fail-fast: false @@ -36,20 +86,17 @@ jobs: APP_TIMEZONE: UTC APP_URL: http://localhost/ APP_ENVIRONMENT_ONLY: "true" - - DB_CONNECTION: mysql - DB_HOST: 127.0.0.1 - DB_DATABASE: testing - DB_USERNAME: root - CACHE_DRIVER: array MAIL_MAILER: array SESSION_DRIVER: array QUEUE_CONNECTION: sync - HASHIDS_SALT: test123 + DB_CONNECTION: mysql + DB_HOST: 127.0.0.1 + DB_DATABASE: testing + DB_USERNAME: root steps: - - name: Code Checkout + - name: Code checkout uses: actions/checkout@v3 - name: Get cache directory @@ -78,7 +125,6 @@ jobs: - name: Unit tests run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit - if: ${{ always() }} env: DB_HOST: UNIT_NO_DB @@ -88,7 +134,7 @@ jobs: DB_PORT: ${{ job.services.database.ports[3306] }} postgres: - name: PostgreSQL + name: Tests (PostgreSQL) runs-on: ubuntu-20.04 if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'ci skip')" strategy: @@ -114,35 +160,32 @@ jobs: APP_TIMEZONE: UTC APP_URL: http://localhost/ APP_ENVIRONMENT_ONLY: "true" - - DB_CONNECTION: pgsql - DB_HOST: 127.0.0.1 - DB_DATABASE: testing - DB_USERNAME: postgres - DB_PASSWORD: postgres - CACHE_DRIVER: array MAIL_MAILER: array SESSION_DRIVER: array QUEUE_CONNECTION: sync - HASHIDS_SALT: test123 + DB_CONNECTION: pgsql + DB_HOST: 127.0.0.1 + DB_DATABASE: testing + DB_USERNAME: postgres + DB_PASSWORD: postgres steps: - - name: Code Checkout + - name: Code checkout uses: actions/checkout@v3 - name: Get cache directory id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache uses: actions/cache@v3 with: - path: | - ~/.php_cs.cache - ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} + 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 @@ -157,7 +200,6 @@ jobs: - name: Unit tests run: vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit - if: ${{ always() }} env: DB_HOST: UNIT_NO_DB diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index a48c6b14ed7..00000000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Lint - -on: - push: - branches: - - "develop" - - "1.0-develop" - pull_request: - branches: - - "develop" - - "1.0-develop" - -jobs: - lint: - name: Lint - runs-on: ubuntu-20.04 - steps: - - name: Code Checkout - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.1" - 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-progress --no-suggest --prefer-dist - - - name: PHP CS Fixer - run: vendor/bin/php-cs-fixer fix --dry-run --diff diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 09ab86410a2..7c4a72bffb1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,20 +10,20 @@ jobs: name: Release runs-on: ubuntu-20.04 steps: - - name: Code Checkout + - name: Code checkout uses: actions/checkout@v3 - name: Setup Node uses: actions/setup-node@v3 with: - node-version: 16 - cache: "yarn" + node-version: 18 + cache: yarn - name: Install dependencies run: yarn install --frozen-lockfile - name: Build - run: yarn build:production + run: yarn build - name: Create release branch and bump version env: @@ -41,7 +41,7 @@ jobs: - name: Create release archive run: | - rm -rf node_modules/ test/ codecov.yml CODE_OF_CONDUCT.md CONTRIBUTING.md phpunit.xml Vagrantfile + rm -rf node_modules/ tests/ CODE_OF_CONDUCT.md CONTRIBUTING.md phpstan.neon phpunit.xml tar -czf panel.tar.gz * .env.example .eslintignore .eslintrc.js - name: Extract changelog @@ -58,7 +58,7 @@ jobs: echo -e "\n#### SHA256 Checksum\n\n\`\`\`\n$SUM\n\`\`\`\n" >> ./RELEASE_CHANGELOG echo $SUM > checksum.txt - - name: Create Release + - name: Create release id: create_release uses: actions/create-release@v1 env: diff --git a/.github/workflows/ui.yaml b/.github/workflows/ui.yaml index e743de46671..e61696c1ceb 100644 --- a/.github/workflows/ui.yaml +++ b/.github/workflows/ui.yaml @@ -11,28 +11,47 @@ on: - "1.0-develop" jobs: - build-and-test: - name: Build and Test + lint: + name: Lint + runs-on: ubuntu-20.04 + steps: + - name: Code checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: yarn + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Lint + run: yarn run lint + + tests: + name: Tests runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - node-version: [16, 18] + node: [16, 18] steps: - - name: Code Checkout + - name: Code checkout uses: actions/checkout@v3 - name: Setup Node uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} - cache: "yarn" + node-version: ${{ matrix.node }} + cache: yarn - name: Install dependencies run: yarn install --frozen-lockfile - name: Build - run: yarn build + run: yarn run build - name: Tests - run: yarn test + run: yarn run test diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000000..0054b0a1758 --- /dev/null +++ b/Containerfile @@ -0,0 +1,76 @@ +# Stage 0 - Caddy +FROM docker.io/library/caddy:latest AS caddy + +# Stage 1 - Builder +FROM registry.access.redhat.com/ubi9/nodejs-16-minimal AS builder + +RUN npm install -g yarn + +WORKDIR /var/www/pterodactyl + +COPY --chown=1001:0 public ./public +COPY --chown=1001:0 resources/scripts ./resources/scripts +COPY --chown=1001:0 .eslintignore .eslintrc.js .prettierrc.json package.json tailwind.config.js tsconfig.json vite.config.ts yarn.lock . + +RUN /opt/app-root/src/.npm-global/bin/yarn install --frozen-lockfile \ + && /opt/app-root/src/.npm-global/bin/yarn build \ + && rm -rf resources/scripts .eslintignore .eslintrc.yml .yarnrc.yml package.json tailwind.config.js tsconfig.json vite.config.ts yarn.lock node_modules + +COPY --chown=1001:0 app ./app +COPY --chown=1001:0 bootstrap ./bootstrap +COPY --chown=1001:0 config ./config +COPY --chown=1001:0 database ./database +COPY --chown=1001:0 resources/lang ./resources/lang +COPY --chown=1001:0 resources/views ./resources/views +COPY --chown=1001:0 routes ./routes +COPY --chown=1001:0 .env.example ./.env +COPY --chown=1001:0 artisan CHANGELOG.md composer.json composer.lock LICENSE.md README.md SECURITY.md . + +# Stage 2 - Final +FROM registry.access.redhat.com/ubi9/ubi-minimal + +RUN microdnf update -y \ + && rpm --install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm \ + && rpm --install https://rpms.remirepo.net/enterprise/remi-release-9.rpm \ + && microdnf update -y \ + && microdnf install -y ca-certificates shadow-utils tar tzdata unzip wget \ + && microdnf module -y reset php \ + && microdnf module -y enable php:remi-8.1 \ + && microdnf install -y cronie php-{bcmath,cli,common,fpm,gd,gmp,intl,json,mbstring,mysqlnd,opcache,pdo,pecl-redis5,pecl-zip,phpiredis,pgsql,process,sodium,xml,zstd} supervisor \ + && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ + && rm /etc/php-fpm.d/www.conf \ + && useradd --home-dir /var/lib/caddy --create-home caddy \ + && mkdir /etc/caddy \ + && wget -O /usr/local/bin/yacron https://github.com/gjcarneiro/yacron/releases/download/0.17.0/yacron-0.17.0-x86_64-unknown-linux-gnu \ + && chmod 755 /usr/local/bin/yacron \ + && microdnf remove -y tar wget \ + && microdnf clean all + +COPY --chown=caddy:caddy --from=builder /var/www/pterodactyl /var/www/pterodactyl + +WORKDIR /var/www/pterodactyl + +RUN mkdir -p /tmp/pterodactyl/cache /tmp/pterodactyl/framework/{cache,sessions,views} storage/framework \ + && rm -rf bootstrap/cache storage/framework/sessions storage/framework/views storage/framework/cache \ + && ln -s /tmp/pterodactyl/cache /var/www/pterodactyl/bootstrap/cache \ + && ln -s /tmp/pterodactyl/framework/cache /var/www/pterodactyl/storage/framework/cache \ + && ln -s /tmp/pterodactyl/framework/sessions /var/www/pterodactyl/storage/framework/sessions \ + && ln -s /tmp/pterodactyl/framework/views /var/www/pterodactyl/storage/framework/views \ + && chmod -R 755 /var/www/pterodactyl/storage/* /tmp/pterodactyl/cache \ + && chown -R caddy:caddy /var/www/pterodactyl /tmp/pterodactyl/{cache,framework} + +USER caddy +ENV USER=caddy + +RUN composer install --no-dev --optimize-autoloader \ + && rm -rf bootstrap/cache/*.php \ + && rm -rf .env storage/logs/*.log + +COPY --from=caddy /usr/bin/caddy /usr/local/bin/caddy +COPY .github/docker/Caddyfile /etc/caddy/Caddyfile +COPY .github/docker/php-fpm.conf /etc/php-fpm.conf +COPY .github/docker/supervisord.conf /etc/supervisord.conf +COPY .github/docker/yacron.yaml /etc/yacron.yaml + +EXPOSE 8080 +CMD ["/usr/bin/supervisord", "--configuration=/etc/supervisord.conf"] diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e215d94066b..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -# 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 of Pterodactyl. -FROM --platform=$TARGETOS/$TARGETARCH mhart/alpine-node:14 -WORKDIR /app -COPY . ./ -RUN yarn install --frozen-lockfile \ - && yarn run build:production - -# Stage 1: -# Build the actual container with all of the needed PHP dependencies that will run the application. -FROM --platform=$TARGETOS/$TARGETARCH php:8.1-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 certbot certbot-nginx \ - && docker-php-ext-configure zip \ - && docker-php-ext-install bcmath gd 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 \ - && chown -R nginx:nginx . - -RUN rm /usr/local/etc/php-fpm.conf \ - && echo "* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1" >> /var/spool/cron/crontabs/root \ - && echo "0 23 * * * certbot renew --nginx --quiet" >> /var/spool/cron/crontabs/root \ - && sed -i s/ssl_session_cache/#ssl_session_cache/g /etc/nginx/nginx.conf \ - && mkdir -p /var/run/php /var/run/nginx - -COPY .github/docker/default.conf /etc/nginx/http.d/default.conf -COPY .github/docker/www.conf /usr/local/etc/php-fpm.conf -COPY .github/docker/supervisord.conf /etc/supervisord.conf - -EXPOSE 80 443 -ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ] -CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ] diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index bcc636b5c79..00cc8615ed9 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -25,7 +25,7 @@ use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -class Handler extends ExceptionHandler +final class Handler extends ExceptionHandler { /** * The validation parser in Laravel formats custom rules using the class name diff --git a/app/Http/Controllers/Admin/ApiController.php b/app/Http/Controllers/Admin/ApiController.php index 02ad6e540d2..247aaee00be 100644 --- a/app/Http/Controllers/Admin/ApiController.php +++ b/app/Http/Controllers/Admin/ApiController.php @@ -9,7 +9,6 @@ use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Services\Acl\Api\AdminAcl; -use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Api\KeyCreationService; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; @@ -23,8 +22,7 @@ class ApiController extends Controller public function __construct( private AlertsMessageBag $alert, private ApiKeyRepositoryInterface $repository, - private KeyCreationService $keyCreationService, - private ViewFactory $view, + private KeyCreationService $keyCreationService ) { } @@ -33,7 +31,7 @@ public function __construct( */ public function index(Request $request): View { - return $this->view->make('admin.api.index', [ + return view('admin.api.index', [ 'keys' => $this->repository->getApplicationKeys($request->user()), ]); } @@ -48,7 +46,7 @@ public function create(): View $resources = AdminAcl::getResourceList(); sort($resources); - return $this->view->make('admin.api.new', [ + return view('admin.api.new', [ 'resources' => $resources, 'permissions' => [ 'r' => AdminAcl::READ, diff --git a/app/Http/Controllers/Admin/BaseController.php b/app/Http/Controllers/Admin/BaseController.php index 53f53ce54c6..2b69330740a 100644 --- a/app/Http/Controllers/Admin/BaseController.php +++ b/app/Http/Controllers/Admin/BaseController.php @@ -3,7 +3,6 @@ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\View\View; -use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Helpers\SoftwareVersionService; @@ -12,7 +11,7 @@ class BaseController extends Controller /** * BaseController constructor. */ - public function __construct(private SoftwareVersionService $version, private ViewFactory $view) + public function __construct(private SoftwareVersionService $version) { } @@ -21,6 +20,6 @@ public function __construct(private SoftwareVersionService $version, private Vie */ public function index(): View { - return $this->view->make('admin.index', ['version' => $this->version]); + return view('admin.index', ['version' => $this->version]); } } diff --git a/app/Http/Controllers/Admin/DatabaseController.php b/app/Http/Controllers/Admin/DatabaseController.php index e0dc0dc5775..dc436ffaa1d 100644 --- a/app/Http/Controllers/Admin/DatabaseController.php +++ b/app/Http/Controllers/Admin/DatabaseController.php @@ -8,7 +8,6 @@ use Pterodactyl\Models\DatabaseHost; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; -use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Databases\Hosts\HostUpdateService; use Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest; @@ -30,8 +29,7 @@ public function __construct( private HostCreationService $creationService, private HostDeletionService $deletionService, private HostUpdateService $updateService, - private LocationRepositoryInterface $locationRepository, - private ViewFactory $view + private LocationRepositoryInterface $locationRepository ) { } @@ -40,7 +38,7 @@ public function __construct( */ public function index(): View { - return $this->view->make('admin.databases.index', [ + return view('admin.databases.index', [ 'locations' => $this->locationRepository->getAllWithNodes(), 'hosts' => $this->repository->getWithViewDetails(), ]); @@ -53,7 +51,7 @@ public function index(): View */ public function view(int $host): View { - return $this->view->make('admin.databases.view', [ + return view('admin.databases.view', [ 'locations' => $this->locationRepository->getAllWithNodes(), 'host' => $this->repository->find($host), 'databases' => $this->databaseRepository->getDatabasesForHost($host), diff --git a/app/Http/Controllers/Admin/LocationController.php b/app/Http/Controllers/Admin/LocationController.php index ea01cbaa90f..41ed581db76 100644 --- a/app/Http/Controllers/Admin/LocationController.php +++ b/app/Http/Controllers/Admin/LocationController.php @@ -35,7 +35,7 @@ public function __construct( */ public function index(): View { - return $this->view->make('admin.locations.index', [ + return view('admin.locations.index', [ 'locations' => $this->repository->getAllWithDetails(), ]); } @@ -47,7 +47,7 @@ public function index(): View */ public function view(int $id): View { - return $this->view->make('admin.locations.view', [ + return view('admin.locations.view', [ 'location' => $this->repository->getWithNodes($id), ]); } diff --git a/app/Http/Controllers/Admin/MountController.php b/app/Http/Controllers/Admin/MountController.php index 097ad6690b7..8f4138989ac 100644 --- a/app/Http/Controllers/Admin/MountController.php +++ b/app/Http/Controllers/Admin/MountController.php @@ -37,7 +37,7 @@ public function __construct( */ public function index(): View { - return $this->view->make('admin.mounts.index', [ + return view('admin.mounts.index', [ 'mounts' => $this->repository->getAllWithDetails(), ]); } @@ -52,7 +52,7 @@ public function view(string $id): View $nests = Nest::query()->with('eggs')->get(); $locations = Location::query()->with('nodes')->get(); - return $this->view->make('admin.mounts.view', [ + return view('admin.mounts.view', [ 'mount' => $this->repository->getWithRelations($id), 'nests' => $nests, 'locations' => $locations, diff --git a/app/Http/Controllers/Admin/Nests/EggController.php b/app/Http/Controllers/Admin/Nests/EggController.php index 010c28af002..1efccfd5d09 100644 --- a/app/Http/Controllers/Admin/Nests/EggController.php +++ b/app/Http/Controllers/Admin/Nests/EggController.php @@ -42,7 +42,7 @@ public function create(): View $nests = $this->nestRepository->getWithEggs(); JavaScript::put(['nests' => $nests->keyBy('id')]); - return $this->view->make('admin.eggs.new', ['nests' => $nests]); + return view('admin.eggs.new', ['nests' => $nests]); } /** @@ -67,7 +67,7 @@ public function store(EggFormRequest $request): RedirectResponse */ public function view(Egg $egg): View { - return $this->view->make('admin.eggs.view', [ + return view('admin.eggs.view', [ 'egg' => $egg, 'images' => array_map( fn ($key, $value) => $key === $value ? $value : "$key|$value", diff --git a/app/Http/Controllers/Admin/Nests/EggScriptController.php b/app/Http/Controllers/Admin/Nests/EggScriptController.php index 4f997e5a86f..b68a211ac15 100644 --- a/app/Http/Controllers/Admin/Nests/EggScriptController.php +++ b/app/Http/Controllers/Admin/Nests/EggScriptController.php @@ -41,7 +41,7 @@ public function index(int $egg): View ['copy_script_from', '=', $egg->id], ]); - return $this->view->make('admin.eggs.scripts', [ + return view('admin.eggs.scripts', [ 'copyFromOptions' => $copy, 'relyOnScript' => $rely, 'egg' => $egg, diff --git a/app/Http/Controllers/Admin/Nests/EggVariableController.php b/app/Http/Controllers/Admin/Nests/EggVariableController.php index 40274b32392..c94a8dc2840 100644 --- a/app/Http/Controllers/Admin/Nests/EggVariableController.php +++ b/app/Http/Controllers/Admin/Nests/EggVariableController.php @@ -39,7 +39,7 @@ public function view(int $egg): View { $egg = $this->repository->getWithVariables($egg); - return $this->view->make('admin.eggs.variables', ['egg' => $egg]); + return view('admin.eggs.variables', ['egg' => $egg]); } /** diff --git a/app/Http/Controllers/Admin/Nests/NestController.php b/app/Http/Controllers/Admin/Nests/NestController.php index 037dd094309..fb24f106935 100644 --- a/app/Http/Controllers/Admin/Nests/NestController.php +++ b/app/Http/Controllers/Admin/Nests/NestController.php @@ -35,7 +35,7 @@ public function __construct( */ public function index(): View { - return $this->view->make('admin.nests.index', [ + return view('admin.nests.index', [ 'nests' => $this->repository->getWithCounts(), ]); } @@ -45,7 +45,7 @@ public function index(): View */ public function create(): View { - return $this->view->make('admin.nests.new'); + return view('admin.nests.new'); } /** @@ -68,7 +68,7 @@ public function store(StoreNestFormRequest $request): RedirectResponse */ public function view(int $nest): View { - return $this->view->make('admin.nests.view', [ + return view('admin.nests.view', [ 'nest' => $this->repository->getWithEggServers($nest), ]); } diff --git a/app/Http/Controllers/Admin/Nodes/NodeController.php b/app/Http/Controllers/Admin/Nodes/NodeController.php index d80df6c80d7..7936772406b 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeController.php @@ -7,17 +7,9 @@ use Pterodactyl\Models\Node; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Contracts\View\Factory as ViewFactory; class NodeController extends Controller { - /** - * NodeController constructor. - */ - public function __construct(private ViewFactory $view) - { - } - /** * Returns a listing of nodes on the system. */ @@ -30,6 +22,6 @@ public function index(Request $request): View ->allowedSorts(['id']) ->paginate(25); - return $this->view->make('admin.nodes.index', ['nodes' => $nodes]); + return view('admin.nodes.index', ['nodes' => $nodes]); } } diff --git a/app/Http/Controllers/Admin/Nodes/NodeViewController.php b/app/Http/Controllers/Admin/Nodes/NodeViewController.php index 67389032305..0ec60a7a03a 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeViewController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeViewController.php @@ -8,13 +8,11 @@ use Illuminate\Support\Collection; use Pterodactyl\Models\Allocation; use Pterodactyl\Http\Controllers\Controller; -use Illuminate\Contracts\View\Factory as ViewFactory; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Services\Helpers\SoftwareVersionService; use Pterodactyl\Repositories\Eloquent\LocationRepository; -use Pterodactyl\Repositories\Eloquent\AllocationRepository; class NodeViewController extends Controller { @@ -24,12 +22,10 @@ class NodeViewController extends Controller * NodeViewController constructor. */ public function __construct( - private AllocationRepository $allocationRepository, private LocationRepository $locationRepository, private NodeRepository $repository, private ServerRepository $serverRepository, - private SoftwareVersionService $versionService, - private ViewFactory $view + private SoftwareVersionService $versionService ) { } @@ -40,7 +36,7 @@ public function index(Request $request, Node $node): View { $node = $this->repository->loadLocationAndServerCount($node); - return $this->view->make('admin.nodes.view.index', [ + return view('admin.nodes.view.index', [ 'node' => $node, 'stats' => $this->repository->getUsageStats($node), 'version' => $this->versionService, @@ -52,7 +48,7 @@ public function index(Request $request, Node $node): View */ public function settings(Request $request, Node $node): View { - return $this->view->make('admin.nodes.view.settings', [ + return view('admin.nodes.view.settings', [ 'node' => $node, 'locations' => $this->locationRepository->all(), ]); @@ -63,7 +59,7 @@ public function settings(Request $request, Node $node): View */ public function configuration(Request $request, Node $node): View { - return $this->view->make('admin.nodes.view.configuration', compact('node')); + return view('admin.nodes.view.configuration', compact('node')); } /** @@ -75,7 +71,7 @@ public function allocations(Request $request, Node $node): View $this->plainInject(['node' => Collection::wrap($node)->only(['id'])]); - return $this->view->make('admin.nodes.view.allocation', [ + return view('admin.nodes.view.allocation', [ 'node' => $node, 'allocations' => Allocation::query()->where('node_id', $node->id) ->groupBy('ip') @@ -94,7 +90,7 @@ public function servers(Request $request, Node $node): View ->only(['scheme', 'fqdn', 'daemonListen', 'daemon_token_id', 'daemon_token']), ]); - return $this->view->make('admin.nodes.view.servers', [ + return view('admin.nodes.view.servers', [ 'node' => $node, 'servers' => $this->serverRepository->loadAllServersForNode($node->id, 25), ]); diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php index 573a1d9f8d3..6d7cba7ffde 100644 --- a/app/Http/Controllers/Admin/NodesController.php +++ b/app/Http/Controllers/Admin/NodesController.php @@ -60,7 +60,7 @@ public function create(): View|RedirectResponse return redirect()->route('admin.locations'); } - return $this->view->make('admin.nodes.new', ['locations' => $locations]); + return view('admin.nodes.new', ['locations' => $locations]); } /** diff --git a/app/Http/Controllers/Admin/Servers/CreateServerController.php b/app/Http/Controllers/Admin/Servers/CreateServerController.php index c7a1653ad4a..4ba37411bfb 100644 --- a/app/Http/Controllers/Admin/Servers/CreateServerController.php +++ b/app/Http/Controllers/Admin/Servers/CreateServerController.php @@ -4,11 +4,11 @@ use JavaScript; use Illuminate\View\View; +use Pterodactyl\Models\Nest; use Pterodactyl\Models\Node; use Pterodactyl\Models\Location; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; -use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; @@ -24,8 +24,7 @@ public function __construct( private AlertsMessageBag $alert, private NestRepository $nestRepository, private NodeRepository $nodeRepository, - private ServerCreationService $creationService, - private ViewFactory $view + private ServerCreationService $creationService ) { } @@ -47,14 +46,14 @@ public function index(): View|RedirectResponse JavaScript::put([ 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), - 'nests' => $nests->map(function ($item) { + 'nests' => $nests->map(function (Nest $item) { return array_merge($item->toArray(), [ 'eggs' => $item->eggs->keyBy('id')->toArray(), ]); })->keyBy('id'), ]); - return $this->view->make('admin.servers.new', [ + return view('admin.servers.new', [ 'locations' => Location::all(), 'nests' => $nests, ]); diff --git a/app/Http/Controllers/Admin/Servers/ServerController.php b/app/Http/Controllers/Admin/Servers/ServerController.php index 430c3f2b910..80de68ef93c 100644 --- a/app/Http/Controllers/Admin/Servers/ServerController.php +++ b/app/Http/Controllers/Admin/Servers/ServerController.php @@ -9,17 +9,9 @@ use Spatie\QueryBuilder\AllowedFilter; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Models\Filters\AdminServerFilter; -use Illuminate\Contracts\View\Factory as ViewFactory; class ServerController extends Controller { - /** - * ServerController constructor. - */ - public function __construct(private ViewFactory $view) - { - } - /** * Returns all the servers that exist on the system using a paginated result set. If * a query is passed along in the request it is also passed to the repository function. @@ -33,6 +25,6 @@ public function index(Request $request): View ]) ->paginate(config()->get('pterodactyl.paginate.admin.servers')); - return $this->view->make('admin.servers.index', ['servers' => $servers]); + return view('admin.servers.index', ['servers' => $servers]); } } diff --git a/app/Http/Controllers/Admin/Servers/ServerTransferController.php b/app/Http/Controllers/Admin/Servers/ServerTransferController.php index 8941ce10c56..6d949635002 100644 --- a/app/Http/Controllers/Admin/Servers/ServerTransferController.php +++ b/app/Http/Controllers/Admin/Servers/ServerTransferController.php @@ -4,6 +4,7 @@ use Carbon\CarbonImmutable; use Illuminate\Http\Request; +use Pterodactyl\Models\Node; use Pterodactyl\Models\Server; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; @@ -11,7 +12,6 @@ use Illuminate\Database\ConnectionInterface; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Nodes\NodeJWTService; -use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Wings\DaemonTransferRepository; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; @@ -25,8 +25,7 @@ public function __construct( private AllocationRepositoryInterface $allocationRepository, private ConnectionInterface $connection, private DaemonTransferRepository $daemonTransferRepository, - private NodeJWTService $nodeJWTService, - private NodeRepository $nodeRepository + private NodeJWTService $nodeJWTService ) { } @@ -48,7 +47,7 @@ public function transfer(Request $request, Server $server): RedirectResponse $additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []); // Check if the node is viable for the transfer. - $node = $this->nodeRepository->getNodeWithResourceUsage($node_id); + $node = Node::query()->findOrFail($node_id); if (!$node->isViable($server->memory, $server->disk)) { $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); @@ -58,7 +57,6 @@ public function transfer(Request $request, Server $server): RedirectResponse $server->validateTransferState(); $this->connection->transaction(function () use ($server, $node_id, $allocation_id, $additional_allocations) { - // Create a new ServerTransfer entry. $transfer = new ServerTransfer(); $transfer->server_id = $server->id; @@ -66,7 +64,7 @@ public function transfer(Request $request, Server $server): RedirectResponse $transfer->new_node = $node_id; $transfer->old_allocation = $server->allocation_id; $transfer->new_allocation = $allocation_id; - $transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id'); + $transfer->old_additional_allocations = $server->allocations->where('id', '!=', $server->allocation_id)->pluck('id')->all(); $transfer->new_additional_allocations = $additional_allocations; $transfer->save(); diff --git a/app/Http/Controllers/Admin/Servers/ServerViewController.php b/app/Http/Controllers/Admin/Servers/ServerViewController.php index 7cf64a2f579..3e662d479df 100644 --- a/app/Http/Controllers/Admin/Servers/ServerViewController.php +++ b/app/Http/Controllers/Admin/Servers/ServerViewController.php @@ -10,11 +10,9 @@ use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Servers\EnvironmentService; -use Illuminate\Contracts\View\Factory as ViewFactory; use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\MountRepository; -use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Repositories\Eloquent\LocationRepository; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; @@ -32,9 +30,7 @@ public function __construct( private MountRepository $mountRepository, private NestRepository $nestRepository, private NodeRepository $nodeRepository, - private ServerRepository $repository, - private EnvironmentService $environmentService, - private ViewFactory $view + private EnvironmentService $environmentService ) { } @@ -43,7 +39,7 @@ public function __construct( */ public function index(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.index', compact('server')); + return view('admin.servers.view.index', compact('server')); } /** @@ -51,7 +47,7 @@ public function index(Request $request, Server $server): View */ public function details(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.details', compact('server')); + return view('admin.servers.view.details', compact('server')); } /** @@ -61,7 +57,7 @@ public function build(Request $request, Server $server): View { $allocations = $server->node->allocations->toBase(); - return $this->view->make('admin.servers.view.build', [ + return view('admin.servers.view.build', [ 'server' => $server, 'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'), 'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'), @@ -88,7 +84,7 @@ public function startup(Request $request, Server $server): View })->keyBy('id'), ]); - return $this->view->make('admin.servers.view.startup', compact('server', 'nests')); + return view('admin.servers.view.startup', compact('server', 'nests')); } /** @@ -96,7 +92,7 @@ public function startup(Request $request, Server $server): View */ public function database(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.database', [ + return view('admin.servers.view.database', [ 'hosts' => $this->databaseHostRepository->all(), 'server' => $server, ]); @@ -109,7 +105,7 @@ public function mounts(Request $request, Server $server): View { $server->load('mounts'); - return $this->view->make('admin.servers.view.mounts', [ + return view('admin.servers.view.mounts', [ 'mounts' => $this->mountRepository->getMountListForServer($server), 'server' => $server, ]); @@ -138,7 +134,7 @@ public function manage(Request $request, Server $server): View 'nodeData' => $this->nodeRepository->getNodesForServerCreation(), ]); - return $this->view->make('admin.servers.view.manage', [ + return view('admin.servers.view.manage', [ 'server' => $server, 'locations' => $this->locationRepository->all(), 'canTransfer' => $canTransfer, @@ -150,6 +146,6 @@ public function manage(Request $request, Server $server): View */ public function delete(Request $request, Server $server): View { - return $this->view->make('admin.servers.view.delete', compact('server')); + return view('admin.servers.view.delete', compact('server')); } } diff --git a/app/Http/Controllers/Admin/Settings/AdvancedController.php b/app/Http/Controllers/Admin/Settings/AdvancedController.php index bf688323259..20f8f3c0368 100644 --- a/app/Http/Controllers/Admin/Settings/AdvancedController.php +++ b/app/Http/Controllers/Admin/Settings/AdvancedController.php @@ -6,7 +6,6 @@ use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Illuminate\Contracts\Console\Kernel; -use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\SettingsRepositoryInterface; @@ -21,8 +20,7 @@ public function __construct( private AlertsMessageBag $alert, private ConfigRepository $config, private Kernel $kernel, - private SettingsRepositoryInterface $settings, - private ViewFactory $view + private SettingsRepositoryInterface $settings ) { } @@ -39,7 +37,7 @@ public function index(): View $showRecaptchaWarning = true; } - return $this->view->make('admin.settings.advanced', [ + return view('admin.settings.advanced', [ 'showRecaptchaWarning' => $showRecaptchaWarning, ]); } diff --git a/app/Http/Controllers/Admin/Settings/IndexController.php b/app/Http/Controllers/Admin/Settings/IndexController.php index eabede932c7..ca92e7d0332 100644 --- a/app/Http/Controllers/Admin/Settings/IndexController.php +++ b/app/Http/Controllers/Admin/Settings/IndexController.php @@ -6,7 +6,6 @@ use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Illuminate\Contracts\Console\Kernel; -use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Traits\Helpers\AvailableLanguages; use Pterodactyl\Services\Helpers\SoftwareVersionService; @@ -24,8 +23,7 @@ public function __construct( private AlertsMessageBag $alert, private Kernel $kernel, private SettingsRepositoryInterface $settings, - private SoftwareVersionService $versionService, - private ViewFactory $view + private SoftwareVersionService $versionService ) { } @@ -34,7 +32,7 @@ public function __construct( */ public function index(): View { - return $this->view->make('admin.settings.index', [ + return view('admin.settings.index', [ 'version' => $this->versionService, 'languages' => $this->getAvailableLanguages(true), ]); diff --git a/app/Http/Controllers/Admin/Settings/MailController.php b/app/Http/Controllers/Admin/Settings/MailController.php index 2db87fd58f1..8c9365641aa 100644 --- a/app/Http/Controllers/Admin/Settings/MailController.php +++ b/app/Http/Controllers/Admin/Settings/MailController.php @@ -8,7 +8,6 @@ use Illuminate\Http\Response; use Illuminate\Contracts\Console\Kernel; use Pterodactyl\Notifications\MailTested; -use Illuminate\View\Factory as ViewFactory; use Illuminate\Support\Facades\Notification; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; @@ -27,8 +26,7 @@ public function __construct( private ConfigRepository $config, private Encrypter $encrypter, private Kernel $kernel, - private SettingsRepositoryInterface $settings, - private ViewFactory $view + private SettingsRepositoryInterface $settings ) { } @@ -38,7 +36,7 @@ public function __construct( */ public function index(): View { - return $this->view->make('admin.settings.mail', [ + return view('admin.settings.mail', [ 'disabled' => $this->config->get('mail.default') !== 'smtp', ]); } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 1d6db656919..ef22189d4af 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -6,13 +6,13 @@ use Illuminate\Http\Request; use Pterodactyl\Models\User; use Pterodactyl\Models\Model; -use Illuminate\Support\Collection; use Illuminate\Http\RedirectResponse; use Prologue\Alerts\AlertsMessageBag; use Spatie\QueryBuilder\QueryBuilder; use Illuminate\View\Factory as ViewFactory; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Translation\Translator; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Traits\Helpers\AvailableLanguages; @@ -57,7 +57,7 @@ public function index(Request $request): View ->allowedSorts(['id', 'uuid']) ->paginate(50); - return $this->view->make('admin.users.index', ['users' => $users]); + return view('admin.users.index', ['users' => $users]); } /** @@ -65,7 +65,7 @@ public function index(Request $request): View */ public function create(): View { - return $this->view->make('admin.users.new', [ + return view('admin.users.new', [ 'languages' => $this->getAvailableLanguages(true), ]); } @@ -75,7 +75,7 @@ public function create(): View */ public function view(User $user): View { - return $this->view->make('admin.users.view', [ + return view('admin.users.view', [ 'user' => $user, 'languages' => $this->getAvailableLanguages(true), ]); @@ -132,22 +132,13 @@ public function update(UserFormRequest $request, User $user): RedirectResponse /** * Get a JSON response of users on the system. */ - public function json(Request $request): Model|Collection + public function json(Request $request): Model|LengthAwarePaginator { - $users = QueryBuilder::for(User::query())->allowedFilters(['email'])->paginate(25); - // Handle single user requests. if ($request->query('user_id')) { - $user = User::query()->findOrFail($request->input('user_id')); - $user->md5 = md5(strtolower($user->email)); - - return $user; + return User::query()->findOrFail($request->input('user_id')); } - return $users->map(function ($item) { - $item->md5 = md5(strtolower($item->email)); - - return $item; - }); + return QueryBuilder::for(User::query())->allowedFilters(['email'])->paginate(25); } } diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index 9548be25e9d..b674145ff26 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -52,7 +52,6 @@ public function index(GetStartupRequest $request, Server $server): array */ public function update(UpdateStartupVariableRequest $request, Server $server): array { - /** @var \Pterodactyl\Models\EggVariable $variable */ $variable = $server->variables()->where('env_variable', $request->input('key'))->first(); $original = $variable->server_value; @@ -62,6 +61,8 @@ public function update(UpdateStartupVariableRequest $request, Server $server): a throw new BadRequestHttpException('The environment variable you are trying to edit is read-only.'); } + /* @var \Pterodactyl\Models\EggVariable $variable */ + // Revalidate the variable value using the egg variable specific validation rules for it. $this->validate($request, ['value' => $variable->rules]); diff --git a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php index 8ce88e89ee5..8d8b0de92ac 100644 --- a/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Api\Remote\Servers; use Illuminate\Http\Request; +use Pterodactyl\Models\Backup; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Facades\Activity; @@ -98,9 +99,11 @@ public function resetState(Request $request): JsonResponse if ($subject = $activity->subjects->where('subject_type', 'backup')->first()) { // Just create a new audit entry for this event and update the server state // so that power actions, file management, and backups can resume as normal. + /** @var Backup $actualSubject */ + $actualSubject = $subject->subject; Activity::event('server:backup.restore-failed') - ->subject($server, $subject->subject) - ->property('name', $subject->subject->name) + ->subject($server, $actualSubject) + ->property('name', $actualSubject->name) ->log(); } } diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index f07282fbac7..74e7a9aa3a2 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -49,7 +49,9 @@ public function __construct() /** * Get the failed login response instance. * - * @throws \Pterodactyl\Exceptions\DisplayException + * @return never + * + * @throws DisplayException */ protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null, string $message = null) { diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index af05c55ef78..82580edc0bc 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -72,7 +72,7 @@ public function __invoke(LoginCheckpointRequest $request): JsonResponse } else { $decrypted = $this->encrypter->decrypt($user->totp_secret); - if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { + if ($this->google2FA->verifyKey($decrypted, $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { Event::dispatch(new ProvidedAuthenticationToken($user)); return $this->sendLoginResponse($user, $request); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 2dbb34ee36a..49a17378da1 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -9,19 +9,10 @@ use Illuminate\Http\JsonResponse; use Pterodactyl\Facades\Activity; use Illuminate\Contracts\View\View; -use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Database\Eloquent\ModelNotFoundException; class LoginController extends AbstractLoginController { - /** - * LoginController constructor. - */ - public function __construct(private ViewFactory $view) - { - parent::__construct(); - } - /** * Handle all incoming requests for the authentication routes and render the * base authentication view component. React will take over at this point and @@ -29,7 +20,7 @@ public function __construct(private ViewFactory $view) */ public function index(): View { - return $this->view->make('templates/auth.core'); + return view('templates/auth.core'); } /** diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 3325a1e6b30..9b22f6f9883 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Http\Controllers\Auth; use Illuminate\Support\Str; +use Pterodactyl\Models\User; use Illuminate\Http\JsonResponse; use Illuminate\Contracts\Hashing\Hasher; use Illuminate\Support\Facades\Password; @@ -67,13 +68,12 @@ function ($user, $password) { * account do not automatically log them in. In those cases, send the user back to the login * form with a note telling them their password was changed and to log back in. * - * @param \Illuminate\Contracts\Auth\CanResetPassword|\Pterodactyl\Models\User $user * @param string $password * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - protected function resetPassword($user, $password) + protected function resetPassword(User $user, $password) { $user = $this->userRepository->update($user->id, [ 'password' => $this->hasher->make($password), diff --git a/app/Http/Controllers/Base/IndexController.php b/app/Http/Controllers/Base/IndexController.php index fecaa91a3e6..ffa278b63bd 100644 --- a/app/Http/Controllers/Base/IndexController.php +++ b/app/Http/Controllers/Base/IndexController.php @@ -23,6 +23,6 @@ public function __construct( */ public function index(): View { - return $this->view->make('templates/base.core'); + return view('templates/base.core'); } } diff --git a/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php b/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php index 5d3530d865c..337f4af7134 100644 --- a/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php +++ b/app/Http/Middleware/Api/Client/Server/ResourceBelongsToServer.php @@ -29,11 +29,11 @@ class ResourceBelongsToServer public function handle(Request $request, Closure $next): mixed { $params = $request->route()->parameters(); - if (is_null($params) || !$params['server'] instanceof Server) { + if (!$params['server'] instanceof Server) { throw new InvalidArgumentException('This middleware cannot be used in a context that is missing a server in the parameters.'); } - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ $server = $request->route()->parameter('server'); $exception = new NotFoundHttpException('The requested resource was not found for this server.'); foreach ($params as $key => $model) { @@ -45,6 +45,7 @@ public function handle(Request $request, Closure $next): mixed continue; } + /** @var Allocation|Backup|Database|Schedule|Subuser $model */ switch (get_class($model)) { // All of these models use "server_id" as the field key for the server // they are assigned to, so the logic is identical for them all. @@ -71,6 +72,7 @@ public function handle(Request $request, Closure $next): mixed // Tasks are special since they're (currently) the only item in the API // that requires something in addition to the server in order to be accessed. case Task::class: + /** @var Schedule $schedule */ $schedule = $request->route()->parameter('schedule'); if ($model->schedule_id !== $schedule->id || $schedule->server_id !== $server->id) { throw $exception; diff --git a/app/Http/Middleware/RequireTwoFactorAuthentication.php b/app/Http/Middleware/RequireTwoFactorAuthentication.php index e3307727f2a..cffcc501167 100644 --- a/app/Http/Middleware/RequireTwoFactorAuthentication.php +++ b/app/Http/Middleware/RequireTwoFactorAuthentication.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Support\Str; use Illuminate\Http\Request; +use Pterodactyl\Models\User; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\Http\TwoFactorAuthRequiredException; @@ -36,12 +37,17 @@ public function __construct(private AlertsMessageBag $alert) */ public function handle(Request $request, Closure $next): mixed { - /** @var \Pterodactyl\Models\User $user */ + /** @var User $user */ $user = $request->user(); $uri = rtrim($request->getRequestUri(), '/') . '/'; $current = $request->route()->getName(); - if (!$user || Str::startsWith($uri, ['/auth/']) || Str::startsWith($current, ['auth.', 'account.'])) { + // Must be logged in + if (!$user instanceof User) { + return $next($request); + } + + if (Str::startsWith($uri, ['/auth/']) || Str::startsWith($current, ['auth.', 'account.'])) { return $next($request); } diff --git a/app/Http/Requests/Admin/LocationFormRequest.php b/app/Http/Requests/Admin/LocationFormRequest.php index b10e304a07c..57fa0310b80 100644 --- a/app/Http/Requests/Admin/LocationFormRequest.php +++ b/app/Http/Requests/Admin/LocationFormRequest.php @@ -12,7 +12,10 @@ class LocationFormRequest extends AdminFormRequest public function rules(): array { if ($this->method() === 'PATCH') { - return Location::getRulesForUpdate($this->route()->parameter('location')->id); + /** @var Location $location */ + $location = $this->route()->parameter('location'); + + return Location::getRulesForUpdate($location->id); } return Location::getRules(); diff --git a/app/Http/Requests/Admin/MountFormRequest.php b/app/Http/Requests/Admin/MountFormRequest.php index 074ea4a503a..22b2475f4cf 100644 --- a/app/Http/Requests/Admin/MountFormRequest.php +++ b/app/Http/Requests/Admin/MountFormRequest.php @@ -12,7 +12,10 @@ class MountFormRequest extends AdminFormRequest public function rules(): array { if ($this->method() === 'PATCH') { - return Mount::getRulesForUpdate($this->route()->parameter('mount')->id); + /** @var Mount $mount */ + $mount = $this->route()->parameter('mount'); + + return Mount::getRulesForUpdate($mount->id); } return Mount::getRules(); diff --git a/app/Http/Requests/Api/Application/ApplicationApiRequest.php b/app/Http/Requests/Api/Application/ApplicationApiRequest.php index 2e0ed133a5b..082bc6921fc 100644 --- a/app/Http/Requests/Api/Application/ApplicationApiRequest.php +++ b/app/Http/Requests/Api/Application/ApplicationApiRequest.php @@ -29,7 +29,7 @@ abstract class ApplicationApiRequest extends FormRequest * Determine if the current user is authorized to perform * the requested action against the API. * - * @throws \Pterodactyl\Exceptions\PterodactylException + * @throws PterodactylException */ public function authorize(): bool { @@ -42,6 +42,7 @@ public function authorize(): bool return true; } + /** @var ApiKey $token */ if ($token->key_type === ApiKey::TYPE_ACCOUNT) { return true; } @@ -81,6 +82,7 @@ public function withValidator(Validator $validator): void */ public function parameter(string $key, string $expect) { + /** @var ApiKey $value */ $value = $this->route()->parameter($key); Assert::isInstanceOf($value, $expect); diff --git a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php index ce42e6f0511..b7acac9781e 100644 --- a/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php +++ b/app/Http/Requests/Api/Application/Locations/UpdateLocationRequest.php @@ -11,7 +11,9 @@ class UpdateLocationRequest extends StoreLocationRequest */ public function rules(): array { - $locationId = $this->route()->parameter('location')->id; + /** @var Location $location */ + $location = $this->route()->parameter('location'); + $locationId = $location->id; return collect(Location::getRulesForUpdate($locationId))->only([ 'short', diff --git a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php index 7133bd0b521..de6b7b45c95 100644 --- a/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/UpdateNodeRequest.php @@ -12,8 +12,9 @@ class UpdateNodeRequest extends StoreNodeRequest */ public function rules(array $rules = null): array { - $node = $this->route()->parameter('node')->id; + /** @var Node $node */ + $node = $this->route()->parameter('node'); - return parent::rules(Node::getRulesForUpdate($node)); + return parent::rules(Node::getRulesForUpdate($node->id)); } } diff --git a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php index d53a0a75ef6..f52bed805d7 100644 --- a/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php +++ b/app/Http/Requests/Api/Application/Servers/Databases/StoreServerDatabaseRequest.php @@ -21,6 +21,7 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest */ public function rules(): array { + /** @var Server $server */ $server = $this->route()->parameter('server'); return [ @@ -67,6 +68,7 @@ public function attributes(): array */ public function databaseName(): string { + /** @var Server $server */ $server = $this->route()->parameter('server'); Assert::isInstanceOf($server, Server::class); diff --git a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php index be4f4a719dc..41569cfabf4 100644 --- a/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Databases/StoreDatabaseRequest.php @@ -21,6 +21,7 @@ public function permission(): string public function rules(): array { + /** @var Server $server */ $server = $this->route()->parameter('server'); Assert::isInstanceOf($server, Server::class); diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php index 7c4fab9d226..1381e5cb92a 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php @@ -63,7 +63,6 @@ protected function validatePermissionsCanBeAssigned(array $permissions) // Otherwise, get the current subuser's permission set, and ensure that the // permissions they are trying to assign are not _more_ than the ones they // already have. - /** @var \Pterodactyl\Models\Subuser|null $subuser */ /** @var \Pterodactyl\Services\Servers\GetUserPermissionsService $service */ $service = $this->container->make(GetUserPermissionsService::class); diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 31c3e342004..c33e5b194cf 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -26,9 +26,9 @@ * @property string|null $startup * @property bool $script_is_privileged * @property string|null $script_install - * @property string $script_entry - * @property string $script_container - * @property int|null $copy_script_from + * @property ?string $script_entry + * @property ?string $script_container + * @property ?int $copy_script_from * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at * @property string|null $copy_script_install diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index 8c34bb39804..dbbf8e0bd0d 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -18,8 +18,9 @@ * @property \Carbon\CarbonImmutable $created_at * @property \Carbon\CarbonImmutable $updated_at * @property bool $required - * @property \Pterodactyl\Models\Egg $egg - * @property \Pterodactyl\Models\ServerVariable $serverVariable + * @property Egg $egg + * @property ServerVariable $serverVariable + * @property string $field_type * * The "server_value" variable is only present on the object if you've loaded this model * using the server relationship. diff --git a/app/Models/Model.php b/app/Models/Model.php index 2e371d9d9cf..34b856c3853 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -155,6 +155,7 @@ public function validate(): void return; } + /** @var \Illuminate\Validation\Validator $validator */ $validator = $this->getValidator(); $validator->setData( // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist diff --git a/app/Models/Node.php b/app/Models/Node.php index 504a28c2480..a1d9688f675 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -6,6 +6,7 @@ use Symfony\Component\Yaml\Yaml; use Illuminate\Container\Container; use Illuminate\Notifications\Notifiable; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -24,20 +25,24 @@ * @property bool $maintenance_mode * @property int $memory * @property int $memory_overallocate + * @property int $sum_memory * @property int $disk * @property int $disk_overallocate + * @property int $sum_disk * @property int $upload_size * @property string $daemon_token_id * @property string $daemon_token * @property int $daemonListen * @property int $daemonSFTP * @property string $daemonBase + * @property int $servers_count * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $updated_at - * @property \Pterodactyl\Models\Location $location - * @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts - * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers - * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations + * @property Location $location + * @property int[]|\Illuminate\Support\Collection $ports + * @property Mount[]|Collection $mounts + * @property Server[]|Collection $servers + * @property Allocation[]|Collection $allocations */ class Node extends Model { @@ -220,11 +225,21 @@ public function allocations(): HasMany return $this->hasMany(Allocation::class); } + public function loadServerSums(): self + { + $this->loadSum('servers as sum_memory', 'memory'); + $this->loadSum('servers as sum_disk', 'disk'); + + return $this; + } + /** * Returns a boolean if the node is viable for an additional server to be placed on it. */ - public function isViable(int $memory, int $disk): bool + public function isViable(int $memory = 0, int $disk = 0): bool { + $this->loadServerSums(); + $memoryLimit = $this->memory * (1.0 + ($this->memory_overallocate / 100.0)); $diskLimit = $this->disk * (1.0 + ($this->disk_overallocate / 100.0)); diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 52c7f1cffb2..ec85a2ecf63 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Models; +/** @property string $value */ class Setting extends Model { /** diff --git a/app/Models/User.php b/app/Models/User.php index eb3c15d220c..bef6da78173 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Builder; use Pterodactyl\Models\Traits\HasAccessTokens; use Illuminate\Auth\Passwords\CanResetPassword; +use Illuminate\Database\Eloquent\Casts\Attribute; use Pterodactyl\Traits\Helpers\AvailableLanguages; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\Access\Authorizable; @@ -129,6 +130,10 @@ class User extends Model implements 'root_admin', ]; + protected $appends = [ + 'md5', + ]; + /** * Cast values to correct type. */ @@ -259,6 +264,13 @@ public function activity(): MorphToMany return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); } + public function md5(): Attribute + { + return Attribute::make( + get: fn () => md5(strtolower($this->email)), + ); + } + /** * Returns all the servers that a user can access by way of being the owner of the * server, or because they are assigned as a subuser for that server. diff --git a/app/Notifications/ServerInstalled.php b/app/Notifications/ServerInstalled.php index 46f38ff84ef..3eb93e79dbc 100644 --- a/app/Notifications/ServerInstalled.php +++ b/app/Notifications/ServerInstalled.php @@ -26,12 +26,14 @@ class ServerInstalled extends Notification implements ShouldQueue, ReceivesEvent * Handle a direct call to this notification from the server installed event. This is configured * in the event service provider. */ - public function handle(Event|Installed $event): void + public function handle(Event|Installed $notification): void { - $event->server->loadMissing('user'); + abort_unless($notification instanceof Installed, 500); + /* @var Installed $notification */ + $notification->server->loadMissing('user'); - $this->server = $event->server; - $this->user = $event->server->user; + $this->server = $notification->server; + $this->user = $notification->server->user; // Since we are calling this notification directly from an event listener we need to fire off the dispatcher // to send the email now. Don't use send() or you'll end up firing off two different events. diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index a78295be460..ea636ce9cef 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -78,6 +78,7 @@ public function getBuilder(): Builder */ public function create(array $fields, bool $validate = true, bool $force = false): Model|bool { + /** @var \Pterodactyl\Models\Model $instance */ $instance = $this->getBuilder()->newModelInstance(); ($force) ? $instance->forceFill($fields) : $instance->fill($fields); @@ -163,6 +164,7 @@ public function deleteWhere(array $attributes, bool $force = false): int public function update(int $id, array $fields, bool $validate = true, bool $force = false): Model|bool { try { + /** @var \Pterodactyl\Models\Model $instance */ $instance = $this->getBuilder()->where('id', $id)->firstOrFail(); } catch (ModelNotFoundException) { throw new RecordNotFoundException(); diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index d7a3818f3d8..0ee703a7952 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -21,11 +21,7 @@ public function model(): string */ public function getUsageStats(Node $node): array { - $stats = $this->getBuilder() - ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') - ->join('servers', 'servers.node_id', '=', 'nodes.id') - ->where('node_id', '=', $node->id) - ->first(); + $stats = $node->loadServerSums(); return Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory]) ->mapWithKeys(function ($value, $key) use ($node) { @@ -53,9 +49,7 @@ public function getUsageStats(Node $node): array */ public function getUsageStatsRaw(Node $node): array { - $stats = $this->getBuilder()->select( - $this->getBuilder()->raw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') - )->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first(); + $stats = $node->loadServerSums(); return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) { $maxUsage = $node->{$key}; @@ -84,9 +78,7 @@ public function loadLocationAndServerCount(Node $node, bool $refresh = false): N // This is quite ugly and can probably be improved down the road. // And by probably, I mean it should. if (is_null($node->servers_count) || $refresh) { - $node->load('servers'); - $node->setRelation('servers_count', count($node->getRelation('servers'))); - unset($node->servers); + $node->loadCount('servers'); } return $node; @@ -135,18 +127,4 @@ public function getNodesForServerCreation(): Collection ]; })->values(); } - - /** - * Returns a node with the given id with the Node's resource usage. - */ - public function getNodeWithResourceUsage(int $node_id): Node - { - $instance = $this->getBuilder() - ->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate']) - ->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory, COALESCE(SUM(servers.disk), 0) as sum_disk') - ->leftJoin('servers', 'servers.node_id', '=', 'nodes.id') - ->where('nodes.id', $node_id); - - return $instance->first(); - } } diff --git a/app/Repositories/Eloquent/SettingsRepository.php b/app/Repositories/Eloquent/SettingsRepository.php index df22fce595b..e8003129a13 100644 --- a/app/Repositories/Eloquent/SettingsRepository.php +++ b/app/Repositories/Eloquent/SettingsRepository.php @@ -46,6 +46,7 @@ public function get(string $key, mixed $default = null): mixed return value($default); } + /** @var Setting $instance */ $instance = $this->getBuilder()->where('key', $key)->first(); if (is_null($instance)) { self::$databaseMiss[$key] = true; diff --git a/app/Rules/Fqdn.php b/app/Rules/Fqdn.php index 47baf510d77..915ca40158d 100644 --- a/app/Rules/Fqdn.php +++ b/app/Rules/Fqdn.php @@ -6,11 +6,11 @@ use Illuminate\Contracts\Validation\Rule; use Illuminate\Contracts\Validation\DataAwareRule; -class Fqdn implements Rule, DataAwareRule +final class Fqdn implements Rule, DataAwareRule { - protected array $data = []; - protected string $message = ''; - protected ?string $schemeField = null; + private array $data = []; + private string $message = ''; + private ?string $schemeField = null; /** * @param array $data diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index ec79d18f183..11d30132245 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -40,23 +40,24 @@ public function __construct(protected AllocationRepositoryInterface $repository, */ public function handle(Node $node, array $data): void { - $explode = explode('/', $data['allocation_ip']); + $allocationIp = $data['allocation_ip']; + $explode = explode('/', $allocationIp); if (count($explode) !== 1) { if (!ctype_digit($explode[1]) || ($explode[1] > self::CIDR_MIN_BITS || $explode[1] < self::CIDR_MAX_BITS)) { throw new CidrOutOfRangeException(); } } + $underlying = 'Unknown IP'; try { // TODO: how should we approach supporting IPv6 with this? // gethostbyname only supports IPv4, but the alternative (dns_get_record) returns // an array of records, which is not ideal for this use case, we need a SINGLE // IP to use, not multiple. - $underlying = gethostbyname($data['allocation_ip']); + $underlying = gethostbyname($allocationIp); $parsed = Network::parse($underlying); } catch (Exception $exception) { - /* @noinspection PhpUndefinedVariableInspection */ - throw new DisplayException("Could not parse provided allocation IP address ({$underlying}): {$exception->getMessage()}", $exception); + throw new DisplayException("Could not parse provided allocation IP address for $allocationIp ($underlying): {$exception->getMessage()}", $exception); } $this->connection->beginTransaction(); diff --git a/app/Services/Backups/DeleteBackupService.php b/app/Services/Backups/DeleteBackupService.php index fd65969c174..bbb54ce6a91 100644 --- a/app/Services/Backups/DeleteBackupService.php +++ b/app/Services/Backups/DeleteBackupService.php @@ -73,7 +73,10 @@ protected function deleteFromS3(Backup $backup): void /** @var \Pterodactyl\Extensions\Filesystem\S3Filesystem $adapter */ $adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3); - $adapter->getClient()->deleteObject([ + /** @var \Aws\S3\S3Client $client */ + $client = $adapter->getClient(); + + $client->deleteObject([ 'Bucket' => $adapter->getBucket(), 'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid), ]); diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index be8f966326b..f1427540310 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -98,17 +98,17 @@ public function handle(Server $server, string $name = null, bool $override = fal // Get the oldest backup the server has that is not "locked" (indicating a backup that should // never be automatically purged). If we find a backup we will delete it and then continue with // this process. If no backup is found that can be used an exception is thrown. - /** @var \Pterodactyl\Models\Backup $oldest */ $oldest = $successful->where('is_locked', false)->orderBy('created_at')->first(); if (!$oldest) { throw new TooManyBackupsException($server->backup_limit); } + /* @var Backup $oldest */ $this->deleteBackupService->handle($oldest); } return $this->connection->transaction(function () use ($server, $name) { - /** @var \Pterodactyl\Models\Backup $backup */ + /** @var Backup $backup */ $backup = $this->repository->create([ 'server_id' => $server->id, 'uuid' => Uuid::uuid4()->toString(), diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index b70cb8b4dc3..0635c1c70c2 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -119,6 +119,7 @@ public function create(Server $server, array $data): Database }); } catch (Exception $exception) { try { + /** @var ?Database $database */ if ($database instanceof Database) { $this->repository->dropDatabase($database->database); $this->repository->dropUser($database->username, $database->remote); diff --git a/app/Services/Deployment/FindViableNodesService.php b/app/Services/Deployment/FindViableNodesService.php index a95211c3f91..bd2acfdd241 100644 --- a/app/Services/Deployment/FindViableNodesService.php +++ b/app/Services/Deployment/FindViableNodesService.php @@ -78,7 +78,7 @@ public function handle(int $perPage = null, int $page = null): LengthAwarePagina ->where('nodes.public', 1); if (!empty($this->locations)) { - $query = $query->whereIn('nodes.location_id', $this->locations); + $query = $query->whereIn('location_id', $this->locations); } $results = $query->groupBy('nodes.id') diff --git a/app/Services/Schedules/ProcessScheduleService.php b/app/Services/Schedules/ProcessScheduleService.php index cfbc7e5cad8..d1b5811a88a 100644 --- a/app/Services/Schedules/ProcessScheduleService.php +++ b/app/Services/Schedules/ProcessScheduleService.php @@ -27,13 +27,13 @@ public function __construct(private ConnectionInterface $connection, private Dis */ public function handle(Schedule $schedule, bool $now = false): void { - /** @var \Pterodactyl\Models\Task $task */ $task = $schedule->tasks()->orderBy('sequence_id')->first(); if (is_null($task)) { throw new DisplayException('Cannot process schedule for task execution: no tasks are registered.'); } + /* @var \Pterodactyl\Models\Task $task */ $this->connection->transaction(function () use ($schedule, $task) { $schedule->forceFill([ 'is_processing' => true, diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 05553d7f10a..b7a22fdaaed 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -88,14 +88,13 @@ private function processAllocations(Server $server, array &$data): void // Handle the addition of allocations to this server. Only assign allocations that are not currently // assigned to a different server, and only allocations on the same node as the server. if (!empty($data['add_allocations'])) { - $query = Allocation::query() - ->where('node_id', $server->node_id) + $query = $server->node->allocations() ->whereIn('id', $data['add_allocations']) ->whereNull('server_id'); // Keep track of all the allocations we're just now adding so that we can use the first // one to reset the default allocation to. - $freshlyAllocated = $query->pluck('id')->first(); + $freshlyAllocated = $query->first()->id ?? null; $query->update(['server_id' => $server->id, 'notes' => null]); } diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 2c9b4cf8446..62e848aa0ae 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -82,7 +82,7 @@ public function handle(array $data, DeploymentObject $deployment = null): Server // // If that connection fails out we will attempt to perform a cleanup by just // deleting the server itself from the system. - /** @var \Pterodactyl\Models\Server $server */ + /** @var Server $server */ $server = $this->connection->transaction(function () use ($data, $eggVariableData) { // Create the server and assign any additional allocations to it. $server = $this->createModel($data); @@ -115,7 +115,7 @@ public function handle(array $data, DeploymentObject $deployment = null): Server */ private function configureDeployment(array $data, DeploymentObject $deployment): Allocation { - /** @var \Illuminate\Support\Collection $nodes */ + /** @var Collection $nodes */ $nodes = $this->findViableNodesService->setLocations($deployment->getLocations()) ->setDisk(Arr::get($data, 'disk')) ->setMemory(Arr::get($data, 'memory')) @@ -136,7 +136,7 @@ private function createModel(array $data): Server { $uuid = $this->generateUniqueUuidCombo(); - /** @var \Pterodactyl\Models\Server $model */ + /** @var Server $model */ $model = $this->repository->create([ 'external_id' => Arr::get($data, 'external_id'), 'uuid' => $uuid, diff --git a/app/Services/Users/UserDeletionService.php b/app/Services/Users/UserDeletionService.php index f7f060cee46..0f373240f97 100644 --- a/app/Services/Users/UserDeletionService.php +++ b/app/Services/Users/UserDeletionService.php @@ -25,7 +25,7 @@ public function __construct( * * @throws \Pterodactyl\Exceptions\DisplayException */ - public function handle(int|User $user): ?bool + public function handle(int|User $user): void { if ($user instanceof User) { $user = $user->id; @@ -36,6 +36,6 @@ public function handle(int|User $user): ?bool throw new DisplayException($this->translator->get('admin/user.exceptions.user_has_servers')); } - return $this->repository->delete($user); + $this->repository->delete($user); } } diff --git a/app/Transformers/Api/Application/ServerVariableTransformer.php b/app/Transformers/Api/Application/ServerVariableTransformer.php index 25e8f879bdc..e27d1e01354 100644 --- a/app/Transformers/Api/Application/ServerVariableTransformer.php +++ b/app/Transformers/Api/Application/ServerVariableTransformer.php @@ -4,6 +4,7 @@ use League\Fractal\Resource\Item; use Pterodactyl\Models\EggVariable; +use Pterodactyl\Models\ServerVariable; use League\Fractal\Resource\NullResource; use Pterodactyl\Services\Acl\Api\AdminAcl; diff --git a/composer.json b/composer.json index 160537b6bbb..a03a9dcfc1e 100644 --- a/composer.json +++ b/composer.json @@ -61,6 +61,7 @@ "laravel/sail": "~1.16", "mockery/mockery": "~1.5", "nunomaduro/collision": "~6.3", + "nunomaduro/larastan": "^2.0", "php-mock/php-mock-phpunit": "~2.6", "phpunit/phpunit": "~9.5", "spatie/laravel-ignition": "~1.5" diff --git a/composer.lock b/composer.lock index 9f4b73f6f9c..381b321ae56 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ae61e7d6e405e3a59c8a54f3eefa2c50", + "content-hash": "88999658a97429a6840f4da57ea115b1", "packages": [ { "name": "aws/aws-crt-php", @@ -9004,6 +9004,103 @@ ], "time": "2022-09-29T12:29:49+00:00" }, + { + "name": "nunomaduro/larastan", + "version": "2.2.7", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/larastan.git", + "reference": "a3f67a4a668e477751557b0b19ad2c870e1e4e56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/a3f67a4a668e477751557b0b19ad2c870e1e4e56", + "reference": "a3f67a4a668e477751557b0b19ad2c870e1e4e56", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^9", + "illuminate/container": "^9", + "illuminate/contracts": "^9", + "illuminate/database": "^9", + "illuminate/http": "^9", + "illuminate/pipeline": "^9", + "illuminate/support": "^9", + "mockery/mockery": "^1.4.4", + "php": "^8.0.2", + "phpmyadmin/sql-parser": "^5.5", + "phpstan/phpstan": "^1.8.7" + }, + "require-dev": { + "nikic/php-parser": "^4.13.2", + "orchestra/testbench": "^7.0.0", + "phpunit/phpunit": "^9.5.11" + }, + "suggest": { + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" + }, + "type": "phpstan-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "NunoMaduro\\Larastan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" + ], + "support": { + "issues": "https://github.com/nunomaduro/larastan/issues", + "source": "https://github.com/nunomaduro/larastan/tree/2.2.7" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/canvural", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2022-10-30T15:02:40+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.3", @@ -9422,6 +9519,138 @@ }, "time": "2022-10-14T12:47:21+00:00" }, + { + "name": "phpmyadmin/sql-parser", + "version": "5.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpmyadmin/sql-parser.git", + "reference": "8ab99cd0007d880f49f5aa1807033dbfa21b1cb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/8ab99cd0007d880f49f5aa1807033dbfa21b1cb5", + "reference": "8ab99cd0007d880f49f5aa1807033dbfa21b1cb5", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "conflict": { + "phpmyadmin/motranslator": "<3.0" + }, + "require-dev": { + "phpmyadmin/coding-standard": "^3.0", + "phpmyadmin/motranslator": "^4.0 || ^5.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/php-code-coverage": "*", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.11", + "zumba/json-serializer": "^3.0" + }, + "suggest": { + "ext-mbstring": "For best performance", + "phpmyadmin/motranslator": "Translate messages to your favorite locale" + }, + "bin": [ + "bin/highlight-query", + "bin/lint-query", + "bin/tokenize-query" + ], + "type": "library", + "autoload": { + "psr-4": { + "PhpMyAdmin\\SqlParser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "The phpMyAdmin Team", + "email": "developers@phpmyadmin.net", + "homepage": "https://www.phpmyadmin.net/team/" + } + ], + "description": "A validating SQL lexer and parser with a focus on MySQL dialect.", + "homepage": "https://github.com/phpmyadmin/sql-parser", + "keywords": [ + "analysis", + "lexer", + "parser", + "sql" + ], + "support": { + "issues": "https://github.com/phpmyadmin/sql-parser/issues", + "source": "https://github.com/phpmyadmin/sql-parser" + }, + "time": "2021-12-09T04:31:52+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.8.11", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "46e223dd68a620da18855c23046ddb00940b4014" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", + "reference": "46e223dd68a620da18855c23046ddb00940b4014", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.8.11" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2022-10-24T15:45:13+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.17", diff --git a/package.json b/package.json index ef5fb95d613..68314a37074 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "boring-avatars": "1.7.0", "chart.js": "3.9.1", "classnames": "2.3.2", - "codemirror": "5.57.0", "copy-to-clipboard": "3.3.2", "date-fns": "2.29.3", "debounce": "1.2.1", @@ -89,7 +88,6 @@ "@testing-library/dom": "8.19.0", "@testing-library/react": "13.4.0", "@testing-library/user-event": "14.4.3", - "@types/codemirror": "0.0.109", "@types/debounce": "1.2.1", "@types/events": "3.0.0", "@types/node": "18.11.9", diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000000..e12e72eb50a --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,35 @@ +includes: + - ./vendor/nunomaduro/larastan/extension.neon + +parameters: + + paths: + - app/ + + # Level 9 is the highest level + level: 4 + + ignoreErrors: + # Ignore dynamic methods from 3rd Party Vendor + - '#Call to an undefined method Prologue\\Alerts\\AlertsMessageBag::(success|info|warning|danger)\(\)#' + + # Ignore repository interface missing methods + - '#Call to an undefined method Pterodactyl\\Repositories\\Wings\\DaemonRepository::(\w+)\(\)#' + + # Ignore magic spatie calls + - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::allowed(\w+)\(\)#' + + # This should be replaced with resources instead of a magic transformer factory, robots in disguise + - '#Method Pterodactyl\\Http\\Controllers\\Api\\Client\\ClientApiController::getTransformer\(\) should return T#' + + excludePaths: + - app/Repositories + + # Bug in Laravel Framework #44807 + - app/Console/Commands/Overrides/UpCommand.php + + # More magic spatie to be replaced + - app/Extensions/Spatie/Fractalistic/Fractal.php + +# +# checkMissingIterableValueType: false diff --git a/resources/scripts/components/elements/CodemirrorEditor.tsx b/resources/scripts/components/elements/CodemirrorEditor.tsx deleted file mode 100644 index 48948ad74bb..00000000000 --- a/resources/scripts/components/elements/CodemirrorEditor.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import CodeMirror from 'codemirror'; -import type { CSSProperties } from 'react'; -import { useCallback, useEffect, useState } from 'react'; -import styled from 'styled-components'; -import tw from 'twin.macro'; - -import modes from '@/modes'; - -require('codemirror/lib/codemirror.css'); -require('codemirror/theme/ayu-mirage.css'); -require('codemirror/addon/edit/closebrackets'); -require('codemirror/addon/edit/closetag'); -require('codemirror/addon/edit/matchbrackets'); -require('codemirror/addon/edit/matchtags'); -require('codemirror/addon/edit/trailingspace'); -require('codemirror/addon/fold/foldcode'); -require('codemirror/addon/fold/foldgutter.css'); -require('codemirror/addon/fold/foldgutter'); -require('codemirror/addon/fold/brace-fold'); -require('codemirror/addon/fold/comment-fold'); -require('codemirror/addon/fold/indent-fold'); -require('codemirror/addon/fold/markdown-fold'); -require('codemirror/addon/fold/xml-fold'); -require('codemirror/addon/hint/css-hint'); -require('codemirror/addon/hint/html-hint'); -require('codemirror/addon/hint/javascript-hint'); -require('codemirror/addon/hint/show-hint.css'); -require('codemirror/addon/hint/show-hint'); -require('codemirror/addon/hint/sql-hint'); -require('codemirror/addon/hint/xml-hint'); -require('codemirror/addon/mode/simple'); -require('codemirror/addon/dialog/dialog.css'); -require('codemirror/addon/dialog/dialog'); -require('codemirror/addon/scroll/annotatescrollbar'); -require('codemirror/addon/scroll/scrollpastend'); -require('codemirror/addon/scroll/simplescrollbars.css'); -require('codemirror/addon/scroll/simplescrollbars'); -require('codemirror/addon/search/jump-to-line'); -require('codemirror/addon/search/match-highlighter'); -require('codemirror/addon/search/matchesonscrollbar.css'); -require('codemirror/addon/search/matchesonscrollbar'); -require('codemirror/addon/search/search'); -require('codemirror/addon/search/searchcursor'); - -require('codemirror/mode/brainfuck/brainfuck'); -require('codemirror/mode/clike/clike'); -require('codemirror/mode/css/css'); -require('codemirror/mode/dart/dart'); -require('codemirror/mode/diff/diff'); -require('codemirror/mode/dockerfile/dockerfile'); -require('codemirror/mode/erlang/erlang'); -require('codemirror/mode/gfm/gfm'); -require('codemirror/mode/go/go'); -require('codemirror/mode/handlebars/handlebars'); -require('codemirror/mode/htmlembedded/htmlembedded'); -require('codemirror/mode/htmlmixed/htmlmixed'); -require('codemirror/mode/http/http'); -require('codemirror/mode/javascript/javascript'); -require('codemirror/mode/jsx/jsx'); -require('codemirror/mode/julia/julia'); -require('codemirror/mode/lua/lua'); -require('codemirror/mode/markdown/markdown'); -require('codemirror/mode/nginx/nginx'); -require('codemirror/mode/perl/perl'); -require('codemirror/mode/php/php'); -require('codemirror/mode/properties/properties'); -require('codemirror/mode/protobuf/protobuf'); -require('codemirror/mode/pug/pug'); -require('codemirror/mode/python/python'); -require('codemirror/mode/rpm/rpm'); -require('codemirror/mode/ruby/ruby'); -require('codemirror/mode/rust/rust'); -require('codemirror/mode/sass/sass'); -require('codemirror/mode/shell/shell'); -require('codemirror/mode/smarty/smarty'); -require('codemirror/mode/sql/sql'); -require('codemirror/mode/swift/swift'); -require('codemirror/mode/toml/toml'); -require('codemirror/mode/twig/twig'); -require('codemirror/mode/vue/vue'); -require('codemirror/mode/xml/xml'); -require('codemirror/mode/yaml/yaml'); - -const EditorContainer = styled.div` - min-height: 16rem; - height: calc(100vh - 20rem); - ${tw`relative`}; - - > div { - ${tw`rounded h-full`}; - } - - .CodeMirror { - font-size: 12px; - line-height: 1.375rem; - } - - .CodeMirror-linenumber { - padding: 1px 12px 0 12px !important; - } - - .CodeMirror-foldmarker { - color: #cbccc6; - text-shadow: none; - margin-left: 0.25rem; - margin-right: 0.25rem; - } -`; - -export interface Props { - style?: CSSProperties; - initialContent?: string; - mode: string; - filename?: string; - onModeChanged: (mode: string) => void; - fetchContent: (callback: () => Promise) => void; - onContentSaved: () => void; -} - -const findModeByFilename = (filename: string) => { - for (let i = 0; i < modes.length; i++) { - const info = modes[i]; - - if (info?.file !== undefined && info.file.test(filename)) { - return info; - } - } - - const dot = filename.lastIndexOf('.'); - const ext = dot > -1 && filename.substring(dot + 1, filename.length); - - if (ext) { - for (let i = 0; i < modes.length; i++) { - const info = modes[i]; - if (info?.ext !== undefined) { - for (let j = 0; j < info.ext.length; j++) { - if (info.ext[j] === ext) { - return info; - } - } - } - } - } - - return undefined; -}; - -export default ({ style, initialContent, filename, mode, fetchContent, onContentSaved, onModeChanged }: Props) => { - const [editor, setEditor] = useState(); - - const ref = useCallback<(_?: unknown) => void>(node => { - if (node === undefined) { - return; - } - - const e = CodeMirror.fromTextArea(node as HTMLTextAreaElement, { - mode: 'text/plain', - theme: 'ayu-mirage', - indentUnit: 4, - smartIndent: true, - tabSize: 4, - indentWithTabs: false, - lineWrapping: true, - lineNumbers: true, - fixedGutter: true, - scrollbarStyle: 'overlay', - coverGutterNextToScrollbar: false, - readOnly: false, - showCursorWhenSelecting: false, - autofocus: false, - spellcheck: true, - autocorrect: false, - autocapitalize: false, - lint: false, - // @ts-expect-error this property is actually used, the d.ts file for CodeMirror is incorrect. - autoCloseBrackets: true, - matchBrackets: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - }); - - setEditor(e); - }, []); - - useEffect(() => { - if (filename === undefined) { - return; - } - - onModeChanged(findModeByFilename(filename)?.mime || 'text/plain'); - }, [filename]); - - useEffect(() => { - editor && editor.setOption('mode', mode); - }, [editor, mode]); - - useEffect(() => { - editor && editor.setValue(initialContent || ''); - }, [editor, initialContent]); - - useEffect(() => { - if (!editor) { - fetchContent(() => Promise.reject(new Error('no editor session has been configured'))); - return; - } - - editor.addKeyMap({ - 'Ctrl-S': () => onContentSaved(), - 'Cmd-S': () => onContentSaved(), - }); - - fetchContent(() => Promise.resolve(editor.getValue())); - }, [editor, fetchContent, onContentSaved]); - - return ( - -