diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..e10da22 --- /dev/null +++ b/.claude/settings.json @@ -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/')" + ] + } +} diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -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 diff --git a/.idea/laravel-idea.xml b/.idea/laravel-idea.xml new file mode 100644 index 0000000..bd941a4 --- /dev/null +++ b/.idea/laravel-idea.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d6a57ee --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..0960256 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml new file mode 100644 index 0000000..4f8104c --- /dev/null +++ b/.idea/phpunit.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/warehouse.iml b/.idea/warehouse.iml new file mode 100644 index 0000000..64ad165 --- /dev/null +++ b/.idea/warehouse.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/deploy-raspberry-pi.md b/docs/deploy-raspberry-pi.md new file mode 100644 index 0000000..412b0c6 --- /dev/null +++ b/docs/deploy-raspberry-pi.md @@ -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 \ No newline at end of file diff --git a/readme.md b/readme.md index 16a7c31..8837475 100644 --- a/readme.md +++ b/readme.md @@ -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`. diff --git a/scripts/deploy-raspi.sh b/scripts/deploy-raspi.sh new file mode 100644 index 0000000..f216312 --- /dev/null +++ b/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 [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 </dev/null <