diff --git a/.env.example b/.env.example index dd1275da0..e0e26c010 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ AUDITING_ENABLED=true APP_URL=http://localhost ASSET_URL=null APP_KEY= +APP_LOCAL_IPS= # audio AUDIO_DISK_DEFAULT=audios_local @@ -84,7 +85,7 @@ REDIS_CACHE_DB=1 # dump DUMP_DISK=dumps_local DUMP_DISK_ROOT= -DUMP_URL=http://localhost +DUMP_URL="${APP_URL}" DUMP_PATH=/dump # external profile @@ -168,12 +169,14 @@ SCRIPT_DISABLE_ASSERTS= SCRIPT_VISIBILITY= # fortify -FORTIFY_HOME=http://localhost +FORTIFY_HOME="${APP_URL}" FORTIFY_PATH= -FORTIFY_URL=http://localhost +FORTIFY_URL="${APP_URL}" # graphql -GRAPHQL_URL= +GRAPHIQL_URL="${GRAPHQL_URL}/graphiql" +GRAPHIQL_GRAPHQL_ENDPOINT="${GRAPHQL_URL}" +GRAPHQL_URL="${APP_URL}" GRAPHQL_PATH=/graphql GRAPHIQL_ENABLED=true @@ -288,21 +291,21 @@ VIDEO_DISK_DEFAULT=videos_local VIDEO_DISKS=videos_local VIDEO_DISK_ROOT= VIDEO_PATH=/video -VIDEO_URL= +VIDEO_URL="${APP_URL}" VIDEO_RATE_LIMITER=90 VIDEO_STREAMING_METHOD=response VIDEO_NGINX_REDIRECT= SCRIPT_DISK=scripts_local SCRIPT_DISK_ROOT= -SCRIPT_URL= +SCRIPT_URL="${APP_URL}" SCRIPT_PATH=/videoscript # web -WEB_URL=http://localhost +WEB_URL="${APP_URL}:3000" WEB_PATH= # wiki -WIKI_LOGIN=http://localhost/login -WIKI_RESET_PASSWORD=http://localhost/reset-password -WIKI_EXTERNAL_PROFILE=http://localhost/external +WIKI_LOGIN="h${APP_URL}:3000/login" +WIKI_RESET_PASSWORD="${APP_URL}:3000/reset-password" +WIKI_EXTERNAL_PROFILE="${APP_URL}:3000/external" diff --git a/.env.example-sail b/.env.example-sail index 17aa5415b..920a8d96e 100644 --- a/.env.example-sail +++ b/.env.example-sail @@ -12,6 +12,7 @@ AUDITING_ENABLED=true APP_URL=http://localhost ASSET_URL=null APP_KEY= +APP_LOCAL_IPS= # audio AUDIO_DISK_DEFAULT=audios_local @@ -85,7 +86,7 @@ REDIS_CACHE_DB=1 # dump DUMP_DISK=dumps DUMP_DISK_ROOT= -DUMP_URL=http://localhost +DUMP_URL="${APP_URL}" DUMP_PATH=/dump # ffmpeg @@ -166,12 +167,14 @@ SCRIPT_DISABLE_ASSERTS= SCRIPT_VISIBILITY= # fortify -FORTIFY_HOME=http://localhost:3000 +FORTIFY_HOME="${APP_URL}":3000 FORTIFY_PATH= -FORTIFY_URL=http://localhost +FORTIFY_URL="${APP_URL}" # graphql -GRAPHQL_URL= +GRAPHIQL_URL="${GRAPHQL_URL}/graphiql" +GRAPHIQL_GRAPHQL_ENDPOINT="${GRAPHQL_URL}" +GRAPHQL_URL="${APP_URL}" GRAPHQL_PATH=/graphql GRAPHIQL_ENABLED=true @@ -236,7 +239,7 @@ ELASTIC_HOST=http://elasticsearch:9200 ELASTIC_SCOUT_DRIVER_REFRESH_DOCUMENTS=false ELASTIC_MIGRATIONS_TABLE=elastic_migrations -TYPESENSE_HOST=localhost +TYPESENSE_HOST=typesense TYPESENSE_PORT=8108 TYPESENSE_PROTOCOL=http TYPESENSE_API_KEY= @@ -286,21 +289,21 @@ VIDEO_DISK_DEFAULT=videos_local VIDEO_DISKS=videos_local VIDEO_DISK_ROOT= VIDEO_PATH=/video -VIDEO_URL= +VIDEO_URL="${APP_URL}" VIDEO_RATE_LIMITER=90 VIDEO_STREAMING_METHOD=response VIDEO_NGINX_REDIRECT= SCRIPT_DISK=scripts_local SCRIPT_DISK_ROOT= -SCRIPT_URL= +SCRIPT_URL="${APP_URL}" SCRIPT_PATH=/videoscript # web -WEB_URL=http://localhost +WEB_URL="${APP_URL}:3000" WEB_PATH= # wiki -WIKI_LOGIN=http://localhost/login -WIKI_RESET_PASSWORD=http://localhost/reset-password -WIKI_EXTERNAL_PROFILE=http://localhost/external \ No newline at end of file +WIKI_LOGIN="${APP_URL}:3000/login" +WIKI_RESET_PASSWORD="${APP_URL}:3000/reset-password" +WIKI_EXTERNAL_PROFILE="${APP_URL}:3000/external" \ No newline at end of file diff --git a/app/Http/Middleware/Api/EnabledOnlyOnLocalhost.php b/app/Http/Middleware/Api/EnabledOnlyOnLocalhost.php index a4d26d4d9..da1ecc6fb 100644 --- a/app/Http/Middleware/Api/EnabledOnlyOnLocalhost.php +++ b/app/Http/Middleware/Api/EnabledOnlyOnLocalhost.php @@ -7,6 +7,7 @@ namespace App\Http\Middleware\Api; use App\Constants\FeatureConstants; use Closure; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Config; use Laravel\Pennant\Feature; class EnabledOnlyOnLocalhost @@ -17,8 +18,7 @@ class EnabledOnlyOnLocalhost public function handle(Request $request, Closure $next): mixed { if (Feature::for(null)->active(FeatureConstants::ENABLED_ONLY_ON_LOCALHOST)) { - $ip = $request->ip(); - abort_if($ip !== '127.0.0.1', 403, 'Route only available for localhost'); + abort_if(!in_array($request->ip(), Config::array('app.local_ips'), true), 403, 'Route only available for localhost'); } return $next($request); diff --git a/app/Http/Middleware/GraphQL/MaxCount.php b/app/Http/Middleware/GraphQL/MaxCount.php index e3d32dea1..f8e120a67 100644 --- a/app/Http/Middleware/GraphQL/MaxCount.php +++ b/app/Http/Middleware/GraphQL/MaxCount.php @@ -29,6 +29,6 @@ class MaxCount */ private function isLocal(Request $request): bool { - return $request->ip() === '127.0.0.1'; + return in_array($request->ip(), Config::array('app.local_ips'), true); } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index cb464df85..4f03110b0 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -33,16 +33,17 @@ class RouteServiceProvider extends ServiceProvider RateLimiter::for('api', function (Request $request) { $user = $request->user(); $ip = $request->ip(); + $isLocal = in_array($ip, Config::array('app.local_ips'), true); $forwardedIp = $request->header('x-forwarded-ip'); // (If request is from client and no forwarded ip) or (the user logged in has permission to bypass API rate limiting) /** @phpstan-ignore-next-line */ - if (($ip === '127.0.0.1' && ! $forwardedIp) || ($user instanceof User && $user->can(SpecialPermission::BYPASS_API_RATE_LIMITER->value))) { + if (($isLocal && ! $forwardedIp) || ($user instanceof User && $user->can(SpecialPermission::BYPASS_API_RATE_LIMITER->value))) { return Limit::none(); } // Check if request is from client to prevent users from using forwarded ip - if ($ip === '127.0.0.1' && $forwardedIp) { + if ($isLocal && $forwardedIp) { $ip = $forwardedIp; } @@ -52,16 +53,17 @@ class RouteServiceProvider extends ServiceProvider RateLimiter::for('graphql', function (Request $request) { $user = $request->user(); $ip = $request->ip(); + $isLocal = in_array($ip, Config::array('app.local_ips'), true); $forwardedIp = $request->header('x-forwarded-ip'); // (If request is from client and no forwarded ip) or (the user logged in has permission to bypass GraphQL rate limiting) /** @phpstan-ignore-next-line */ - if (($ip === '127.0.0.1' && ! $forwardedIp) || ($user instanceof User && $user->can(SpecialPermission::BYPASS_GRAPHQL_RATE_LIMITER->value))) { + if (($isLocal && ! $forwardedIp) || ($user instanceof User && $user->can(SpecialPermission::BYPASS_GRAPHQL_RATE_LIMITER->value))) { return Limit::none(); } // Check if request is from client to prevent users from using forwarded ip - if ($ip === '127.0.0.1' && $forwardedIp) { + if ($isLocal && $forwardedIp) { $ip = $forwardedIp; } diff --git a/config/app.php b/config/app.php index 44a9287e3..5245094b2 100644 --- a/config/app.php +++ b/config/app.php @@ -251,4 +251,9 @@ return [ // ... ])->toArray(), + 'local_ips' => [ + '127.0.0.1', + '172.19.0.1', + ...explode(',', env('APP_LOCAL_IPS', '')), + ], ]; diff --git a/config/graphiql.php b/config/graphiql.php index c8221afc4..7292c6c58 100644 --- a/config/graphiql.php +++ b/config/graphiql.php @@ -24,7 +24,7 @@ return [ 'name' => 'graphiql', 'middleware' => ['web'], 'prefix' => env('GRAPHQL_PREFIX', null), - 'domain' => env('GRAPHQL_URL', env('APP_URL')), + 'domain' => env('GRAPHIQL_URL'), /* |-------------------------------------------------------------------------- @@ -37,7 +37,7 @@ return [ | */ - 'endpoint' => env('GRAPHQL_URL', env('APP_URL')).'/'.env('GRAPHQL_PATH'), + 'endpoint' => env('GRAPHIQL_GRAPHQL_ENDPOINT', 'GRAPHQL_URL').'/'.env('GRAPHQL_PATH', ''), /* |-------------------------------------------------------------------------- diff --git a/config/scout.php b/config/scout.php index 9d44fe5b3..86a9a7e09 100644 --- a/config/scout.php +++ b/config/scout.php @@ -556,6 +556,7 @@ return [ [ 'name' => 'entries', 'type' => 'object[]', + 'optional' => true, ], ], ], diff --git a/docker-compose.yml b/docker-compose.yml index ffa0cb65e..c8b79f70d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,18 @@ services: laravel.test: build: - context: ./docker/8.4 + context: ./vendor/laravel/sail/runtimes/8.5 dockerfile: Dockerfile args: - WWWGROUP: '${WWWGROUP}' - image: sail-8.4/app + WWWGROUP: '${WWWGROUP:-1000}' + image: sail-8.5/app extra_hosts: - 'host.docker.internal:host-gateway' ports: - '${APP_PORT:-80}:80' - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' environment: - WWWUSER: '${WWWUSER}' + WWWUSER: '${WWWUSER:-1000}' LARAVEL_SAIL: 1 XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' @@ -26,7 +26,7 @@ services: - redis - mailpit mysql: - image: 'mysql/mysql-server:8.0' + image: 'mysql:8.4' ports: - '${FORWARD_DB_PORT:-3306}:3306' environment: @@ -36,6 +36,7 @@ services: MYSQL_USER: '${DB_USERNAME}' MYSQL_PASSWORD: '${DB_PASSWORD}' MYSQL_ALLOW_EMPTY_PASSWORD: 1 + MYSQL_EXTRA_OPTIONS: '${MYSQL_EXTRA_OPTIONS:-}' volumes: - 'sail-mysql:/var/lib/mysql' - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' @@ -72,17 +73,52 @@ services: networks: - sail elasticsearch: - image: 'elasticsearch:8.12.2' + image: 'elasticsearch:9.4.0' + profiles: + - elastic ports: - '9200:9200' - '9300:9300' environment: - discovery.type: 'single-node' + discovery.type: single-node xpack.security.enabled: 'false' volumes: - 'sail-elasticsearch:/usr/share/elasticsearch/data' networks: - sail + typesense: + image: 'typesense/typesense:27.1' + ports: + - '${FORWARD_TYPESENSE_PORT:-8108}:8108' + environment: + TYPESENSE_DATA_DIR: '${TYPESENSE_DATA_DIR:-/typesense-data}' + TYPESENSE_API_KEY: '${TYPESENSE_API_KEY:-xyz}' + TYPESENSE_ENABLE_CORS: '${TYPESENSE_ENABLE_CORS:-true}' + volumes: + - 'sail-typesense:/typesense-data' + networks: + - sail + healthcheck: + test: + - CMD + - bash + - '-c' + - 'exec 3<>/dev/tcp/localhost/8108 && printf ''GET /health HTTP/1.1\r\nConnection: close\r\n\r\n'' >&3 && head -n1 <&3 | grep ''200'' && exec 3>&-' + retries: 5 + timeout: 7s + phpmyadmin: + image: phpmyadmin/phpmyadmin + profiles: + - local + ports: + - '8080:80' + environment: + PMA_HOST: mysql + PMA_PORT: 3306 + depends_on: + - mysql + networks: + - sail networks: sail: driver: bridge @@ -93,3 +129,5 @@ volumes: driver: local sail-elasticsearch: driver: local + sail-typesense: + driver: local diff --git a/docker/8.4/Dockerfile b/docker/8.4/Dockerfile deleted file mode 100644 index cb0fbdc10..000000000 --- a/docker/8.4/Dockerfile +++ /dev/null @@ -1,71 +0,0 @@ -FROM ubuntu:24.04 - -LABEL maintainer="Taylor Otwell" - -ARG WWWGROUP -ARG NODE_VERSION=22 -ARG MYSQL_CLIENT="mysql-client" -ARG POSTGRES_VERSION=17 - -WORKDIR /var/www/html - -ENV DEBIAN_FRONTEND=noninteractive -ENV TZ=UTC -ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" -ENV SUPERVISOR_PHP_USER="sail" - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ - echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ - echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom - -RUN apt-get update && apt-get upgrade -y \ - && mkdir -p /etc/apt/keyrings \ - && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ - && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ - && apt-get update \ - && apt-get install -y php8.4-cli php8.4-dev \ - php8.4-pgsql php8.4-sqlite3 php8.4-gd \ - php8.4-curl php8.4-mongodb \ - php8.4-imap php8.4-mysql php8.4-mbstring \ - php8.4-xml php8.4-zip php8.4-bcmath php8.4-soap \ - php8.4-intl php8.4-readline \ - php8.4-ldap \ - php8.4-msgpack php8.4-igbinary php8.4-redis php8.4-swoole \ - php8.4-memcached php8.4-pcov php8.4-imagick php8.4-xdebug \ - && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ - && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ - && apt-get update \ - && apt-get install -y nodejs \ - && npm install -g npm \ - && npm install -g pnpm \ - && npm install -g bun \ - && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ - && apt-get update \ - && apt-get install -y yarn \ - && apt-get install -y $MYSQL_CLIENT \ - && apt-get install -y postgresql-client-$POSTGRES_VERSION \ - && apt-get -y autoremove \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.4 - -RUN userdel -r ubuntu -RUN groupadd --force -g $WWWGROUP sail -RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail - -COPY start-container /usr/local/bin/start-container -COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf -COPY php.ini /etc/php/8.4/cli/conf.d/99-sail.ini -RUN chmod +x /usr/local/bin/start-container - -EXPOSE 80/tcp - -ENTRYPOINT ["start-container"] diff --git a/docker/8.4/php.ini b/docker/8.4/php.ini deleted file mode 100644 index 01f2236b0..000000000 --- a/docker/8.4/php.ini +++ /dev/null @@ -1,8 +0,0 @@ -[PHP] -post_max_size = 100M -upload_max_filesize = 100M -variables_order = EGPCS -pcov.directory = . - -[xdebug] -xdebug.mode=${XDEBUG_MODE} diff --git a/docker/8.4/start-container b/docker/8.4/start-container deleted file mode 100644 index 40c55dfe3..000000000 --- a/docker/8.4/start-container +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then - echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." - exit 1 -fi - -if [ ! -z "$WWWUSER" ]; then - usermod -u $WWWUSER sail -fi - -if [ ! -d /.composer ]; then - mkdir /.composer -fi - -chmod -R ugo+rw /.composer - -if [ $# -gt 0 ]; then - if [ "$SUPERVISOR_PHP_USER" = "root" ]; then - exec "$@" - else - exec gosu $WWWUSER "$@" - fi -else - exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf -fi diff --git a/docker/8.4/supervisord.conf b/docker/8.4/supervisord.conf deleted file mode 100644 index 656da8a9f..000000000 --- a/docker/8.4/supervisord.conf +++ /dev/null @@ -1,14 +0,0 @@ -[supervisord] -nodaemon=true -user=root -logfile=/var/log/supervisor/supervisord.log -pidfile=/var/run/supervisord.pid - -[program:php] -command=%(ENV_SUPERVISOR_PHP_COMMAND)s -user=%(ENV_SUPERVISOR_PHP_USER)s -environment=LARAVEL_SAIL="1" -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 diff --git a/phpunit.xml b/phpunit.xml index 95c522ea5..534c991bc 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,8 +21,7 @@ - - +