# Installation Guide

This guide walks a developer who has never seen SocietyHub Pro through a complete production
installation, from an empty hosting account to a working, logged-in admin session. It applies to both
shared hosting (cPanel) and a VPS — differences are called out where they occur.

This package ships **pre-built**: `vendor/` (production dependencies) and `public/build/` (the compiled
frontend) are already included. You do not need Composer or Node.js installed on the server to deploy
it — only PHP and MySQL/MariaDB. If you'd rather build from source, see the note at the end of step 3.

---

## 1. Requirements

### Required for deployment (the server this runs on)

- **PHP 8.3 or newer**, with these extensions enabled: `gd`, `mbstring`, `pdo_mysql`, `fileinfo`, `intl`,
  `curl`, `zip`. All are enabled by default on essentially every shared PHP host and standard VPS PHP
  build. (`gd` is used for membership-card/receipt rendering and the media optimization pipeline — the
  application has no Imagick dependency.)
- **MySQL 5.7+ or MariaDB 10.3+**
- A **web server** — Apache (with `mod_rewrite`) or Nginx + PHP-FPM
- **HTTPS** (a free Let's Encrypt / cPanel AutoSSL certificate is sufficient) — required for Google
  OAuth, Cloudflare Turnstile, and PWA installability in production
- **Cron access** — either shell cron (`crontab`) or, failing that, a way to schedule an HTTP request
  (cPanel's Cron Jobs UI, or an external scheduler) — see [§13](#13-configure-the-scheduler)

### Required only if building from source instead of using this pre-built package

- Composer 2
- Node.js 18+ and npm

---

## 2. Prepare the server

Confirm your hosting plan/server meets the requirements above. On cPanel: **MultiPHP Manager** (or
**Select PHP Version**) to set PHP to 8.3+, and its **Extensions** tab to confirm the extensions listed
above are checked.

## 3. Upload the files

Upload the entire contents of this package to your server (SFTP, cPanel File Manager, or `git`/`rsync`
if you're deploying from your own repository instead of this package).

**Important:** the application directory must **not** be the web-facing document root — only the
`public/` subdirectory should ever be web-accessible. See [§4](#4-configure-the-document-root) for how
to point your domain at it correctly.

> **Building from source instead of using the included `vendor/`/`public/build/`:** delete those two
> directories, then run `composer install --no-dev --optimize-autoloader`, `npm install`, and
> `npm run build` on a machine with Composer/Node available (your own machine, or the server if it has
> them) before proceeding.

## 4. Configure the document root

Laravel's entire security model assumes only `public/` is web-accessible — `.env`, `app/`, `config/`,
`database/`, `routes/`, `storage/`, and `vendor/` must never be servable directly.

**Method A — your host lets you set a custom document root (preferred).** Most cPanel setups do, via
**Domains** → edit the domain's document root. Upload the app to e.g. `~/societyhub` and set the
domain's document root to `~/societyhub/public`. This is the cleanest setup — use it if available.

**Method B — `public_html` is a fixed document root (some budget shared-hosting plans).** Upload the
app *outside* `public_html` (e.g. `~/societyhub`), then either:

- Symlink it in: `ln -s ~/societyhub/public ~/public_html` (only works if `public_html` doesn't already
  exist / can be replaced with the symlink), or
- Copy the contents of `public/` into `public_html/` and edit the two path constants at the top of
  `public_html/index.php` to point up to your app directory:

  ```php
  require __DIR__.'/../societyhub/vendor/autoload.php';
  $app = require_once __DIR__.'/../societyhub/bootstrap/app.php';
  ```

  (adjust the relative path to wherever you actually uploaded the app relative to `public_html`).

A root-level `.htaccess` is also included at the application root (not `public/`) for hosts that force
`public_html` to serve the whole app directory directly rather than letting you point at `public/` —
it transparently routes requests into `public/`. It's inert and unused if your host lets `public/` be
the real document root (Method A above).

## 5. Create the database

Via cPanel's **MySQL Databases** tool, or your VPS's MySQL/MariaDB client:

```sql
CREATE DATABASE societyhub CHARACTER SET utf8mb4;
CREATE USER 'societyhub'@'localhost' IDENTIFIED BY 'a-strong-password';
GRANT ALL PRIVILEGES ON societyhub.* TO 'societyhub'@'localhost';
FLUSH PRIVILEGES;
```

(On cPanel, database/user names are typically prefixed with your account name automatically — note the
final prefixed names, you'll need them in the next step.)

## 6. Configure `.env`

Copy `.env.example` to `.env` in the application root (not inside `public/`) and set at minimum:

```
APP_NAME="Your Society Name"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://members.yourdomain.org

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=societyhub
DB_USERNAME=societyhub
DB_PASSWORD=the-password-you-set
```

**`APP_DEBUG=false` is required in production** — leaving it `true` leaks stack traces (including
database credentials) to anyone who triggers a 500 error.

Payment gateways, Google OAuth, SMTP, SMS providers, CAPTCHA, and branding are **not** environment
variables — they're configured after your first login from **Admin → Settings**, stored in the
database, and rotatable without touching the server again. See `docs/CONFIGURATION.md`.

## 7. Generate the application key

```bash
php artisan key:generate
```

Skip this only if you built `vendor/` yourself and already generated a key locally that you're carrying
over in `.env`. Without a key, every request fails.

## 8. Run migrations

```bash
php artisan migrate --force
```

`--force` is required because `migrate` refuses to run non-interactively when `APP_ENV=production`, as
a safety check. **Never run `migrate:fresh` against a real installation** — it drops every table.

## 9. Seed initial data

**Do not run the demo seeder (`php artisan db:seed`) in production** — it creates local-only demo
accounts and sample data. Production installs get their permission catalog and admin roles seeded
automatically by the Installation Wizard in the next step (`PermissionCatalogSeeder`) — there is nothing
to seed manually.

## 10. Configure storage

```bash
php artisan storage:link
```

This symlinks `public/storage` → `storage/app/public`, which is where uploaded media (logos, avatars,
membership card photos, message attachments) is served from. If your hosting environment doesn't
support symlinks, see `docs/TROUBLESHOOTING.md`.

## 11. Configure file permissions

`storage/` and `bootstrap/cache/` must be writable by the web server process:

```bash
chmod -R 775 storage bootstrap/cache
```

If your host runs PHP under a different user than your shell user, you may additionally need `chown` —
ask your host's support if `775` alone doesn't resolve write errors. Avoid `777` — it's broader than
necessary and most hosts flag it as a security issue.

## 12. Run the Installation Wizard

Visit your site's URL in a browser. With no administrator account yet in the database, `/` redirects
straight to `/install` — a guided setup that checks system requirements (PHP version, required
extensions, writable `storage/`, the storage symlink), collects your organization's name/email/timezone,
and creates your first administrator account (automatically assigned the built-in "Super Administrator"
role — full access from the start, no manual role assignment needed).

The wizard seeds the permission catalog itself; you do not run `db:seed` in production. It locks itself
shut the moment an administrator exists — re-visiting `/install` on an already-set-up site just
redirects to `/login`.

## 13. Configure the scheduler

The reminder engine, scheduled backups, and the media optimization pipeline all run on Laravel's
scheduler — nothing runs automatically without this step.

**Option A — shell cron (preferred, if your host allows it):**

```
* * * * * cd /home/youruser/societyhub && php artisan schedule:run >> /dev/null 2>&1
```

This runs every minute and lets Laravel's own scheduler (`routes/console.php`) decide when each task
actually fires — `reminders:send` (daily), `backup:run` (daily, no-ops unless a backup is actually due),
and `media:process-pending` (every 5 minutes, processes a small batch of newly uploaded images into
optimized WebP/AVIF variants). This single cron entry covers all of them, and any future scheduled task
the application adds, with no further configuration.

**Option B — HTTP endpoint (if cron can only run `curl`/`wget`, or you'd rather not grant cron shell
access):**

```
0 6 * * * curl -s -X POST https://members.yourdomain.org/cron/reminders -H "Authorization: Bearer YOUR_CRON_SECRET"
5 6 * * * curl -s -X POST https://members.yourdomain.org/cron/backup -H "Authorization: Bearer YOUR_CRON_SECRET"
*/5 * * * * curl -s -X POST https://members.yourdomain.org/cron/media-process -H "Authorization: Bearer YOUR_CRON_SECRET"
```

Set `CRON_SECRET` in `.env` to a long random value first, and use the same value in each
`Authorization` header — every endpoint rejects requests without a matching bearer token.

No queue worker is required for any of this — the application does not depend on a persistent
`php artisan queue:work` process.

## 14. Configure email

Email is not an environment variable — configure it after logging in, from **Admin → Settings →
Notifications**, using a real SMTP provider's credentials (host, port, username, password, encryption,
from-address/name). See `docs/EMAIL_SETUP.md`. Until configured, outbound email simply won't send;
nothing else in the application depends on it being set up immediately.

## 15. Configure Paystack (and/or PayPal)

Also configured from **Admin → Settings → Payments**, not `.env` — see `docs/PAYSTACK_SETUP.md` and
`docs/PAYPAL_SETUP.md`. Use your **live** secret/public keys for a production deployment; Paystack's
**test** keys are for verifying the integration end-to-end before going live, and should never be left
configured on a real production install. Configure the webhook URL shown in those guides in your
Paystack/PayPal dashboard so payment status updates reach the application even if a member closes their
browser mid-payment.

## 16. Configure SSL

Enable HTTPS via your host's free certificate tool (cPanel AutoSSL, or `certbot` on a VPS) if it isn't
already active. Required for Google OAuth, Cloudflare Turnstile, and PWA installability.

## 17. Cache configuration for production performance

```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

Re-run these after any `.env` change or file deploy — a stale config cache is the most common source of
"why isn't my setting taking effect" confusion. (`php artisan config:clear` undoes it for debugging.)

## 18. Verify the installation

1. Log in with the administrator account you created in the wizard.
2. Go to **Admin → Status** and run a health check. Confirm Application, Database, Authentication, and
   Storage read Healthy; review any Configuration Warnings for integrations you intend to use but
   haven't configured yet.
3. Go to **Admin → Settings → Deployment Center** — confirm Storage Link, Environment File, Database,
   Scheduler, and Filesystem Permissions all read Ready.
4. Walk through `docs/CONFIGURATION.md` to enable email, payments, OAuth, and CAPTCHA as needed.
5. Confirm HTTPS is enforced and the site loads with no mixed-content warnings.

---

## Troubleshooting

See `docs/TROUBLESHOOTING.md` for common issues (broken storage symlink, session/cache errors before a
database exists, everyone signed out after Optimize Application, and more). A few frequent ones:

- **500 error on every page:** almost always a missing `APP_KEY` (§7) or `storage`/`bootstrap/cache`
  not writable (§11). Temporarily set `APP_DEBUG=true` to see the real error, then set it back to
  `false` once resolved.
- **CSS/JS not loading:** the document root isn't actually pointed at `public/` (§4), or the browser is
  serving a cached page from before `public/build/` was uploaded — hard-refresh.
- **Reminders/backups/media processing never happen:** the scheduler isn't wired up (§13) — check
  **Admin → Settings → Deployment Center**, which specifically detects whether the scheduler heartbeat
  is running.
