|
| 1 | +# StaticHTMLSites |
| 2 | + |
| 3 | +A Laravel 12 application for hosting static HTML mini-sites. |
| 4 | +Each page is accessible via subdomain (`{slug}.statichtmlsites.mtex.dev`) and path URL (`statichtmlsites.mtex.dev/{slug}/...`). |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Tech Stack |
| 9 | + |
| 10 | +| Layer | Technology | |
| 11 | +|----------|----------------------------------| |
| 12 | +| Backend | Laravel 12, PHP 8.2+ | |
| 13 | +| Frontend | Tailwind CSS 3, Alpine.js 3 | |
| 14 | +| Editor | CodeMirror 6 (npm) | |
| 15 | +| Build | Vite 5 + laravel-vite-plugin | |
| 16 | +| Auth | Laravel Breeze (blade stack) | |
| 17 | +| Database | SQLite (default) or MySQL | |
| 18 | +| Storage | Local filesystem | |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Installation |
| 23 | + |
| 24 | +```bash |
| 25 | +# 1. Install PHP + JS dependencies |
| 26 | +composer install |
| 27 | +npm install |
| 28 | + |
| 29 | +# 2. Environment |
| 30 | +cp .env.example .env |
| 31 | +php artisan key:generate |
| 32 | + |
| 33 | +# 3. Migrate |
| 34 | +php artisan migrate |
| 35 | + |
| 36 | +# 4. Install Breeze auth scaffolding |
| 37 | +php artisan breeze:install blade |
| 38 | +php artisan migrate |
| 39 | + |
| 40 | +# 5. Build assets |
| 41 | +npm run build |
| 42 | +``` |
| 43 | + |
| 44 | +Key `.env` variables: |
| 45 | + |
| 46 | +| Variable | Value | |
| 47 | +|-------------------|--------------------------------------| |
| 48 | +| APP_URL | https://statichtmlsites.mtex.dev | |
| 49 | +| APP_BASE_DOMAIN | statichtmlsites.mtex.dev | |
| 50 | +| MAX_UPLOAD_MB | 50 | |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Wildcard Subdomain (DNS + Nginx) |
| 55 | + |
| 56 | +**DNS:** |
| 57 | +``` |
| 58 | +*.statichtmlsites.mtex.dev CNAME statichtmlsites.mtex.dev |
| 59 | +``` |
| 60 | + |
| 61 | +**Nginx:** |
| 62 | +```nginx |
| 63 | +server { |
| 64 | + listen 443 ssl; |
| 65 | + server_name statichtmlsites.mtex.dev *.statichtmlsites.mtex.dev; |
| 66 | + ssl_certificate /path/to/wildcard.crt; |
| 67 | + ssl_certificate_key /path/to/wildcard.key; |
| 68 | + root /var/www/statichtmlsites/public; |
| 69 | + index index.php; |
| 70 | + location / { try_files $uri $uri/ /index.php?$query_string; } |
| 71 | + location ~ \.php$ { |
| 72 | + fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; |
| 73 | + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; |
| 74 | + include fastcgi_params; |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +**config/filesystems.php** — add to the returned array: |
| 80 | +```php |
| 81 | +'max_upload_mb' => env('MAX_UPLOAD_MB', 50), |
| 82 | +``` |
| 83 | + |
| 84 | +--- |
| 85 | + |
| 86 | +## Project Structure |
| 87 | + |
| 88 | +``` |
| 89 | +app/ |
| 90 | + Http/Controllers/ |
| 91 | + DashboardController.php Dashboard page |
| 92 | + FileManagerController.php File CRUD JSON API |
| 93 | + PageController.php Create / update / delete pages |
| 94 | + PageServeController.php Serve static files + <base> injection |
| 95 | + Models/ |
| 96 | + Page.php ULID model, storage helpers, slug uniqueness |
| 97 | + User.php Breeze user + pages() HasMany |
| 98 | + Policies/PagePolicy.php Ownership checks |
| 99 | + Providers/AppServiceProvider.php Register FileManagerService singleton + policy |
| 100 | + Services/FileManagerService.php All file ops: tree, read, save, upload, delete, rename |
| 101 | +
|
| 102 | +resources/ |
| 103 | + css/app.css Tailwind + CodeMirror overrides |
| 104 | + js/ |
| 105 | + app.js Global entry (Alpine on non-manager pages) |
| 106 | + file-manager.js CodeMirror 6 + Alpine file-tree component |
| 107 | + views/ |
| 108 | + layouts/app.blade.php App shell with nav |
| 109 | + welcome.blade.php Marketing landing |
| 110 | + pages/ |
| 111 | + dashboard.blade.php Page grid |
| 112 | + create.blade.php New page form |
| 113 | + manager.blade.php Full-screen file manager |
| 114 | +
|
| 115 | +database/migrations/ |
| 116 | + ..._create_pages_table.php |
| 117 | +
|
| 118 | +routes/web.php All routes + subdomain group |
| 119 | +``` |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +## Key Design Decisions |
| 124 | + |
| 125 | +- **`<base>` injection** — `PageServeController` detects subdomain vs. path access and injects `<base href="...">` into every HTML file before serving, keeping relative asset paths correct under both URL shapes. |
| 126 | +- **Path traversal guard** — `FileManagerService::guard()` and `PageServeController` both resolve real paths and assert they sit inside the page's storage root. |
| 127 | +- **Storage isolation** — `storage/app/pages/{slug}/`. Directories are auto-created on page creation, renamed on slug change, and deleted on page deletion via Eloquent model events. |
| 128 | +- **File tree** — Pure PHP recursive directory scan returned as JSON, rendered by an Alpine component with inline HTML strings. |
| 129 | + |
| 130 | +--- |
| 131 | + |
| 132 | +## License |
| 133 | +MIT |
0 commit comments