| @@ -0,0 +1,20 @@ | |||
| { | |||
| "permissions": { | |||
| "allow": [ | |||
| "Bash(docker compose *)", | |||
| "Bash(grep -E '^\\\\.env$|^\\\\.env' .gitignore)", | |||
| "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://127.0.0.1:8090/)", | |||
| "Bash(docker rm *)", | |||
| "Bash(powershell.exe -NoProfile -Command \"netsh interface ipv4 show excludedportrange protocol=tcp\")", | |||
| "Bash(curl -s -o /dev/null -w \"app :8080 -> %{http_code}\\\\n\" http://127.0.0.1:8080/)", | |||
| "Bash(curl -s -o /dev/null -w \"phpliteadmin :8091 -> %{http_code}\\\\n\" http://127.0.0.1:8091/)", | |||
| "Bash(curl -s http://127.0.0.1:8080/)", | |||
| "Bash(curl -s -o /dev/null -w \"app :9092 -> %{http_code}\\\\n\" http://127.0.0.1:9092/)", | |||
| "Bash(curl -s -o /dev/null -w \"phpliteadmin :9091 -> %{http_code}\\\\n\" http://127.0.0.1:9091/)", | |||
| "Bash(curl -s http://127.0.0.1:9092/)", | |||
| "Bash(curl -s http://127.0.0.1:9092/locations)", | |||
| "Bash(curl -s http://127.0.0.1:9092/items)", | |||
| "Bash(grep -E '\\\\.env$|\\\\.sqlite$|storage/')" | |||
| ] | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| # Default ignored files | |||
| /shelf/ | |||
| /workspace.xml | |||
| # Editor-based HTTP Client requests | |||
| /httpRequests/ | |||
| # Ignored default folder with query files | |||
| /queries/ | |||
| # Datasource local storage ignored files | |||
| /dataSources/ | |||
| /dataSources.local.xml | |||
| @@ -0,0 +1,8 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project version="4"> | |||
| <component name="InertiaPackage"> | |||
| <option name="directoryPaths"> | |||
| <list /> | |||
| </option> | |||
| </component> | |||
| </project> | |||
| @@ -0,0 +1,8 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project version="4"> | |||
| <component name="ProjectModuleManager"> | |||
| <modules> | |||
| <module fileurl="file://$PROJECT_DIR$/.idea/warehouse.iml" filepath="$PROJECT_DIR$/.idea/warehouse.iml" /> | |||
| </modules> | |||
| </component> | |||
| </project> | |||
| @@ -0,0 +1,27 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project version="4"> | |||
| <component name="MessDetectorOptionsConfiguration"> | |||
| <option name="transferred" value="true" /> | |||
| </component> | |||
| <component name="PHPCSFixerOptionsConfiguration"> | |||
| <option name="transferred" value="true" /> | |||
| </component> | |||
| <component name="PHPCodeSnifferOptionsConfiguration"> | |||
| <option name="highlightLevel" value="WARNING" /> | |||
| <option name="transferred" value="true" /> | |||
| </component> | |||
| <component name="PhpProjectSharedConfiguration" php_language_level="8.0"> | |||
| <option name="suggestChangeDefaultLanguageLevel" value="false" /> | |||
| </component> | |||
| <component name="PhpStanOptionsConfiguration"> | |||
| <option name="transferred" value="true" /> | |||
| </component> | |||
| <component name="PhpUnit"> | |||
| <phpunit_settings> | |||
| <PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" /> | |||
| </phpunit_settings> | |||
| </component> | |||
| <component name="PsalmOptionsConfiguration"> | |||
| <option name="transferred" value="true" /> | |||
| </component> | |||
| </project> | |||
| @@ -0,0 +1,10 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project version="4"> | |||
| <component name="PHPUnit"> | |||
| <option name="directories"> | |||
| <list> | |||
| <option value="$PROJECT_DIR$/tests" /> | |||
| </list> | |||
| </option> | |||
| </component> | |||
| </project> | |||
| @@ -0,0 +1,6 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project version="4"> | |||
| <component name="VcsDirectoryMappings"> | |||
| <mapping directory="" vcs="Git" /> | |||
| </component> | |||
| </project> | |||
| @@ -0,0 +1,12 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <module type="WEB_MODULE" version="4"> | |||
| <component name="NewModuleRootManager"> | |||
| <content url="file://$MODULE_DIR$"> | |||
| <sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" packagePrefix="App\" /> | |||
| <sourceFolder url="file://$MODULE_DIR$/framework" isTestSource="false" packagePrefix="Framework\" /> | |||
| <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> | |||
| </content> | |||
| <orderEntry type="inheritedJdk" /> | |||
| <orderEntry type="sourceFolder" forTests="false" /> | |||
| </component> | |||
| </module> | |||
| @@ -0,0 +1,62 @@ | |||
| # Raspberry Pi deployment | |||
| This app can run directly on a Raspberry Pi with **nginx + php-fpm** instead of Docker. | |||
| ## What the script does | |||
| `scripts/deploy-raspi.sh` runs from your local machine and connects over SSH to the Pi. On the Pi it will: | |||
| - install `nginx`, `git`, `composer`, and PHP 8.2 packages | |||
| - clone or update the repository | |||
| - create/update `.env` | |||
| - install Composer dependencies | |||
| - prepare the default SQLite database | |||
| - run migrations | |||
| - create an nginx vhost that serves `public/` | |||
| - create a `systemd` service for `php command.php queue:work` | |||
| ## Assumptions | |||
| - the Raspberry Pi is running a Debian/Raspberry Pi OS style distro with `apt` | |||
| - your SSH user can run `sudo` | |||
| - the repository is reachable from the Pi over Git/SSH or HTTPS | |||
| - you want the app at `/var/www/warehouse` | |||
| - you are fine using the app's default **SQLite** setup | |||
| ## Basic usage | |||
| From this repo on your local machine: | |||
| ```bash | |||
| chmod +x scripts/deploy-raspi.sh | |||
| scripts/deploy-raspi.sh pi@raspberrypi.local git@github.com:YOUR_USER/warehouse.git DietPi.ntp.kentcommunications.com | |||
| ``` | |||
| Arguments: | |||
| - argument 1: SSH target, for example `pi@192.168.1.50` | |||
| - argument 2: Git clone URL the Pi can access | |||
| - argument 3: nginx `server_name` value, optional, defaults to `DietPi.ntp.kentcommunications.com` | |||
| ## Useful environment overrides | |||
| ```bash | |||
| DEPLOY_REF=main \ | |||
| DEPLOY_PATH=/var/www/warehouse \ | |||
| PHP_VERSION=8.2 \ | |||
| scripts/deploy-raspi.sh pi@raspberrypi.local git@github.com:YOUR_USER/warehouse.git DietPi.ntp.kentcommunications.com | |||
| ``` | |||
| ## After deploy | |||
| - browse to `http://DietPi.ntp.kentcommunications.com` | |||
| - check nginx with `sudo systemctl status nginx` | |||
| - check php-fpm with `sudo systemctl status php8.2-fpm` | |||
| - check the queue worker with `sudo systemctl status warehouse-queue.service` | |||
| ## Notes | |||
| - the app serves from `public/`, so the nginx root must stay pointed there | |||
| - low-stock emails are always queued in this app, so the queue worker service matters in production | |||
| - this script sets up **HTTP only**; add TLS separately with `certbot` or another reverse-proxy setup if you want HTTPS | |||
| - if you prefer MySQL instead of SQLite, update `.env` and the script before rerunning it | |||
| @@ -37,3 +37,7 @@ Note: host ports `9092`/`9091` map to the app's internal `80`/`8081` — the hos | |||
| ## Framework guide | |||
| This app is built on a small custom MVC framework in `framework/`. See [docs/framework.md](docs/framework.md) for how routing, controllers, views, models/migrations, validation, sessions, caching, queues, and the CLI commands all work. | |||
| ## Raspberry Pi deployment | |||
| If you want to run this app directly on a Raspberry Pi with `nginx` and `php-fpm`, see [docs/deploy-raspberry-pi.md](docs/deploy-raspberry-pi.md) and use `scripts/deploy-raspi.sh`. | |||
| @@ -0,0 +1,125 @@ | |||
| #!/usr/bin/env bash | |||
| set -euo pipefail | |||
| if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then | |||
| echo "Usage: scripts/deploy-raspi.sh <ssh-host> <git-repo> [domain]" | |||
| exit 1 | |||
| fi | |||
| ssh_host="$1" | |||
| repo_url="$2" | |||
| domain="${3:-DietPi.ntp.kentcommunications.com}" | |||
| deploy_path="${DEPLOY_PATH:-/var/www/warehouse}" | |||
| deploy_ref="${DEPLOY_REF:-main}" | |||
| php_version="${PHP_VERSION:-8.2}" | |||
| ssh "$ssh_host" bash -s -- "$repo_url" "$domain" "$deploy_path" "$deploy_ref" "$php_version" <<'REMOTE' | |||
| set -euo pipefail | |||
| repo_url="$1" | |||
| domain="$2" | |||
| deploy_path="$3" | |||
| deploy_ref="$4" | |||
| php_version="$5" | |||
| owner="$(id -un)" | |||
| group="$(id -gn)" | |||
| php_fpm_service="php${php_version}-fpm" | |||
| php_fpm_socket="/run/php/${php_fpm_service}.sock" | |||
| set_env() { | |||
| file="$1" | |||
| key="$2" | |||
| value="$3" | |||
| if grep -q "^${key}=" "$file"; then | |||
| sed -i "s|^${key}=.*|${key}=${value}|" "$file" | |||
| else | |||
| printf '%s=%s\n' "$key" "$value" >> "$file" | |||
| fi | |||
| } | |||
| sudo apt-get update | |||
| sudo apt-get install -y git nginx composer \ | |||
| "php${php_version}" "php${php_version}-cli" "php${php_version}-fpm" \ | |||
| "php${php_version}-mbstring" "php${php_version}-xml" \ | |||
| "php${php_version}-curl" "php${php_version}-zip" \ | |||
| "php${php_version}-sqlite3" "php${php_version}-mysql" | |||
| sudo mkdir -p "$(dirname "$deploy_path")" | |||
| if [ ! -d "$deploy_path/.git" ]; then | |||
| sudo git clone --branch "$deploy_ref" --single-branch "$repo_url" "$deploy_path" | |||
| sudo chown -R "$owner:$group" "$deploy_path" | |||
| else | |||
| git -C "$deploy_path" fetch --prune origin | |||
| git -C "$deploy_path" checkout "$deploy_ref" | |||
| git -C "$deploy_path" reset --hard "origin/$deploy_ref" | |||
| fi | |||
| cd "$deploy_path" | |||
| if [ ! -f .env ] && [ -f .env.example ]; then cp .env.example .env; fi | |||
| set_env .env APP_ENV prod | |||
| set_env .env DB_CONNECTION sqlite | |||
| set_env .env DB_SQLITE_PATH "$deploy_path/database/database.sqlite" | |||
| composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-reqs | |||
| mkdir -p storage/framework/views database | |||
| touch database/database.sqlite | |||
| sudo chgrp -R www-data storage database | |||
| sudo chmod -R g+rwX storage database | |||
| sudo find storage database -type d -exec chmod 2775 {} \; | |||
| sudo chmod 664 database/database.sqlite | |||
| php command.php migrate | |||
| sudo tee /etc/systemd/system/warehouse-queue.service >/dev/null <<EOF | |||
| [Unit] | |||
| Description=Warehouse queue worker | |||
| After=network.target | |||
| [Service] | |||
| Type=simple | |||
| User=${owner} | |||
| Group=www-data | |||
| WorkingDirectory=${deploy_path} | |||
| ExecStart=/usr/bin/php ${deploy_path}/command.php queue:work | |||
| Restart=always | |||
| RestartSec=3 | |||
| [Install] | |||
| WantedBy=multi-user.target | |||
| EOF | |||
| sudo tee /etc/nginx/sites-available/warehouse >/dev/null <<EOF | |||
| server { | |||
| listen 80; | |||
| listen [::]:80; | |||
| server_name ${domain}; | |||
| root ${deploy_path}/public; | |||
| index index.php; | |||
| location / { | |||
| try_files \$uri \$uri/ /index.php?\$query_string; | |||
| } | |||
| location ~ \.php$ { | |||
| include snippets/fastcgi-php.conf; | |||
| fastcgi_pass unix:${php_fpm_socket}; | |||
| } | |||
| location ~ /\. { | |||
| deny all; | |||
| } | |||
| } | |||
| EOF | |||
| sudo ln -sfn /etc/nginx/sites-available/warehouse /etc/nginx/sites-enabled/warehouse | |||
| sudo rm -f /etc/nginx/sites-enabled/default | |||
| sudo systemctl daemon-reload | |||
| sudo systemctl enable "$php_fpm_service" nginx warehouse-queue.service | |||
| sudo systemctl restart "$php_fpm_service" | |||
| sudo nginx -t | |||
| sudo systemctl restart nginx | |||
| sudo systemctl restart warehouse-queue.service | |||
| echo "Deployment complete: http://${domain}" | |||
| REMOTE | |||
Powered by TurnKey Linux.