# Deployment guide

**Fastest path:** on a fresh Ubuntu 24.04 server run, as root,
`DOMAIN=club.example.org ADMIN_EMAIL=you@example.org deploy/provision-ubuntu.sh cioclub-<version>.zip`.
It does sections 1–7 below for you. Later releases: `deploy/release.sh cioclub-<version>.zip` (working hours only).
The manual steps below remain for other platforms.

## 1. Server requirements
- PHP 8.3 with extensions: pdo_mysql, mbstring, intl, curl, gd, **xml (dom)**, openssl, fileinfo
  (Ubuntu: `apt install php8.3-fpm php8.3-mysql php8.3-mbstring php8.3-intl php8.3-curl php8.3-gd php8.3-xml`)
- MySQL 8 or MariaDB 10.6+ (utf8mb4)
- Nginx or Apache, HTTPS certificate
- Cron

Check: `php -m | grep -E 'pdo_mysql|mbstring|intl|curl|gd|dom'` must list all six.

## 2. Files
Upload the folder. **Only `public/` is the web root.** `config.php`, `lib/`, `pages/`, `lang/`, `sql/`, `bin/`,
`storage/` must not be reachable from the web.

    chown -R www-data:www-data storage public/uploads
    chmod -R 750 storage public/uploads

## 3. Database
Create an empty database and two accounts:

    CREATE DATABASE cioclub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'cioclub_app'@'localhost' IDENTIFIED BY '<strong password>';
    GRANT SELECT, INSERT, UPDATE, DELETE ON cioclub.* TO 'cioclub_app'@'localhost';

The application account has no DDL rights by design. The installer uses a DBA account once and never stores it.

## 4. Configure
    cp config.sample.php config.php

Set `base_url` (https, no trailing slash), `app_key` (64 random characters: `php -r "echo bin2hex(random_bytes(32));"`),
database credentials, and set `env` to `production`.

## 5. Install
    php bin/install.php --dba-user=root --dba-pass='...' --admin-email=you@caresoftfoundation.org --admin-first=Smeetha --admin-last=Kumar

It prints a one-time password for the super admin; you must change it at first sign-in.
Re-running the installer later applies only new SQL files (ledger in `schema_migrations`). Never edit an applied SQL
file; ship changes as a new numbered file (`sql/03_....sql`).

## 6. Web server
**Nginx**

    server {
        server_name www.example.org;
        root /var/www/cioclub/public;
        index index.php;
        client_max_body_size 8M;
        location ^~ /uploads/ { location ~ \.php$ { deny all; } try_files $uri =404; }
        location / { try_files $uri /index.php?$query_string; }
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root/index.php;
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        }
        add_header Strict-Transport-Security "max-age=31536000" always;
    }

**Apache**: point DocumentRoot at `public/`; the included `.htaccess` files handle routing and block PHP in uploads
(`AllowOverride All`, `mod_rewrite` on).

Delete `public/router.php` in production (it is only for `php -S`).

## 7. Cron (every minute)
Also expires threat-intel posts and stale unpaid checkouts, updates the AI search index, sends membership renewal reminders and event reminders, and delivers push notifications.

    * * * * * php /var/www/cioclub/bin/cron.php >> /var/www/cioclub/storage/logs/cron.log 2>&1

Publishes scheduled articles, sends queued email and WhatsApp (retries 5 times with back-off), ends expired ad
campaigns, clears old rate-limit rows. Safe if two runs overlap.

## 8. Amazon SES
1. Verify the sending domain in SES (DKIM + SPF + DMARC records) in the region you choose. Request production access.
2. Create an IAM user with only `ses:SendEmail`; put its key and secret in `config.php`, set `mail.driver` to `ses`.
3. Bounces and complaints: create a configuration set, add an SNS destination for Bounce and Complaint events,
   subscribe the HTTPS endpoint `https://<your-domain>/hooks/ses`. The endpoint verifies the SNS signature and confirms
   the subscription automatically. Suppressed addresses appear under Admin, Email & WhatsApp.
4. The mail layer is one function (`send_email_now` in `lib/comm.php`), so Caresoft's own mail server can replace SES
   later without touching the rest of the app.

## 9. WhatsApp Cloud API
1. In Meta Business Manager create a WhatsApp Business account and phone number; copy the phone number ID and a
   permanent token into `config.php`, set `whatsapp.driver` to `cloud`.
2. Create and get approval for these templates (body parameters in this order):
   - `membership_approved`: {{1}} first name, {{2}} club name, {{3}} sign-in link
   - `event_registered`: {{1}} first name, {{2}} event title, {{3}} date and time, {{4}} check-in code
   - `query_received`: {{1}} first name, {{2}} reference
   Add language versions (ar, fr, es) in Meta and matching rows under Admin, Email & WhatsApp, Templates.
3. Only members who tick the opt-in and give a phone number receive WhatsApp messages.

