# Installing on your server

You have two paths:

- **Automatic (`install.sh`)** — recommended. One script does the whole server-side
  install. Jump to *Automatic install* below.
- **Manual** — the step-by-step, if you want to understand or customise each part.
  It starts at *Manual install*.

Either way, two things live **outside** the server and can't be scripted: the
**wildcard DNS record** (`*.yourdomain.com`) and a **TLS certificate** for it.
The script generates a ready nginx vhost and prints the exact DNS + certbot
commands to finish.

---

## Automatic install

On a server that already has **PHP 8.2+**, **Composer 2**, and the **MySQL/MariaDB
client** (one-time, Ubuntu: `sudo apt install -y php8.3 php8.3-{cli,fpm,mysql,mbstring,xml,curl,bcmath,zip} composer mysql-server nginx`):

```bash
unzip saas-pos-platform.zip -d saas-pos-platform
cd saas-pos-platform
chmod +x install.sh

# Let the script create the database + user too (give it MySQL root once):
./install.sh --domain yourdomain.com \
  --db-pass 'a-strong-db-password' \
  --mysql-root-user root --mysql-root-pass 'your-mysql-root-password'
```

That single command:

1. builds a fresh Laravel 11 app (default `~/saas-pos`) and installs stancl/tenancy,
2. overlays this scaffold **already wired** — CSRF exemptions for the gateway
   callbacks and automatic per-tenant seeding are baked in, so there are *no files
   to edit by hand*,
3. writes `.env` (with the tenancy-safe `file` session/cache drivers) and generates
   the app key,
4. creates the central database and an app user that can create tenant databases,
5. migrates the central schema,
6. provisions your first tenant — its database is created, migrated, and seeded
   (chart of accounts + default warehouse) automatically — with demo data,
7. writes a ready-to-use nginx vhost to `<app>/deploy/`.

Run `./install.sh --help` for all options (custom app dir, DB host/port, tenant
name/domain, `--no-demo`, etc.). If you can't share MySQL root, omit the
`--mysql-root-*` flags, but make sure your DB user can `CREATE DATABASE` (tenants
need it).

When it finishes it prints the two remaining manual bits (wildcard DNS + the
nginx/certbot commands). Add more tenants any time with:

```bash
php artisan app:make-tenant "Acme Store" acme.yourdomain.com --demo
```

---

## Manual install

## Before you start — does your host qualify?

Multi-database tenancy needs a server you control. You need:

- **Root/VPS** (Ubuntu/Debian assumed below). Typical *shared hosting will not
  work* — it usually can't create databases on the fly or serve `*.subdomains`.
- **PHP 8.2+** with extensions: `pdo_mysql, mbstring, openssl, tokenizer, xml,
  ctype, json, bcmath, curl, fileinfo`.
- **MySQL 8 / MariaDB**, and a DB user that can **CREATE and DROP DATABASES**
  (each tenant gets its own DB). This is the #1 gotcha.
- **Composer 2**.
- A **domain** where you can set a **wildcard DNS record** (`*.yourdomain.com`).

---

## 1. Get a Laravel app and add tenancy

```bash
cd /var/www
composer create-project laravel/laravel saas-pos
cd saas-pos

composer require stancl/tenancy
php artisan tenancy:install
```

`tenancy:install` publishes `config/tenancy.php`, `routes/tenant.php`,
`app/Providers/TenancyServiceProvider.php`, and the `tenants` / `domains`
migrations.

## 2. Overlay this scaffold

Unzip `saas-pos-platform.zip` and copy its folders over the Laravel app,
**overwriting** `config/tenancy.php` and `routes/tenant.php` with the versions
in this scaffold:

```bash
# from inside the unzipped scaffold folder
cp -r app config database routes resources /var/www/saas-pos/
```

What this adds: the models/services/controllers, the tenant migrations under
`database/migrations/tenant`, `config/payments.php`, and the Blade views.

## 3. Configure the environment

Edit `/var/www/saas-pos/.env`:

```env
APP_NAME="SaaS POS"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

# Tenancy: the domain that serves the marketing/admin site.
# Tenants live on ANY other subdomain/domain.
CENTRAL_DOMAIN=yourdomain.com

# Central database (the control plane: tenants, plans, subscriptions)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=saas_central
DB_USERNAME=saas_user
DB_PASSWORD=change_me

# IMPORTANT for tenancy: the default DB connection gets swapped to the tenant
# during tenant requests, so keep sessions/cache off the database driver.
SESSION_DRIVER=file
CACHE_STORE=file
QUEUE_CONNECTION=sync

# Payments — your PLATFORM keys (tenants enter their own in-app)
PAYMENTS_DEFAULT=myfatoorah
PAYMENTS_CURRENCY=KWD
MYFATOORAH_TOKEN=
MYFATOORAH_BASE_URL=https://apitest.myfatoorah.com
TAP_SECRET_KEY=
TAP_BASE_URL=https://api.tap.company
```

