Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .docker/php/shell/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ if [ "${fresh_install}" = true ]; then
akaunting_fix_ownership
fi

# Repair root-owned Laravel cache directories on every start. A previous
# administrative command must not prevent the web user from writing cache.
akaunting_repair_runtime_cache_permissions

exec php-fpm
13 changes: 13 additions & 0 deletions .docker/php/shell/lib/akaunting.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ akaunting_run_upgrade() {
php artisan migrate --force
}

# A one-off root Artisan call can create 0755 cache directories owned by root,
# while PHP-FPM writes as www-data. Restore only runtime cache ownership on
# startup. Do not traverse the full application volume or alter financial files.
akaunting_repair_runtime_cache_permissions() {
local cache_dir="${APP_ROOT}/storage/framework/cache"
if [ "$(id -u)" -ne 0 ] || [ ! -d "${cache_dir}" ]; then
return 0
fi

find "${cache_dir}" -xdev \( ! -user www-data -o ! -group www-data \) \
-exec chown www-data:www-data {} +
}

akaunting_fix_ownership() {
chown -R www-data:www-data "${APP_ROOT}"
}
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ The update command checks out the selected Akaunting tag, applies the repository

If the patch no longer applies to a new upstream release, the update stops instead of continuing with a partially patched installation.

Use the `www-data` wrapper for one-off Artisan commands (do not invoke
`php artisan` as root inside the PHP container):

```bash
bash scripts/artisan.sh about
bash scripts/artisan.sh route:list --path=api
```

The PHP-FPM worker writes Laravel's cache as `www-data`. Root-run Artisan
commands may create root-owned `0755` cache subdirectories, causing HTTP 500.
The container corrects ownership of **only runtime cache paths** on startup,
without recursively rewriting the application or financial data.


## Local development

The repository intentionally does **not** version `docker-compose.override.yml`.
Expand Down
11 changes: 11 additions & 0 deletions scripts/artisan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail

# Always run Artisan as the PHP-FPM user. Invoking it as root can create
# root-owned Laravel cache directories, making ordinary web requests fail 500.
if [ "$#" -eq 0 ]; then
echo "Usage: scripts/artisan.sh <artisan-command> [arguments...]" >&2
exit 2
fi
service="${AKAUNTING_SERVICE:-akaunting.php}"
docker compose exec -u www-data "${service}" php artisan "$@"
27 changes: 27 additions & 0 deletions tests/artisan.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bats

load test_helper

setup() {
setup_test_bin
stub_command docker
}

@test "artisan wrapper executes as the same user as PHP-FPM" {
run bash "${BATS_TEST_DIRNAME}/../scripts/artisan.sh" cache:clear
[ "$status" -eq 0 ]
grep -q '^docker compose exec -u www-data akaunting.php php artisan cache:clear$' "${CALL_LOG}"
}

@test "artisan wrapper preserves arguments and selected service" {
AKAUNTING_SERVICE=custom.php run bash "${BATS_TEST_DIRNAME}/../scripts/artisan.sh" route:list --path=api
[ "$status" -eq 0 ]
grep -q '^docker compose exec -u www-data custom.php php artisan route:list --path=api$' "${CALL_LOG}"
}

@test "artisan wrapper refuses empty commands" {
run bash "${BATS_TEST_DIRNAME}/../scripts/artisan.sh"
[ "$status" -eq 2 ]
[[ "$output" == *"Usage:"* ]]
[ ! -s "${CALL_LOG}" ]
}
8 changes: 4 additions & 4 deletions tests/entrypoint.bats
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ setup() {
run bash "${BATS_TEST_DIRNAME}/../.docker/php/shell/entrypoint.sh"

[ "$status" -eq 0 ]
[ "$(cat "${TEST_LIFECYCLE_LOG}")" = "patches" ]
[ "$(cat "${TEST_LIFECYCLE_LOG}")" = $'patches\ncache-ownership' ]
grep -q '^php-fpm ' "${CALL_LOG}"
}

@test "fresh installation runs lifecycle in deterministic order" {
run bash "${BATS_TEST_DIRNAME}/../.docker/php/shell/entrypoint.sh"

[ "$status" -eq 0 ]
expected=$'clone\npatches\nphp-dependencies\ninstall\nownership'
expected=$'clone\npatches\nphp-dependencies\ninstall\nownership\ncache-ownership'
[ "$(cat "${TEST_LIFECYCLE_LOG}")" = "$expected" ]
}

Expand All @@ -42,7 +42,7 @@ setup() {
run bash "${BATS_TEST_DIRNAME}/../.docker/php/shell/entrypoint.sh"

[ "$status" -eq 0 ]
expected=$'patches\nphp-dependencies'
expected=$'patches\nphp-dependencies\ncache-ownership'
[ "$(cat "${TEST_LIFECYCLE_LOG}")" = "$expected" ]
}

Expand All @@ -54,6 +54,6 @@ setup() {
run bash "${BATS_TEST_DIRNAME}/../.docker/php/shell/entrypoint.sh"

[ "$status" -eq 0 ]
expected=$'patches\ninstall'
expected=$'patches\ninstall\ncache-ownership'
[ "$(cat "${TEST_LIFECYCLE_LOG}")" = "$expected" ]
}
4 changes: 4 additions & 0 deletions tests/test_helper.bash
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ akaunting_fix_ownership() {
record_lifecycle ownership
}

akaunting_repair_runtime_cache_permissions() {
record_lifecycle cache-ownership
}

akaunting_update_checkout() {
record_lifecycle checkout
}
Expand Down
Loading