## 10. Stripe
1. In the Stripe dashboard copy the secret key into `config.php` (`stripe.secret_key`) and set `stripe.driver` to `live`.
   Keep `fake` only on development machines; the fake checkout page refuses to work when the driver is `live`.
2. Add a webhook endpoint `https://<your-domain>/hooks/stripe` with events `checkout.session.completed`,
   `checkout.session.expired` and `charge.refunded`. Copy its signing secret into `stripe.webhook_secret`.
3. Enable the currencies you sell in (Admin, Settings) and keep the exchange rates current; the USD rate card is converted
   unless you set a local price (Admin, Ads, Rate card).
4. Stripe accepts new Indian businesses by invitation only. Confirm eligibility for the operating entity early; the payment
   code is isolated in `lib/stripe.php` if another provider is needed for some markets.
5. Refunds: issue them in Stripe. The webhook marks the payment refunded and pauses the campaign.

## 11. AI (Claude)
1. Create an Anthropic API key and put it in `config.php` (`ai.api_key`); set `ai.driver` to `anthropic`. The model is
   configurable (`ai.model`).
2. Admin, AI assistant & usage: set the daily limit per member, rebuild the search index after restoring a backup, and see
   token usage for the last 30 days.
3. What is sent to the AI: the member's question plus excerpts of club content that member can already see, with authors
   replaced by role. Threat-intel posts, other circles and personal data are never sent. Mention this in the privacy notice.
4. New languages: Admin, AI assistant & usage, "Draft a new language", or `php bin/ai_translate_lang.php pt`. Review the
   drafts in Translations with a native speaker, then switch the language on in Settings.

## 12. Mobile app
The site is an installable web app (Add to Home Screen on Android and iOS). For store listings, wrap
`https://<domain>/en/app` with the Caresoft webview app builder; the manifest, icons and offline page are already in place.
The service worker never caches member pages, so private content does not persist on shared devices.

## 13. Push notifications
1. Run `php bin/vapid_keys.php` once and paste the output into `config.php` under `push`; set `subject` to a monitored
   mailbox. Never change the keys afterwards, or every member will have to turn push on again.
2. HTTPS is required. iOS shows web push only when the site is added to the Home Screen (iOS 16.4+) or wrapped as an app.
3. Members turn push on per device in Settings. Reminders and notifications are delivered by the cron worker.

## 14. CareMeet live roundtables
1. Ask the CareMeet team to implement `docs/CAREMEET_API.md`, then set `caremeet.driver` to `api` with `base_url`,
   `api_key`, `join_secret` and `webhook_secret` (32+ random characters each; share the two secrets with CareMeet only).
2. CareMeet must allow framing by the club domain (`frame-ancestors`) and call `https://<domain>/hooks/caremeet`.
3. In Admin, Events choose "CareMeet room inside the club". The room opens 15 minutes before the start; hosts can enter an
   hour early. Attendance and CPE are recorded automatically; recordings appear for registrants when ready.

## 15. Go-live checklist
- [ ] Launch content seeded (`php bin/seed_launch_content.php`) and reviewed against docs/LAUNCH_CONTENT.md
- [ ] Brand name set (Admin, Settings) once the domain is confirmed
- [ ] Legal review of privacy notice and terms (template wording, English only)
- [ ] `env` = production, `display_errors` off, HTTPS forced
- [ ] SES out of sandbox, DKIM passing, SNS subscription confirmed
- [ ] Stripe live key, webhook secret, test payment and refund done on staging
- [ ] Exchange rates reviewed (Admin, Settings)
- [ ] Vendor list seeded and healthcare ad policy published
- [ ] Anthropic key set, AI usage limit chosen, privacy notice mentions AI processing
- [ ] First masterclass published, first awards cycle drafted
- [ ] Every admin has set up two-step verification (enforced; check Admin → Admins & roles)
- [ ] `php tests/security.php https://<staging>` passes; VAPT round booked with docs/vapt/VAPT_PACK.md
- [ ] Backup key copied off the server; first restore drill done
- [ ] fail2ban jail active (`fail2ban-client status cioclub`)
- [ ] VAPID keys generated and push tested on Android, desktop and an iOS Home Screen app
- [ ] CareMeet driver set to api and one test roundtable run end to end (join, attendance, recording)
- [ ] WhatsApp templates approved
- [ ] Cron running (check `storage/logs/cron.log`)
- [ ] Daily database backup (e.g. `mysqldump --single-transaction cioclub | gzip > backup-$(date +%F).sql.gz`) plus
      `public/uploads`
- [ ] First country admins appointed (Admin, Admins & roles)
- [ ] House ads reviewed (Admin, Ads)
- [ ] Submit `https://<domain>/sitemap.xml` to Google Search Console and Bing Webmaster Tools
- [ ] Run both test suites against staging with log/fake drivers (never against production)

Caresoft practice: publish updates during working hours only.
