# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Local Development Server

```bash
php -S 127.0.0.1:3000 router.php
```

The `router.php` handles static file serving, gzip, ETag caching, and PHP dispatch for the built-in server. No build step is required.

## Cron Jobs

**Policy: cron location is server-specific.**
- Shop-server crons → `__CRON/` only. Never register these on IKEv2 server.
- IKEv2-server crons → `IKEv2SellerOffice4U/Crons/` only. Never register these on shop server.

Run consistency checks and retry queues manually:
```bash
# Dry-run consistency check (shop DB as master)
php __CRON/cron_ikev2_consistency_check_cli.php --limit=100

# Auto-fix inconsistencies (re-syncs vpnid_list + requeues provisioning)
php __CRON/cron_ikev2_consistency_check_cli.php --apply-fix --limit=100

# Retry failed vpnid sync jobs
php __CRON/cron_ikev2_vpnid_sync_retry_cli.php --limit=50

# Shop↔VPN sync healthcheck (IKEv2 server only)
php IKEv2SellerOffice4U/Crons/cron_shop_sync_healthcheck.php --json
php IKEv2SellerOffice4U/Crons/cron_shop_sync_code_summary.php --limit=100 --json
```

## High-Level Architecture

### Layers

This is a **Gnuboard5** (Korean PHP CMS v5.3.3.3) instance extended with a custom VPN subscription business on top.

```
Browser / Mobile App
        │
  Gnuboard5 core (common.php → config.php → dbconfig.php)
        │
  Shop module (shop/ or shoppoint/)
        │ purchase event
  mylib/ikev2_provision.lib.php   ← VPN provisioning hook
        │
  app_ikev2_provision_queue (MySQL)
  app_ikev2_vpnid_sync_queue (MySQL)
        │
  IKEv2 VPN Backend (separate server)
  IKEv2SellerOffice4U/  ← management panel on that server
        │
  ipsec.secrets (file)  +  user_billing.json (file)
  vpnid_list (MySQL on IKEv2 server)
```

### Key Directories

| Path | Role |
|---|---|
| `common.php` | Gnuboard bootstrap – loaded by every page |
| `config.php` | Constants (paths, DB name, feature flags) |
| `mylib/` | Custom VPN provisioning & business logic |
| `mylib/ikev2_provision.lib.php` | Core provisioning library (queuing, native purchase hook, compensation) |
| `mylib/_common.php` | Entry point for mylib AJAX/API files |
| `shop/` | Shop module (product purchase, cart, order, settle) |
| `shoppoint/` | Points-based shop variant |
| `IKEv2SellerOffice4U/` | VPN admin panel for Ubuntu20 IKEv2 servers |
| `IKEv2SellerOffice/` | VPN admin panel for CentOS7 IKEv2 servers (legacy) |
| `IKEv2SellerOffice4U/Crons/` | IKEv2-server cron scripts |
| `__CRON/` | Shop-server cron scripts |
| `autosync/` | Auto-sync utilities between servers |
| `adm/` | Gnuboard admin panel |
| `__DbMgR/` | PHPMyAdmin installation |
| `__api/` | REST/Python API endpoints |

### Shop ↔ VPN Backend Sync

The shop sends a signed HTTP POST to `mylib/ikev2_shop_sync_event_receiver.php` on the VPN server side. Trust is established via:

- Header `X-VPN-Sync-Signature`: HMAC-SHA256 of the **raw request body**
- Header or POST field `X-VPN-Sync-Origin` / `sync_origin`: must be `shop_backend`, `shop_sync`, or `shop_update`
- Shared secret configured in `.env` as `IKEV2_SHOP_SYNC_SHARED_SECRET`

Allowed backend actions (whitelist): `add_user`, `update_user`, `delete_user`, `extend_expire`, `charge_points`, `withdraw_points`, `enable_user`, `disable_user`.

Shop-managed (`shop-managed`) accounts cannot be manually modified through the VPN admin panel – only backend-signed requests are accepted.

### VPN Credential Storage

- **Authentication/access metadata**: `ipsec.secrets` (text file on IKEv2 server)
- **Billing config**: `IKEv2SellerOffice4U/data/user_billing.json`
- **Provisioned profiles**: `vpnid_list` MySQL table

Line format in `ipsec.secrets`:
```
vpn1001 : EAP "password" # expire:YYYY-mm-dd HH:ii:ss|memo:[source:...]|admin:shop_auto|created:...
```

### Custom MySQL Tables

These are not part of standard Gnuboard; they are project-specific:

| Table | Purpose |
|---|---|
| `vpnid_list` | Provisioned VPN user IDs (source of truth per member + server) |
| `app_ikev2_provision_queue` | Retry queue for pushing credentials to IKEv2 servers |
| `app_ikev2_vpnid_sync_queue` | Retry queue for syncing vpnid_list from order/cart data |
| `vpn_native_buy_sales` | Native (non-web) purchase line records |
| `vpn_native_buy_order_map` | Idempotency map for native purchases (order_id → shop od_id) |

The shop's canonical order state lives in Gnuboard's standard tables: `g5_shop_order_table`, `g5_shop_cart_table`, and `g5_shop_item_table`. Shop DB is treated as master; `vpnid_list` and provisioning queues are derived from it.

### Native Purchase Hook

`vpnlink_after_native_vpn_purchase()` in `mylib/_common.php` is the main entry point for in-app (non-web) VPN purchases. It:
1. Checks idempotency via `vpn_native_buy_order_map`
2. Creates Gnuboard shop order + cart rows
3. Deducts member points via `insert_point()`
4. Calls `apms_order()` for affiliate handling
5. Syncs `extension_of_period_*` fields
6. Queues IKEv2 provisioning

Compensation (rollback) is handled by `vpnlink_native_purchase_compensate_order()`.

### Gnuboard Bootstrap Guard

Every standalone-runnable PHP file that uses Gnuboard functions must check:
```php
if (!defined('_GNUBOARD_')) exit;
```
Files that load `common.php` directly should use `include_once(__DIR__ . '/../common.php')` and then define `_GNUBOARD_` is set automatically.

### Secret Resolution Priority

`vpnlink_ikev2_secret()` in `mylib/ikev2_provision.lib.php` resolves the provisioning shared secret in this order:
1. `$_SERVER['IKEV2_PROVISION_SHARED_SECRET']` / `$_ENV[...]` / `getenv(...)`
2. `mylib/ikev2_secret.php` (PHP file returning array or string)
3. `$config['cf_10']` (Gnuboard admin config field)

The `.env` file at the project root is read by `IKEv2SellerOffice4U/config.php`'s `vpn_env_get()` helper for the VPN panel side.

## Operational Runbooks

- Consistency check & repair: `ikev2_master_consistency_runbook.md`
- Shop↔VPN sync healthcheck: `ikev2_shop_sync_healthcheck_runbook.md`
- Backend action request spec: `ikev2_shop_to_vpn_backend_action_request_spec.md`
- Order payload mapping: `ikev2_shop_payload_mapping.md`
- Idempotency QA: `idempotency_qa_checklist.md`
