Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
924d389488 Bump the major-updates group across 1 directory with 12 updates
Bumps the major-updates group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@tanstack/vue-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/vue-query-devtools) | `5.91.0` | `6.1.24` |
| [lucide-vue-next](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-vue-next) | `0.487.0` | `1.0.0` |
| [tailwind-merge](https://github.com/dcastil/tailwind-merge) | `2.6.1` | `3.5.0` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.2` | `10.0.1` |
| [@inertiajs/vue3](https://github.com/inertiajs/inertia/tree/HEAD/packages/vue3) | `2.3.13` | `3.0.3` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.7` | `25.6.0` |
| [laravel-vite-plugin](https://github.com/laravel/vite-plugin) | `2.1.0` | `3.0.1` |
| [postcss-import](https://github.com/postcss/postcss-import) | `15.1.0` | `16.1.1` |
| [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/HEAD/plugins/postcss-nesting) | `12.1.5` | `14.0.0` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `3.4.19` | `4.2.4` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.9.3` | `6.0.3` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.3.1` | `8.0.10` |



Updates `@tanstack/vue-query-devtools` from 5.91.0 to 6.1.24
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/vue-query-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/vue-query-devtools@6.1.24/packages/vue-query-devtools)

Updates `lucide-vue-next` from 0.487.0 to 1.0.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.0.0/packages/lucide-vue-next)

Updates `tailwind-merge` from 2.6.1 to 3.5.0
- [Release notes](https://github.com/dcastil/tailwind-merge/releases)
- [Commits](https://github.com/dcastil/tailwind-merge/compare/v2.6.1...v3.5.0)

Updates `@eslint/js` from 9.39.2 to 10.0.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v10.0.1/packages/js)

Updates `@inertiajs/vue3` from 2.3.13 to 3.0.3
- [Release notes](https://github.com/inertiajs/inertia/releases)
- [Changelog](https://github.com/inertiajs/inertia/blob/3.x/CHANGELOG.md)
- [Commits](https://github.com/inertiajs/inertia/commits/v3.0.3/packages/vue3)

Updates `@types/node` from 22.19.7 to 25.6.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `laravel-vite-plugin` from 2.1.0 to 3.0.1
- [Release notes](https://github.com/laravel/vite-plugin/releases)
- [Changelog](https://github.com/laravel/vite-plugin/blob/3.x/CHANGELOG.md)
- [Upgrade guide](https://github.com/laravel/vite-plugin/blob/3.x/UPGRADE.md)
- [Commits](https://github.com/laravel/vite-plugin/compare/v2.1.0...v3.0.1)

Updates `postcss-import` from 15.1.0 to 16.1.1
- [Release notes](https://github.com/postcss/postcss-import/releases)
- [Changelog](https://github.com/postcss/postcss-import/blob/master/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss-import/compare/15.1.0...16.1.1)

Updates `postcss-nesting` from 12.1.5 to 14.0.0
- [Changelog](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-nesting/CHANGELOG.md)
- [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/plugins/postcss-nesting)

Updates `tailwindcss` from 3.4.19 to 4.2.4
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.4/packages/tailwindcss)

Updates `typescript` from 5.9.3 to 6.0.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.3)

Updates `vite` from 7.3.1 to 8.0.10
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.10/packages/vite)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 10.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@inertiajs/vue3"
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@tanstack/vue-query-devtools"
  dependency-version: 6.1.10
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: laravel-vite-plugin
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: lucide-vue-next
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: postcss-import
  dependency-version: 16.1.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: postcss-nesting
  dependency-version: 14.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: tailwind-merge
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: tailwindcss
  dependency-version: 4.2.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: typescript
  dependency-version: 6.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: vite
  dependency-version: 8.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 09:01:37 +00:00
11 changed files with 932 additions and 2118 deletions

View File

@@ -1,258 +0,0 @@
name: Image Smoke Tests
on:
pull_request:
paths:
- 'docker/prod/**'
- '.github/workflows/image-smoke-test.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
smoke:
name: Smoke (${{ matrix.mode }})
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
mode:
- default
- puid-pgid
- openshift
- drop-never
- diagnostic
- puid-mismatch-warning
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Copy .env template
run: |
cp .env.production .env
rm .env.production .env.ci .env.example
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, dom, fileinfo, pgsql
- name: Composer install
run: composer install --no-dev --no-ansi --no-interaction --prefer-dist --ignore-platform-reqs --classmap-authoritative
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: NPM ci
run: npm ci
- name: NPM build
run: npm run build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build smoke image
uses: docker/build-push-action@v6
with:
context: .
file: docker/prod/Dockerfile
build-args: |
DOCKER_FILES_BASE_PATH=docker/prod/
load: true
tags: solidtime-smoke:test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: "Smoke: default (image config + fresh deploy with empty bind mounts)"
if: matrix.mode == 'default'
run: |
echo "[smoke] image's default USER is root (entrypoint needs root to drop privs)"
user=$(docker inspect --format '{{.Config.User}}' solidtime-smoke:test)
if [ "$user" != "root" ]; then
echo "Expected 'root', got '$user'. The Dockerfile must end with USER root so the entrypoint can chown/usermod and drop privileges."
exit 1
fi
echo "[smoke] storage tree is group-0 owned (OpenShift / arbitrary-UID compat)"
group=$(docker run --rm --entrypoint stat solidtime-smoke:test -c '%g' /var/www/html/storage)
if [ "$group" != "0" ]; then
echo "Expected group 0, got '$group'. The Dockerfile must chgrp -R 0 storage bootstrap/cache so arbitrary-UID containers can write."
exit 1
fi
mkdir -p test-storage test-cache
docker run --rm \
-v "$(pwd)/test-storage:/var/www/html/storage" \
-v "$(pwd)/test-cache:/var/www/html/bootstrap/cache" \
solidtime-smoke:test \
sh -c '
set -e
echo "[smoke] framework subdirs exist"
test -d /var/www/html/storage/framework/cache/data
test -d /var/www/html/storage/framework/sessions
test -d /var/www/html/storage/framework/views
test -d /var/www/html/storage/framework/testing
test -d /var/www/html/storage/logs
test -d /var/www/html/storage/app/public
test -d /var/www/html/storage/app/private
test -d /var/www/html/bootstrap/cache
echo "[smoke] storage is writable"
touch /var/www/html/storage/framework/cache/data/test-file
echo "[smoke] running as octane (UID 1000)"
[ "$(id -u)" = "1000" ]
echo "[smoke] PASS"
'
- name: "Smoke: PUID/PGID remap"
if: matrix.mode == 'puid-pgid'
run: |
mkdir -p test-storage test-cache
sudo chown -R 1501:1501 test-storage test-cache
docker run --rm \
-e PUID=1501 -e PGID=1501 \
-v "$(pwd)/test-storage:/var/www/html/storage" \
-v "$(pwd)/test-cache:/var/www/html/bootstrap/cache" \
solidtime-smoke:test \
sh -c '
set -e
echo "[smoke] running as remapped UID/GID 1501"
[ "$(id -u)" = "1501" ]
[ "$(id -g)" = "1501" ]
echo "[smoke] storage is writable as 1501"
touch /var/www/html/storage/framework/cache/data/test-file
echo "[smoke] PASS"
'
- name: "Smoke: OpenShift / arbitrary UID + group 0"
if: matrix.mode == 'openshift'
run: |
mkdir -p test-storage test-cache
sudo chown -R 2000:0 test-storage test-cache
sudo chmod -R g+rwX test-storage test-cache
docker run --rm --user 2000:0 \
-v "$(pwd)/test-storage:/var/www/html/storage" \
-v "$(pwd)/test-cache:/var/www/html/bootstrap/cache" \
solidtime-smoke:test \
sh -c '
set -e
echo "[smoke] running as arbitrary UID 2000, group 0"
[ "$(id -u)" = "2000" ]
[ "$(id -g)" = "0" ]
echo "[smoke] storage is writable via group 0"
touch /var/www/html/storage/framework/cache/data/test-file
echo "[smoke] PASS"
'
- name: "Smoke: SOLIDTIME_DROP_PRIVILEGES=never (run as root)"
if: matrix.mode == 'drop-never'
run: |
mkdir -p test-storage test-cache
docker run --rm \
-e SOLIDTIME_DROP_PRIVILEGES=never \
-v "$(pwd)/test-storage:/var/www/html/storage" \
-v "$(pwd)/test-cache:/var/www/html/bootstrap/cache" \
solidtime-smoke:test \
sh -c '
set -e
echo "[smoke] running as root (privilege drop disabled)"
[ "$(id -u)" = "0" ]
echo "[smoke] bootstrap still ran"
test -d /var/www/html/storage/framework/cache/data
echo "[smoke] storage writable as root"
touch /var/www/html/storage/framework/cache/data/test-file
echo "[smoke] PASS"
'
- name: "Smoke: PUID set + started non-root prints a warning but continues"
if: matrix.mode == 'puid-mismatch-warning'
run: |
mkdir -p test-storage test-cache
sudo chown -R 1500:1500 test-storage test-cache
set +e
docker run --rm \
--user 1500:1500 \
-e PUID=1500 -e PGID=1500 \
-v "$(pwd)/test-storage:/var/www/html/storage" \
-v "$(pwd)/test-cache:/var/www/html/bootstrap/cache" \
solidtime-smoke:test \
sh -c '
set -e
echo "[smoke] running as 1500 (user: directive wins)"
[ "$(id -u)" = "1500" ]
echo "[smoke] storage is writable as 1500"
touch /var/www/html/storage/framework/cache/data/test-file
echo "[smoke] container completed successfully"
' \
>stdout.log 2>stderr.log
exit_code=$?
set -e
echo "[smoke] exit code: $exit_code"
echo "--- stderr ---"
cat stderr.log
echo "--- end stderr ---"
if [ "$exit_code" -ne 0 ]; then
echo "Expected the entrypoint to continue (warning is non-fatal)."
exit 1
fi
for needle in "PUID/PGID is set but the container started as UID" "remove any 'user:' directive" "Continuing as UID"; do
if ! grep -q "$needle" stderr.log; then
echo "Missing warning fragment: $needle"
exit 1
fi
done
echo "[smoke] PASS"
- name: "Smoke: diagnostic error path (read-only storage mount)"
if: matrix.mode == 'diagnostic'
run: |
# Pre-create the full storage tree on the host so the entrypoint's
# bootstrap_storage_tree() is a no-op (mkdir -p on existing dirs
# returns 0 even on a read-only mount). The write test then fires
# against the RO mount and triggers our diagnostic.
mkdir -p test-storage/framework/cache/data \
test-storage/framework/sessions \
test-storage/framework/views \
test-storage/framework/testing \
test-storage/logs \
test-storage/app/public \
test-storage/app/private \
test-cache
set +e
docker run --rm \
-v "$(pwd)/test-storage:/var/www/html/storage:ro" \
-v "$(pwd)/test-cache:/var/www/html/bootstrap/cache:ro" \
solidtime-smoke:test \
true \
>stdout.log 2>stderr.log
exit_code=$?
set -e
echo "[smoke] exit code: $exit_code"
echo "--- stderr ---"
cat stderr.log
echo "--- end stderr ---"
if [ "$exit_code" -eq 0 ]; then
echo "Expected the entrypoint to exit non-zero on an unwritable storage mount."
exit 1
fi
for needle in "not writable" "PUID=" "permissions"; do
if ! grep -q "$needle" stderr.log; then
echo "Missing diagnostic fragment: $needle"
exit 1
fi
done
echo "[smoke] PASS"

View File

@@ -68,7 +68,6 @@ RUN apt-get update; \
wget \ wget \
vim \ vim \
git \ git \
gosu \
ncdu \ ncdu \
procps \ procps \
unzip \ unzip \
@@ -194,20 +193,9 @@ COPY --link --chown=${WWWUSER}:${WWWUSER} . .
#COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public #COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public
RUN mkdir -p \ RUN mkdir -p \
storage/framework/{sessions,views,cache/data,testing} \ storage/framework/{sessions,views,cache,testing} \
storage/logs \ storage/logs \
storage/app/public \ bootstrap/cache && chmod -R a+rw storage
storage/app/private \
bootstrap/cache && \
ln -s ../storage/app/public public/storage && \
chmod -R a+rw storage bootstrap/cache
# OpenShift / arbitrary-UID compatibility: group 0 (root group) gets read+write+execute
# on writable paths. Any UID can run the container if it joins the root group.
# https://docs.openshift.com/container-platform/latest/openshift_images/create-images.html
USER root
RUN chgrp -R 0 storage bootstrap/cache && \
chmod -R g+rwX storage bootstrap/cache
#RUN composer install \ #RUN composer install \
# --classmap-authoritative \ # --classmap-authoritative \

View File

@@ -1,6 +1,7 @@
[program:octane] [program:octane]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = php %(ENV_ROOT)s/artisan octane:frankenphp --host=0.0.0.0 --port=8000 --admin-port=2019 --caddyfile=%(ENV_ROOT)s/docker/prod/deployment/octane/FrankenPHP/Caddyfile command = php %(ENV_ROOT)s/artisan octane:frankenphp --host=0.0.0.0 --port=8000 --admin-port=2019 --caddyfile=%(ENV_ROOT)s/docker/prod/deployment/octane/FrankenPHP/Caddyfile
user = %(ENV_USER)s
priority = 1 priority = 1
autostart = true autostart = true
autorestart = true autorestart = true
@@ -13,6 +14,7 @@ stderr_logfile_maxbytes = 0
[program:horizon] [program:horizon]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = php %(ENV_ROOT)s/artisan horizon command = php %(ENV_ROOT)s/artisan horizon
user = %(ENV_USER)s
priority = 3 priority = 3
autostart = %(ENV_WITH_HORIZON)s autostart = %(ENV_WITH_HORIZON)s
autorestart = true autorestart = true
@@ -25,6 +27,7 @@ stopwaitsecs = 3600
[program:scheduler] [program:scheduler]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = supercronic -overlapping /etc/supercronic/laravel command = supercronic -overlapping /etc/supercronic/laravel
user = %(ENV_USER)s
autostart = %(ENV_WITH_SCHEDULER)s autostart = %(ENV_WITH_SCHEDULER)s
autorestart = true autorestart = true
stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log
@@ -35,6 +38,7 @@ stderr_logfile_maxbytes = 200MB
[program:clear-scheduler-cache] [program:clear-scheduler-cache]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = php %(ENV_ROOT)s/artisan schedule:clear-cache command = php %(ENV_ROOT)s/artisan schedule:clear-cache
user = %(ENV_USER)s
autostart = %(ENV_WITH_SCHEDULER)s autostart = %(ENV_WITH_SCHEDULER)s
autorestart = false autorestart = false
startsecs = 0 startsecs = 0
@@ -47,6 +51,7 @@ stderr_logfile_maxbytes = 200MB
[program:reverb] [program:reverb]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = php %(ENV_ROOT)s/artisan reverb:start command = php %(ENV_ROOT)s/artisan reverb:start
user = %(ENV_USER)s
priority = 2 priority = 2
autostart = %(ENV_WITH_REVERB)s autostart = %(ENV_WITH_REVERB)s
autorestart = true autorestart = true

View File

@@ -1,240 +1,6 @@
#!/bin/bash #!/usr/bin/env sh
set -e set -e
# ============================================================================
# Solidtime container entrypoint.
#
# Layout:
# 1. Storage tree bootstrap (idempotent, runs as any user)
# 2. UID/GID remap + chown (root only, controlled by PUID/PGID env vars)
# 3. Pre-flight write test (fails fast with a diagnosis message)
# 4. Privilege drop via gosu, then re-exec self as APP_USER
# 5. Original CONTAINER_MODE routing (runs as APP_USER)
#
# Env vars:
# PUID, PGID UID/GID for the application user. Defaults 1000:1000.
# Only takes effect when the container starts as root
# (which is the image's default — if you set a
# `user:` directive in compose, PUID/PGID are ignored
# and a startup warning is printed).
# SOLIDTIME_DROP_PRIVILEGES auto (default) | never
# auto: if started as root, drop privileges to APP_USER; otherwise just exec.
# never: never drop privileges. Run as whatever UID/GID was started.
# ============================================================================
APP_USER="octane"
APP_PATH="${ROOT:-/var/www/html}"
STORAGE_PATH="${APP_PATH}/storage"
CACHE_PATH="${APP_PATH}/bootstrap/cache"
DEFAULT_UID=1000
DEFAULT_GID=1000
TARGET_UID="${PUID:-${DEFAULT_UID}}"
TARGET_GID="${PGID:-${DEFAULT_GID}}"
DROP_PRIVS="${SOLIDTIME_DROP_PRIVILEGES:-auto}"
WRITABLE_PATHS=(
"${STORAGE_PATH}/framework/cache/data"
"${STORAGE_PATH}/framework/sessions"
"${STORAGE_PATH}/framework/views"
"${STORAGE_PATH}/framework/testing"
"${STORAGE_PATH}/logs"
"${STORAGE_PATH}/app/public"
"${STORAGE_PATH}/app/private"
"${CACHE_PATH}"
)
case "${DROP_PRIVS}" in
never) SHOULD_DROP=0 ;;
auto)
if [ "$(id -u)" = "0" ]; then
SHOULD_DROP=1
else
SHOULD_DROP=0
fi
;;
*)
echo "[entrypoint] ERROR: invalid SOLIDTIME_DROP_PRIVILEGES='${DROP_PRIVS}'" >&2
echo "[entrypoint] Valid values: auto (default), never" >&2
exit 1
;;
esac
# Warn if PUID/PGID are set but the container started non-root. PUID/PGID only
# take effect during the drop-privileges flow, which requires starting as root.
# A common cause is leaving `user:` in the compose file alongside PUID env vars.
if { [ -n "${PUID}" ] || [ -n "${PGID}" ]; } \
&& [ "$(id -u)" != "0" ] \
&& [ "${SOLIDTIME_PRIVILEGES_DROPPED:-0}" != "1" ]; then
cat >&2 <<EOF
[entrypoint] WARNING: PUID/PGID is set but the container started as UID $(id -u) (not root).
[entrypoint] WARNING: PUID/PGID only apply when the entrypoint runs as root and drops privileges.
[entrypoint] WARNING:
[entrypoint] WARNING: To use PUID/PGID: remove any 'user:' directive from your compose file.
[entrypoint] WARNING: To run as a fixed UID: remove PUID/PGID from your env.
[entrypoint] WARNING:
[entrypoint] WARNING: Continuing as UID $(id -u). See:
[entrypoint] WARNING: https://docs.solidtime.io/self-hosting/guides/permissions
EOF
fi
bootstrap_storage_tree() {
mkdir -p "${WRITABLE_PATHS[@]}" 2>/dev/null || return 1
}
# Proactive warning when the existing storage directory is owned by a non-default
# UID (typical on NAS systems where host users aren't UID 1000) and PUID/PGID
# aren't set. Without this nudge, the chown step silently re-owns the files to
# 1000:1000 and the user only discovers the mismatch later when host-side tools
# (backup, file browser, rsync) show unfamiliar ownership.
maybe_warn_ownership_mismatch() {
[ "${SHOULD_DROP}" = "1" ] || return 0
[ -n "${PUID}" ] && return 0
[ -n "${PGID}" ] && return 0
[ -d "${STORAGE_PATH}" ] || return 0
local owner_uid owner_gid
owner_uid="$(stat -c '%u' "${STORAGE_PATH}" 2>/dev/null)" || return 0
owner_gid="$(stat -c '%g' "${STORAGE_PATH}" 2>/dev/null)" || return 0
# Root-owned: probably freshly created by the entrypoint, will be chowned shortly.
[ "${owner_uid}" = "0" ] && return 0
# Already the target: nothing to warn about.
[ "${owner_uid}" = "${TARGET_UID}" ] && return 0
cat >&2 <<EOF
[entrypoint] NOTE: ${STORAGE_PATH} is owned by UID ${owner_uid}:${owner_gid},
[entrypoint] but the container is starting as UID ${TARGET_UID}:${TARGET_GID}.
[entrypoint] Files will be chowned to ${TARGET_UID}:${TARGET_GID} and may
[entrypoint] appear with an unfamiliar owner on the host.
[entrypoint]
[entrypoint] If you want the container to write as UID ${owner_uid} (common
[entrypoint] on Synology / TrueNAS / Unraid where host users aren't UID
[entrypoint] 1000), set in your env and restart:
[entrypoint]
[entrypoint] PUID=${owner_uid}
[entrypoint] PGID=${owner_gid}
[entrypoint]
[entrypoint] More: https://docs.solidtime.io/self-hosting/guides/permissions
EOF
}
print_write_test_failure() {
local owner
owner="$(stat -c '%u:%g' "${STORAGE_PATH}" 2>/dev/null || echo unknown)"
local runtime_uid
local runtime_gid
if [ "$(id -u)" = "0" ] && [ "${SHOULD_DROP}" = "1" ]; then
runtime_uid="${TARGET_UID}"
runtime_gid="${TARGET_GID}"
else
runtime_uid="$(id -u)"
runtime_gid="$(id -g)"
fi
local owner_uid
owner_uid="$(stat -c '%u' "${STORAGE_PATH}" 2>/dev/null || echo 1000)"
local owner_gid
owner_gid="$(stat -c '%g' "${STORAGE_PATH}" 2>/dev/null || echo 1000)"
cat >&2 <<EOF
============================================================
ERROR: Solidtime writable directories are not writable.
Diagnosis:
Container will run as: UID ${runtime_uid}, GID ${runtime_gid}
Storage directory owner: ${owner}
Likely cause: a bind-mounted host directory is owned by a different
user than the container's application user.
Fix on the host:
sudo chown -R ${runtime_uid}:${runtime_gid} <your-bind-mount-path>
Or set PUID/PGID to match the host directory owner:
PUID=${owner_uid}
PGID=${owner_gid}
To run intentionally as root, set:
SOLIDTIME_DROP_PRIVILEGES=never
For more help: https://docs.solidtime.io/self-hosting/guides/permissions
============================================================
EOF
}
write_test_as_user() {
local user="$1"
local script='
set -e
for dir in "$@"; do
test_file="${dir}/.solidtime-write-test"
touch "${test_file}"
rm -f "${test_file}"
done
'
if [ -n "${user}" ]; then
gosu "${user}" sh -c "${script}" sh "${WRITABLE_PATHS[@]}" 2>/dev/null
else
sh -c "${script}" sh "${WRITABLE_PATHS[@]}" 2>/dev/null
fi
}
# ----------------------------------------------------------------------------
# Root preamble: bootstrap, remap, chown, write-test, then drop and re-exec.
# ----------------------------------------------------------------------------
if [ "$(id -u)" = "0" ]; then
if ! bootstrap_storage_tree; then
echo "[entrypoint] ERROR: failed to create storage subdirectories at ${STORAGE_PATH}" >&2
exit 1
fi
if [ "${SHOULD_DROP}" = "1" ]; then
maybe_warn_ownership_mismatch
if [ "${TARGET_UID}" != "${DEFAULT_UID}" ] || [ "${TARGET_GID}" != "${DEFAULT_GID}" ]; then
echo "[entrypoint] Remapping ${APP_USER} to ${TARGET_UID}:${TARGET_GID}"
groupmod -o -g "${TARGET_GID}" "${APP_USER}"
usermod -o -u "${TARGET_UID}" "${APP_USER}"
fi
# Idempotent chown: only fix entries whose owner or group is wrong.
# On large storage volumes (lots of user uploads) this is dramatically
# faster than a blanket `chown -R` every restart. Pattern borrowed from
# docker-library/postgres and linuxserver.io's baseimage.
find "${STORAGE_PATH}" "${CACHE_PATH}" \
\( ! -user "${TARGET_UID}" -o ! -group "${TARGET_GID}" \) \
-exec chown "${TARGET_UID}:${TARGET_GID}" {} + 2>/dev/null || true
if ! write_test_as_user "${APP_USER}"; then
print_write_test_failure
exit 1
fi
exec gosu "${APP_USER}" env SOLIDTIME_PRIVILEGES_DROPPED=1 "$0" "$@"
fi
if ! write_test_as_user ""; then
print_write_test_failure
exit 1
fi
else
if ! bootstrap_storage_tree; then
echo "[entrypoint] WARNING: could not create some storage subdirectories at ${STORAGE_PATH} (will continue if existing tree is writable)" >&2
fi
if ! write_test_as_user ""; then
print_write_test_failure
exit 1
fi
fi
# ----------------------------------------------------------------------------
# Application: runs as APP_USER (or whatever non-root UID was started).
# ----------------------------------------------------------------------------
unset SOLIDTIME_PRIVILEGES_DROPPED
container_mode=${CONTAINER_MODE:-"http"} container_mode=${CONTAINER_MODE:-"http"}
octane_server=${OCTANE_SERVER} octane_server=${OCTANE_SERVER}
auto_db_migrate=${AUTO_DB_MIGRATE:-false} auto_db_migrate=${AUTO_DB_MIGRATE:-false}
@@ -242,16 +8,14 @@ auto_db_migrate=${AUTO_DB_MIGRATE:-false}
initialStuff() { initialStuff() {
echo "Container mode: $container_mode" echo "Container mode: $container_mode"
if [ "${auto_db_migrate}" = "true" ]; then if [ ${auto_db_migrate} = "true" ]; then
echo "Auto database migration enabled." echo "Auto database migration enabled."
php artisan migrate --isolated --force php artisan migrate --isolated --force
fi fi
if [ ! -L "${APP_PATH}/public/storage" ]; then php artisan storage:link; \
php artisan storage:link php artisan optimize:clear; \
fi php artisan optimize;
php artisan optimize:clear
php artisan optimize
} }
if [ "$1" != "" ]; then if [ "$1" != "" ]; then

View File

@@ -1,5 +1,6 @@
[supervisord] [supervisord]
nodaemon = true nodaemon = true
user = %(ENV_USER)s
logfile = /var/log/supervisor/supervisord.log logfile = /var/log/supervisor/supervisord.log
pidfile = /var/run/supervisord.pid pidfile = /var/run/supervisord.pid

View File

@@ -1,6 +1,7 @@
[program:horizon] [program:horizon]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = php %(ENV_ROOT)s/artisan horizon command = php %(ENV_ROOT)s/artisan horizon
user = %(ENV_USER)s
autostart = true autostart = true
autorestart = true autorestart = true
stdout_logfile = /dev/stdout stdout_logfile = /dev/stdout

View File

@@ -1,6 +1,7 @@
[program:reverb] [program:reverb]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = php %(ENV_ROOT)s/artisan reverb:start command = php %(ENV_ROOT)s/artisan reverb:start
user = %(ENV_USER)s
autostart = true autostart = true
autorestart = true autorestart = true
stdout_logfile = /dev/stdout stdout_logfile = /dev/stdout

View File

@@ -1,6 +1,7 @@
[program:scheduler] [program:scheduler]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = supercronic -overlapping /etc/supercronic/laravel command = supercronic -overlapping /etc/supercronic/laravel
user = %(ENV_USER)s
autostart = true autostart = true
autorestart = true autorestart = true
stdout_logfile = /dev/stdout stdout_logfile = /dev/stdout
@@ -11,6 +12,7 @@ stderr_logfile_maxbytes = 0
[program:clear-scheduler-cache] [program:clear-scheduler-cache]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = php %(ENV_ROOT)s/artisan schedule:clear-cache command = php %(ENV_ROOT)s/artisan schedule:clear-cache
user = %(ENV_USER)s
autostart = true autostart = true
autorestart = false autorestart = false
startsecs = 0 startsecs = 0

View File

@@ -1,6 +1,7 @@
[program:worker] [program:worker]
process_name = %(program_name)s_%(process_num)s process_name = %(program_name)s_%(process_num)s
command = %(ENV_WORKER_COMMAND)s command = %(ENV_WORKER_COMMAND)s
user = %(ENV_USER)s
autostart = true autostart = true
autorestart = true autorestart = true
stdout_logfile = /dev/stdout stdout_logfile = /dev/stdout

2489
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,26 +19,26 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.19.0", "@eslint/js": "^10.0.1",
"@inertiajs/vue3": "^2.0.0", "@inertiajs/vue3": "^3.0.3",
"@playwright/test": "^1.41.1", "@playwright/test": "^1.41.1",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/chroma-js": "^3.1.0", "@types/chroma-js": "^3.1.0",
"@types/node": "^22.10.10", "@types/node": "^25.6.0",
"@vitejs/plugin-vue": "^6.0.3", "@vitejs/plugin-vue": "^6.0.3",
"@vue/tsconfig": "^0.8.0", "@vue/tsconfig": "^0.8.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^1.6.4", "axios": "^1.6.4",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"laravel-vite-plugin": "^2.1.0", "laravel-vite-plugin": "^3.0.1",
"openapi-zod-client": "^1.16.2", "openapi-zod-client": "^1.16.2",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-import": "^15.1.0", "postcss-import": "^16.1.1",
"postcss-nesting": "^12.1.5", "postcss-nesting": "^14.0.0",
"tailwindcss": "^3.4.13", "tailwindcss": "^4.2.4",
"typescript": "^5.7.3", "typescript": "^6.0.3",
"vite": "^7.0.0", "vite": "^8.0.10",
"vite-plugin-checker": "^0.12.0", "vite-plugin-checker": "^0.12.0",
"vue": "^3.5.0", "vue": "^3.5.0",
"vue-tsc": "^3.0.0" "vue-tsc": "^3.0.0"
@@ -51,7 +51,7 @@
"@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/container-queries": "^0.1.1",
"@tanstack/vue-form": "^1.3.1", "@tanstack/vue-form": "^1.3.1",
"@tanstack/vue-query": "^5.56.2", "@tanstack/vue-query": "^5.56.2",
"@tanstack/vue-query-devtools": "^5.58.0", "@tanstack/vue-query-devtools": "^6.1.24",
"@tanstack/vue-table": "^8.21.2", "@tanstack/vue-table": "^8.21.2",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.3.0", "@vue/eslint-config-typescript": "^14.3.0",
@@ -64,12 +64,12 @@
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"echarts": "^6.0.0", "echarts": "^6.0.0",
"focus-trap": "^8.0.0", "focus-trap": "^8.0.0",
"lucide-vue-next": "^0.487.0", "lucide-vue-next": "^1.0.0",
"parse-duration": "^2.0.1", "parse-duration": "^2.0.1",
"pinia": "^3.0.0", "pinia": "^3.0.0",
"radix-vue": "^1.9.6", "radix-vue": "^1.9.6",
"reka-ui": "^2.8.2", "reka-ui": "^2.8.2",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vue-echarts": "^8.0.0", "vue-echarts": "^8.0.0",
"zod": "^3.23.8" "zod": "^3.23.8"