# Installing ZoomIt

Two paths, depending on who's setting this up:

- **[Local development](#local-development)** — you have a terminal, Composer
  and Node, and you're running this on your own machine.
- **[cPanel / handing this to a client](#cpanel--handing-this-to-a-client)** —
  no terminal access assumed; a browser-based installer does the rest.

Both end up in the same place: a database, an admin account, and a running
site. Video calling (LiveKit) is deliberately **not** part of either path —
see [Adding video calling](#adding-video-calling-livekit) at the end.

---

## Local development

### Requirements

- PHP 8.3+, Composer
- MySQL 8
- Node 20+

### Steps

```bash
composer install
npm install

cp .env.example .env
php artisan key:generate
```

Create the database:

```dotenv
# .env
DB_DATABASE=zoomit
```

```bash
php artisan migrate --seed   # seeds an admin, a user and a demo meeting
npm run build                # or: npm run dev
php artisan serve
php artisan queue:work       # required for recording downloads and Telegram
```

Seeded logins (`password` for both):

| Email                | Role  |
| --------------------- | ----- |
| `admin@zoomit.test`  | admin |
| `user@zoomit.test`   | user  |

That's it — no browser installer runs locally, since `php artisan migrate
--seed` already does what it would do. The installer described below only
appears on a copy where `storage/installed` doesn't exist yet, which a fresh
`composer install` checkout never creates on its own.

---

## cPanel / handing this to a client

There's a browser-based installer for exactly this: a client uploads a release
package, opens it in a browser, and never touches Composer, npm, or a
terminal.

### 1. Build a release package (you do this once, before handing it over)

```bash
composer install --no-dev --optimize-autoloader
npm ci && npm run build
```

Then zip the **actual files on disk** — `vendor/` and `public/build/` are
gitignored for development but must be *in* the zip, since the client's server
won't run Composer or npm to generate them. A plain `git archive` would skip
both; use your file manager or `zip -r release.zip . -x ".git/*"` instead.

Before zipping, make sure the package does **not** contain:

- **`storage/installed`** — its presence is what tells the app it's already
  set up. If it's in the zip, the installer never appears and the client is
  stuck.
- **A real `.env`** with your own database credentials or `APP_KEY` in it.

Do include a `.env` — copy `.env.example` to `.env` and run
`php artisan key:generate` against *that* copy, so `APP_KEY` is already filled
in before it ships. The installer only *rewrites* keys in this same file
(database credentials, site URL, timezone); it never creates one from scratch,
and a blank `APP_KEY` would break the installer's very first page load.

### 2. What the client does

1. **In cPanel, create a database and a database user for it** — *MySQL®
   Databases* → create a database, create a user, add that user to the
   database with all privileges. Note the database name, username and
   password (cPanel usually prefixes all three with the account name).
2. **Upload the zip and extract it** via *File Manager*.
3. **Point the domain at `public/`.** If the plan allows a custom document
   root (*Domains* in cPanel), point it at the extracted folder's `public/`
   subfolder — the normal Laravel setup. If it doesn't, move the *contents* of
   `public/` into `public_html/` and edit the two `require` paths at the top
   of `public_html/index.php` to point up one level.
4. **Visit the site.** Since `storage/installed` doesn't exist yet, they land
   on a two-step installer:
   - **Guide** — explains what's about to happen and lists the three things
     to have ready (the database from step 1, the site's name/URL/timezone,
     and a name/email/password for the admin account they're about to
     create).
   - **Setup** — a system check (PHP version, required extensions, that
     `.env`/`storage/`/`bootstrap/cache/`/`public/` are writable), then one
     form covering the database, the site basics, and **the first admin
     account**. Submitting it writes `.env`, creates the database tables,
     creates that admin account, and locks the installer behind
     `EnsureNotInstalled` — it can't be reached again afterwards.
5. **Add a cron job** (cPanel → *Cron Jobs*) running every minute:
   ```
   php /home/theiraccount/path/to/app/artisan queue:work --stop-when-empty --max-time=50
   ```
   Shared hosting can't run a persistent `queue:work`; this drains the queue
   and exits, and cron restarts it a minute later. It's what delivers
   recording downloads and Telegram notifications.
6. **Sign in** with the admin account just created, and configure branding,
   Telegram, and (when ready) LiveKit from *Settings*.

### What the installer checks

PHP 8.3+, the extensions Laravel needs (`pdo_mysql`, `mbstring`, `openssl`, and
the rest of the standard list), and that `.env`, `storage/`,
`bootstrap/cache/` and `public/` are writable. It reports exactly what's
missing rather than a stack trace — these are the things that quietly break a
cPanel install if skipped, usually fixed from *MultiPHP Manager* (extensions)
or File Manager → right-click → Permissions (writable folders).

The database connection is tested for real, with the submitted credentials,
*before* anything is written to `.env` — a wrong host or password is reported
immediately, not as a confusing migration failure three steps later.

---

## Adding video calling (LiveKit)

Neither installation path asks for LiveKit credentials. The site works fully
without them — scheduling, invitations, branding, the admin dashboard — and
says so clearly at the one place they're actually needed: pressing **Join now**
in the lobby returns a plain "LiveKit is not configured" message instead of
failing silently.

To add it:

1. Create a free project at [cloud.livekit.io](https://cloud.livekit.io) (or
   run a self-hosted `livekit-server`).
2. Add its keys to `.env`:
   ```dotenv
   LIVEKIT_URL=wss://your-project.livekit.cloud
   LIVEKIT_API_KEY=API...
   LIVEKIT_API_SECRET=...
   ```
3. That's it — no migration, no restart needed beyond the next request.

## After installing

- **Timezone** — `APP_TIMEZONE` (set during install) supplies the default
  preselected on the scheduling form. Individual meetings can override it; see
  the main [README](README.md#timezones).
- **Branding** — logo, favicon, site name and the client-facing wording all
  live under *Settings* once signed in, and take effect immediately.
- **Telegram** — also under *Settings*: a bot token and chat ID, with a
  **Send test message** button. Once set, new registrations and invitees
  actually joining a meeting are reported there.
