# Deployment Guide — 月账 · Monthly Ledger

This walks through moving the app from local XAMPP to a live web host, and
how to restore from a backup if anything ever goes wrong. Follow the main
walkthrough in order the first time you go live; the **Restore Runbook**
section stands alone for disaster recovery later — bookmark it separately.

## Before you start

You'll need, from your host:
- PHP 8.2+ and MySQL 8.0+ / MariaDB 10.6+ (Section 5's target stack).
- FTP/SFTP or a file manager to upload files.
- phpMyAdmin (or equivalent) to create a database and import SQL.
- HTTPS available for your domain — most hosts include a free certificate; enable it before going live.

## 1. Upload the files

Upload the entire project folder to your host, keeping the same structure.
Once live, confirm the project is **not** browsable at `includes/`,
`database/`, `logs/`, or `backups/` — `.htaccess` blocks this, but only if
your host actually honors `.htaccess` (Apache with `AllowOverride All`, or
equivalent on nginx). Test this in the smoke test below before trusting it.

## 2. Create the database

In phpMyAdmin (or your host's database tool):

1. Create a new, empty database with the `utf8mb4_unicode_ci` collation.
2. Create a database user with full privileges on that database only.
3. Import `database/schema.sql`, then `database/seed.sql` — **only if this
   is a genuinely fresh install.** If you're moving your existing XAMPP
   data to the new host, skip both files and follow the Restore Runbook
   below instead (import your latest backup, not the seed).

## 3. Edit includes/config.php

Update these four lines with your host's real database credentials:

```php
define('DB_HOST', 'localhost');   // often stays 'localhost' even on shared hosting
define('DB_NAME', 'your_db_name');
define('DB_USER', 'your_db_user');
define('DB_PASS', 'your_db_password');
```

Nothing else in the app needs to change — every page reads through this
one file, by design (Section 10.1).

## 4. Force HTTPS

Once your host's SSL certificate is active, open `.htaccess` and uncomment
the two lines under "Go-live" (search for that word):

```apache
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
```

This is required, not optional, before the app is safe on the public
internet — session and remember-me cookies carry real login credentials,
and Section 14's threat model assumes HTTPS is on.

## 5. Set production mode

In `includes/config.php`, change:

```php
define('APP_ENV', 'development');
```

to:

```php
define('APP_ENV', 'production');
```

This does three things (Section 17): PHP errors stop displaying on screen,
error details go to `logs/app.log` instead, and visitors see a plain
"Something went wrong — please try again" message if anything breaks,
instead of a stack trace.

**Verify it actually works** — don't assume:

1. Temporarily rename a file the app needs (e.g. `includes/helpers.php` →
   `helpers.php.bak`) and load any page.
2. Confirm you see the generic error message, not a PHP error dump.
3. Confirm the real error detail landed in `logs/app.log`.
4. Rename the file back and confirm the app works again.

## 6. Change both temporary passwords

Log in as each user and change the password immediately (Settings → Change
password). The seeded passwords were only ever meant to get you to this
first login.

## 7. Confirm your host's own backups

Most hosts run their own automated backups (daily/weekly) independent of
this app's one-click backup. Confirm yours does, and note how to request a
restore from them — that's your third safety net alongside the manual
`.sql` download and CSV export (Section 18).

## 8. Smoke test

- [ ] Log in as both users, with and without "stay signed in"
- [ ] Add an expense, edit it, delete it
- [ ] Browse to a past month and a future month
- [ ] Download a CSV export and open it in Excel/Numbers/Sheets
- [ ] Download a full backup and confirm the file isn't empty
- [ ] Confirm `https://yourdomain/includes/config.php`, `.../database/schema.sql`,
      and `.../monthly_ledger_blueprint.md` all return **403**, not file contents
- [ ] Confirm plain `http://` requests redirect to `https://`

---

## Restore Runbook

Keep this section usable on its own — you may be reading it stressed, at
2am, without the rest of this document open. Target: back online in under
15 minutes (Section 18).

1. Create a new, empty database (or fully drop and recreate the existing one).
2. In phpMyAdmin, import the `.sql` file — either the one you downloaded
   from **Settings → Backup**, or your host's own automated backup.
3. Confirm `includes/config.php` still points at the right database (same
   host/name/user/password as before, unless you're also switching hosts —
   in which case update them, per step 3 of the main walkthrough).
4. Log in and check that your most recent entries are present.

The backup file already contains full `CREATE TABLE` statements, so you do
**not** need to run `schema.sql` first — importing the backup alone is
enough to go from a brand-new empty database to a fully working app.

**Retention:** keep the last 6 monthly backups somewhere outside the web
host (Section 18) — Google Drive is a natural home, the same place the old
Excel files lived.

---

## Security Hardening Checklist (Section 14)

Work through this once at go-live, and re-check it after any config change:

- [ ] HTTPS is enforced (`.htaccess` redirect uncommented, valid certificate installed)
- [ ] `APP_ENV` is `'production'` in `includes/config.php`, and step 5's verification above was actually performed
- [ ] Both users have changed their password from the seeded temporary one
- [ ] `includes/`, `database/`, `logs/`, `backups/`, and any `.sql`/`.md` file all return 403 when browsed to directly
- [ ] Host-level automated backups are confirmed active
- [ ] Database credentials in `config.php` are unique to this app, not reused elsewhere
- [ ] No stray `.sql` export or database dump is sitting in the public web root outside `backups/`
