# Deploying the server for production (App Store submission)

Everything so far has run against your Mac (`localhost`/LAN IP). To submit
the app to Apple — even just to unlock IAP sandbox testing, per the "first
consumable IAP must ship with a new app version" requirement — the app
needs to talk to a server that's actually reachable from the internet, not
your laptop.

This assumes: a Linux server instance you already have SSH access to, with
MySQL already installed and running on that same instance (as you said).
Adjust paths/commands if your instance differs (different distro, a
different process manager already in use, etc.) — the structure holds
either way.

## 0. What you need before starting

- SSH access to the instance, and a domain name (or subdomain) you can
  point at it — e.g. `api.yourdomain.com`. Apple's App Transport Security
  requires HTTPS for anything a submitted app talks to; a bare IP address
  with a self-signed cert won't satisfy it. If you don't have a domain yet,
  get one pointed at the instance's IP (an A record) before continuing.
- MySQL reachable on that instance (you said this is already true).
- Node.js — this project was built against **v24**; anything **v20+**
  (current LTS or newer) should work fine. Check with `node --version`;
  install via [nvm](https://github.com/nvm-sh/nvm) or your distro's
  package manager if it's missing or too old.
- `bun` (this repo's package manager — `server/bun.lock` is what's
  committed) or plain `npm` both work; use whichever is already on the
  instance, or install bun: `curl -fsSL https://bun.sh/install | bash`.

## 1. Get the code onto the server

```bash
# On the server, wherever you want the app to live:
git clone <your-repo-url> ytplayer
cd ytplayer/server
bun install   # or: npm install
```

`youtube-dl-exec` downloads a matching `yt-dlp` binary for the host OS
during install — this happens automatically, but it's worth confirming it
actually worked before moving on:

```bash
node -e "require('youtube-dl-exec')('--version').then(v => console.log('yt-dlp', v))" 2>&1 || \
node --input-type=module -e "import youtubedl from 'youtube-dl-exec'; youtubedl('--version').then(v => console.log('yt-dlp', v))"
```

If that errors, check `node_modules/youtube-dl-exec/bin/` for the
downloaded binary and consult that package's README for manual install
steps for your platform.

## 2. Set up the database

Run this from the server (adjust the password — pick a real one, don't
reuse the local dev password):

```bash
mysql -u root -p <<'SQL'
CREATE DATABASE IF NOT EXISTS ytplayer CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'ytplayer'@'localhost' IDENTIFIED BY 'REPLACE_WITH_A_REAL_PASSWORD';
GRANT ALL PRIVILEGES ON ytplayer.* TO 'ytplayer'@'localhost';
FLUSH PRIVILEGES;
SQL

mysql -u ytplayer -p ytplayer < server/db/schema.sql
```

That last command runs the *entire* schema — all 6 tables (`users`,
`purchases`, `app_config`, `credit_products`, `download_history`) plus the
seeded `app_config`/`credit_products` rows (paywall settings, the 3 real
iOS credit tiers). This is a **fresh production database** — it
intentionally does *not* carry over any of the local dev users/test data,
which is exactly what you want (no `sagark1510@gmail.com`/sandbox test
accounts showing up in prod).

Verify it landed correctly:

```bash
mysql -u ytplayer -p ytplayer -e "SHOW TABLES; SELECT * FROM app_config; SELECT * FROM credit_products;"
```

You should see `paywall_enabled=true`, `max_media_size_bytes=1073741824`,
`service_status_override=auto`, and the 3 iOS rows
(`credits_29`/`credits_99`/`credits_299` — 29/99/299 credits) plus the 3
Android placeholder rows.

## 3. Configure environment variables

```bash
cd server
cp .env.example .env
```

Then edit `.env` — here's what changes from the dev values you've been
using, and why:

```bash
PORT=4000

# Real download storage on the server. Make sure this disk has room —
# max_media_size_bytes (1 GiB) times however many concurrent downloads
# you expect. Files get auto-cleaned per JOB_TTL_MS either way.
DOWNLOAD_DIR=./downloads

# MUST be the real public HTTPS URL clients will reach this server at —
# this is what gets embedded in download links returned to the app.
# Not localhost, not a LAN IP.
PUBLIC_BASE_URL=https://api.yourdomain.com

JOB_TTL_MS=3600000
CLEANUP_INTERVAL_MS=600000

# --- Sign in with Google/Apple ---
# SAME values as local dev — these are tied to the OAuth client
# registered in Google Cloud Console / your Apple bundle id, not to
# which server is running the code.
GOOGLE_CLIENT_ID=<same Web Client ID you already have>
APPLE_BUNDLE_ID=com.trentiums.ytdownloadplayer

# Generate a FRESH one for production — do not reuse the dev secret.
# openssl rand -hex 32
JWT_SECRET=<run: openssl rand -hex 32>

# --- Database ---
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=ytplayer
DB_PASSWORD=<the real password you set in step 2>
DB_NAME=ytplayer

# --- In-app purchases ---
ANDROID_PACKAGE_NAME=com.trentiums.ytdownloadplayer
# Leave these three blank for now — purchase verification runs in the
# same DEV MODE it does locally (decodes the JWS without checking
# Apple's signature) until you're ready to set up real App Store Server
# API credentials. Fine for getting the submission unblocked; revisit
# before this handles real paying users at any real volume.
APPLE_ISSUER_ID=
APPLE_IAP_KEY_ID=
APPLE_IAP_PRIVATE_KEY=
```

## 4. Run it under a process manager

`node --watch server.js` (the local dev command) isn't meant to survive a
reboot or crash. Use `pm2`:

```bash
npm install -g pm2
cd server
pm2 start server.js --name ytplayer-api
pm2 save
pm2 startup   # prints a command to run once, to make pm2 survive reboots — run whatever it prints
```

Check it's actually up and connected to MySQL:

```bash
pm2 logs ytplayer-api --lines 30
# should show: "YouTube downloader backend listening on http://localhost:4000" then "[DB] Connected."
curl http://localhost:4000/health
# {"ok":true}
```

## 5. Put HTTPS in front of it (nginx + Let's Encrypt)

```bash
sudo apt install -y nginx certbot python3-certbot-nginx   # Debian/Ubuntu; adjust for your distro
```

Nginx site config (e.g. `/etc/nginx/sites-available/ytplayer-api`):

```nginx
server {
    listen 80;
    server_name api.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:4000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # Downloads can be large and take a while — don't let nginx time
        # them out early.
        proxy_read_timeout 300s;
        client_max_body_size 20m;
    }
}
```

```bash
sudo ln -s /etc/nginx/sites-available/ytplayer-api /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.yourdomain.com   # gets + installs a real cert, sets up auto-renewal
```

Verify from your Mac (not the server):

```bash
curl https://api.yourdomain.com/health
# {"ok":true}
```

## 6. Firewall

Make sure only 80/443 (and 22 for SSH) are open to the world — port 4000
should only be reachable from `localhost` on the instance itself (nginx
proxies to it internally):

```bash
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
sudo ufw status
```

## 7. Point the app at it, before you archive

Two changes on your Mac, in `my-player/`, **before running Product →
Archive** in Xcode:

**`.env`**:
```bash
EXPO_PUBLIC_API_BASE_URL=https://api.yourdomain.com
```
(no `/api` suffix — the app appends that itself)

**`app.json`** — remove the local-dev-only ATS override now that the
server is real HTTPS. Apple's App Review can flag apps that ship with a
blanket `NSAllowsArbitraryLoads: true` with no justification, and it's no
longer needed once everything is HTTPS:
```jsonc
"ios": {
  "infoPlist": {
    "UIBackgroundModes": ["audio"],
    // Remove this whole NSAppTransportSecurity block:
    // "NSAppTransportSecurity": { "NSAllowsArbitraryLoads": true },
    "NSLocalNetworkUsageDescription": "..."
  }
}
```

Then a clean prebuild + archive:
```bash
cd my-player
npx expo prebuild --platform ios --clean
cd ios && RCT_NEW_ARCH_ENABLED=1 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pod install
```
Then open `ios/myplayer.xcworkspace` in Xcode and Product → Archive as
usual.

## 8. Post-deploy sanity checks

Worth doing before archiving, so you're not debugging a broken server at
the same time as an Xcode submission:

- `curl https://api.yourdomain.com/health` → `{"ok":true}`
- Sign in through the app pointed at the new URL, confirm `/api/auth/*`
  and `/api/config` work.
- Try one real download end-to-end — **this is the one most likely to
  surprise you**: datacenter/VPS IPs get flagged by YouTube's anti-bot
  system far more readily than a home network's IP ever did locally. If
  downloads start failing with "Sign in to confirm you're not a bot" or
  similar, that's exactly what `GET /api/service-status` (built earlier)
  exists to detect — check it, and check `pm2 logs` for the raw yt-dlp
  error.
- Confirm `GET /api/purchases/products?platform=ios` returns the 3 real
  products with `creditsGranted: 29/99/299`.

## What's *not* covered here (separate, later)

- Real Apple App Store Server API credentials (moving off "dev mode"
  purchase verification) — see `docs/PLAN_MONETIZATION.md`.
- Log rotation / monitoring / backups for the production MySQL data —
  worth setting up before this has real paying users, not blocking for
  getting the submission unblocked.
- Android production deployment (Google Play credentials, real Play
  Console products) — separate track, not needed for this iOS
  submission.