Then:

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

Create the central database and grant the user database-creation rights:

```sql
CREATE DATABASE saas_central CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'saas_user'@'localhost' IDENTIFIED BY 'change_me';
-- needs *.* so it can create tenant_<uuid> databases:
GRANT ALL PRIVILEGES ON *.* TO 'saas_user'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
```

(If your MySQL host forbids `*.*`, you must pre-create each tenant DB by hand —
multi-DB tenancy is awkward there.)

## 4. Wire two framework files

**a) Auto-migrate AND seed each new tenant.** Open
`app/Providers/TenancyServiceProvider.php`, find the `TenantCreated` pipeline,
and **uncomment the seed job** so every new tenant gets its chart of accounts +
default warehouse (without this, ringing up a sale fails — the ledger needs
those accounts):

```php
Events\TenantCreated::class => [
    JobPipeline::make([
        Jobs\CreateDatabase::class,
        Jobs\MigrateDatabase::class,
        Jobs\SeedDatabase::class,   // <-- uncomment this line
    ])->send(fn (Events\TenantCreated $event) => $event->tenant)->shouldBeQueued(false),
],
```

**b) Exempt the gateway callbacks/webhooks from CSRF.** In `bootstrap/app.php`,
inside `->withMiddleware(...)`:

```php
->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'billing/callback/*', 'billing/webhook/*',
        'checkout/callback/*', 'checkout/webhook/*',
    ]);
})
```

(Make sure `use Illuminate\Foundation\Configuration\Middleware;` is at the top.)

## 5. Migrate the central database

```bash
php artisan migrate
```

This creates `tenants`, `domains`, `plans`, `subscriptions` (tenant databases
are created/migrated automatically when you add a tenant in the next step).

## 6. Web server + DNS + HTTPS

**DNS** — add an A record for the apex and a **wildcard** to your server IP:

```
@   A   YOUR.SERVER.IP
*   A   YOUR.SERVER.IP
```

**nginx** — `/etc/nginx/sites-available/saas-pos` (match your PHP-FPM version):

```nginx
server {
    listen 80;
    server_name yourdomain.com *.yourdomain.com;
    root /var/www/saas-pos/public;
    index index.php;

    location / { try_files $uri $uri/ /index.php?$query_string; }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
    location ~ /\.(?!well-known).* { deny all; }
}
```

```bash
sudo ln -s /etc/nginx/sites-available/saas-pos /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```

**HTTPS (wildcard, covers all tenant subdomains)** — needs a DNS challenge:

```bash
sudo certbot certonly --manual --preferred-challenges=dns \
  -d 'yourdomain.com' -d '*.yourdomain.com'
```

Then add the `listen 443 ssl;` block with the cert paths and reload nginx.
(Tenants on their own *custom* domains each need their own certificate — easiest
with a reverse proxy that does on-demand TLS, e.g. Caddy. Subdomains are covered
by the wildcard above.)

**Permissions:**

```bash
sudo chown -R www-data:www-data /var/www/saas-pos/storage /var/www/saas-pos/bootstrap/cache
```

## 7. Create your first tenant

```bash
php artisan tinker
```
```php
use App\Models\Plan; use App\Models\Tenant; use Illuminate\Support\Str;

$plan = Plan::create(['name'=>'Starter','slug'=>'starter','price'=>0,'billing_period'=>'monthly','is_active'=>true]);

$t = Tenant::create(['id'=>(string) Str::uuid(),'name'=>'Demo Store','email'=>'owner@demo.com','plan_id'=>$plan->id]);
$t->domains()->create(['domain'=>'demo.yourdomain.com']);   // tenant DB is created, migrated & seeded here
```

Open `https://demo.yourdomain.com/pos`. (Optional sample products + invoices:
`$t->run(fn () => (new Database\Seeders\Tenant\DemoSalesSeeder())->run());`)

If you ever skip the seed job in step 4, seed all tenants manually with
`php artisan tenants:seed`.

## 8. Production hardening

```bash
composer install --no-dev --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

If you later switch `QUEUE_CONNECTION` off `sync`, run a worker under Supervisor
(`php artisan queue:work`) so tenant provisioning and other jobs process.

---

## Troubleshooting

- **"Access denied… creating database" / tenant DB not made** → the MySQL user
  lacks CREATE privilege (step 3).
- **419 Page Expired on a payment return** → CSRF exemptions missing (step 4b).
- **Ringing up a sale errors with "No query results for model … Account"** →
  the tenant wasn't seeded; run `php artisan tenants:seed` (or step 4a).
- **A tenant subdomain shows the central site or 404s** → `CENTRAL_DOMAIN`
  doesn't match, or the wildcard DNS record is missing.
- **Session/login errors on a tenant page** → make sure `SESSION_DRIVER=file`
  and `CACHE_STORE=file` (step 3).
