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 @@
-
-
+