feat(save-transfer): add saveutil CLI and token-gated import endpoint

Adds two complementary paths for transferring character save data between
Erupe instances without breaking the SHA-256 integrity check system:

- `cmd/saveutil/`: admin CLI with `import`, `export`, `grant-import`, and
  `revoke-import` subcommands. Direct DB access; no server running required.
- `POST /v2/characters/{id}/import`: player-facing API endpoint gated behind
  a one-time token issued by `saveutil grant-import` (default TTL 24 h).
  Token is validated and consumed atomically to prevent TOCTOU races.
- Migration `0013_save_transfer`: `savedata_import_token` and
  `savedata_import_token_expiry` columns on `characters` table.
- Both paths decompress incoming savedata and recompute the SHA-256 hash
  server-side, so the integrity check remains valid after import.
- README documents both methods and the per-character hash-reset workaround.

Closes #183.
This commit is contained in:
Houmgaor
2026-03-21 20:14:58 +01:00
parent dbbfb927f8
commit 5fe1b22550
9 changed files with 679 additions and 0 deletions

View File

@@ -130,6 +130,64 @@ Edit `config.json` before starting the server. The essential settings are:
`config.example.json` is intentionally minimal — all other settings have sane defaults built into the server. For the full configuration reference (gameplay multipliers, debug options, Discord integration, in-game commands, entrance/channel definitions), see [config.reference.json](./config.reference.json) and the [Erupe Wiki](https://github.com/Mezeporta/Erupe/wiki).
## Save Transfers
To move a character from one Erupe instance to another, use the `saveutil` admin tool.
### Build saveutil
```bash
go build -o saveutil ./cmd/saveutil/
```
### Method 1: Direct admin import (recommended)
This method does not require the server to be running.
**On the source server**, export the character:
```bash
./saveutil export --config config.json --char-id <SOURCE_ID> --output my_character.json
```
**On the destination server**, find the target character ID (use pgAdmin or `psql`), then import:
```bash
./saveutil import --config config.json --char-id <DEST_ID> --file my_character.json
```
### Method 2: Player self-service via API
This method lets players import their own save without admin DB access, but requires the admin to grant a one-time token first.
**Admin step** — grant a token (valid for 24 hours by default):
```bash
./saveutil grant-import --config config.json --char-id <DEST_ID> [--ttl 48h]
# → Import token for character 42: abc123...
```
Give the printed token to the player. They then call the import endpoint:
```bash
curl -X POST http://<server>:8080/v2/characters/<DEST_ID>/import \
-H "Authorization: Bearer <player_token>" \
-H "Content-Type: application/json" \
-d '{
"import_token": "<admin_token>",
"character": <paste contents of my_character.json .character field here>
}'
```
The token is consumed on success and cannot be reused. To cancel a pending grant:
```bash
./saveutil revoke-import --config config.json --char-id <DEST_ID>
```
### Troubleshooting
**"savedata integrity check failed"** — the character was imported directly into the DB without going through `saveutil`. Fix by clearing the stored hash:
```sql
UPDATE characters SET savedata_hash = NULL WHERE id = <char_id>;
```
The correct hash will be recomputed on the next save.
## Features
- **Multi-version Support**: Compatible with all Monster Hunter Frontier versions from Season 6.0 to ZZ