New updates

This commit is contained in:
Mark Gregory
2026-09-09 08:23:43 +01:00
parent 868604b848
commit 5b9c19c52f
107 changed files with 11126 additions and 8 deletions
+97
View File
@@ -0,0 +1,97 @@
# Rsync Studio — Commercial Release Stage 3
Stage 3 adds the commercial licensing foundation and 30-day evaluation period. It deliberately does not add payment processing or a production licensing server yet.
## Runtime behaviour
- A new installation receives a 30-day trial on first launch.
- Trial start, last-seen time, installation UUID and installed licence token are stored in macOS Keychain.
- The installation UUID is random. Rsync Studio does not use the Mac serial number, MAC address or other hardware fingerprint.
- A backwards clock movement greater than five minutes causes the trial to pause with a clock-correction message rather than silently extending the trial.
- When the trial expires, background scheduling/file watching are stopped and the operational UI is replaced by the licence-required view.
- Jobs and history remain stored locally and are not deleted.
- A valid signed licence immediately re-enables the application.
Deleting the application's Keychain entries can reset local-only trial state. A production licensing service can close that gap by recording trial/activation state server-side. Stage 3 intentionally avoids invasive hardware fingerprinting just to make a local trial impossible to reset.
## Licence token design
Tokens use:
`RS3.<base64url JSON payload>.<base64url Ed25519 signature>`
The payload contains licence ID, customer display information, edition, installation UUID, issue time, optional expiry and optional maximum major version. Rsync Studio verifies the signature locally with an embedded public key using CryptoKit. The private signing key never belongs in the app, Git repository, release archive or DMG.
## Generate the signing keys
Run once on a trusted Mac:
`swift Scripts/license-keygen.swift`
The script stores the private/public keypair under `~/Library/Application Support/Rsync Studio Licensing/`. Protect and back up the private key. The private key should later be moved into production secret storage used by the licensing service.
Copy the printed public key into the Xcode build setting that generates:
`RsyncStudioLicensePublicKey`
The Stage 3 project contains an empty value by default so no secret/test key is accidentally shipped.
## Create a test offline licence
Copy the Installation ID shown in Settings → Licence, then run:
`swift Scripts/license-sign.swift --installation-id "INSTALLATION-UUID" --name "Test Customer" --email "customer@example.com" --edition "Professional" --max-major 1`
Paste the returned `RS3...` token into Settings → Licence → Install Signed Token.
## Online activation API contract
Set `RsyncStudioLicenseAPIBaseURL` to an HTTPS base URL, for example `https://licensing.example.com/`. Rsync Studio then uses:
### POST /v1/activate
Request:
```json
{
"licenseKey": "XXXX-XXXX-XXXX-XXXX",
"installationID": "random-uuid",
"appVersion": "1.0.0",
"platform": "macOS"
}
```
Success:
```json
{
"licenseToken": "RS3.payload.signature"
}
```
Errors should use a non-2xx response and preferably:
```json
{ "message": "Human-readable error" }
```
### POST /v1/deactivate
Request:
```json
{
"licenseToken": "RS3.payload.signature",
"installationID": "random-uuid"
}
```
A successful response may be `{}` or an empty 2xx body.
## Information sent during activation
Only the licence key, random installation UUID, application version and platform are sent for activation. Deactivation sends the signed licence token and installation UUID. Rsync Studio does not send sync paths, filenames, SSH hosts, SSH keys, credentials, job history, rsync logs or file contents.
## Production boundary
Stage 4 should implement the production licensing service and merchant-of-record/payment webhook integration, licence records, activation limits, signed-token issuance, revocation/deactivation and administrative tooling. The Ed25519 private key belongs there, not in Rsync Studio.
+125
View File
@@ -0,0 +1,125 @@
# Commercial Release Stage 4 — Licensing service and purchase provisioning
Stage 4 adds a production Laravel 13 licensing backend under `Licensing Server/`. The server owns customer licence records, activation limits, revocation and purchase webhook processing. Rsync Studio itself never receives payment card data.
## Security architecture
- Plaintext licence keys are shown once and only a SHA-256 hash plus last four characters are stored.
- The Ed25519 private signing seed stays outside source control and outside the web root.
- The server signs an installation-specific `RS3` token; the Mac verifies it locally with the embedded public key.
- Activation is idempotent for the same installation and locked inside a database transaction to enforce machine limits safely under concurrency.
- Deactivation verifies the signed token before freeing a seat.
- Purchase webhook events are signature-verified against their raw request bodies before JSON parsing and are persisted idempotently.
- Refund/reversal events revoke the corresponding licence.
- No synced paths, filenames, SSH hosts, credentials, logs or file contents are handled by the licensing service.
## Install into Laravel 13
Create a fresh private Laravel application on the server, then copy this overlay into it. Example:
`composer create-project laravel/laravel:^13.0 rsync-studio-licensing`
From the Rsync Studio source tree:
`./"Licensing Server"/install-overlay.sh /var/www/rsync-studio-licensing`
Add `App\\Providers\\RsyncStudioLicensingServiceProvider::class` to `bootstrap/providers.php`. Add `require __DIR__.'/api.stage4.php';` to `routes/api.php`, or rename `api.stage4.php` to your normal API routes file if it is a dedicated service.
Ensure PHP Sodium is enabled, then migrate:
`php artisan migrate`
## Signing key
Use the exact 32-byte Base64 private key created during Stage 3. Keep it outside the repository, for example `/etc/rsync-studio/license-private-key.txt`, readable only by the PHP-FPM account or a tightly controlled deployment group.
Environment:
`RSYNC_STUDIO_LICENSE_PRIVATE_KEY_PATH=/etc/rsync-studio/license-private-key.txt`
`RSYNC_STUDIO_LICENSE_PUBLIC_KEY=THE_STAGE3_PUBLIC_KEY`
Never put the private key into `.env` as a literal if your deployment tooling or diagnostics may expose environment values; the file path approach supports restrictive filesystem permissions and rotation procedures.
## Create products
Personal, one Mac:
`php artisan rsync-studio:product personal --name="Rsync Studio Personal" --edition=Personal --activations=1 --max-major=1`
Professional, three Macs:
`php artisan rsync-studio:product professional --name="Rsync Studio Professional" --edition=Professional --activations=3 --max-major=1`
Business, five Macs:
`php artisan rsync-studio:product business-5 --name="Rsync Studio Business 5" --edition=Business --activations=5 --max-major=1`
Manual support licence:
`php artisan rsync-studio:license:issue professional --name="Customer Name" --email="customer@example.com"`
The plaintext licence key is intentionally printed only at issue time.
## Mac API configuration
Set the signed application's generated Info.plist key `RsyncStudioLicenseAPIBaseURL` to your HTTPS endpoint, for example `https://licensing.example.com/api/`.
The Mac uses:
- `POST /api/v1/activate`
- `POST /api/v1/deactivate`
Stage 3 already implements these client calls.
## Paddle webhook
Set:
`PADDLE_WEBHOOK_SECRET=YOUR_NOTIFICATION_DESTINATION_SECRET`
Configure Paddle to send verified webhooks to:
`https://licensing.example.com/api/v1/webhooks/paddle`
Create the local product mapping with the Paddle price or product ID, for example:
`php artisan rsync-studio:product professional --name="Rsync Studio Professional" --edition=Professional --activations=3 --max-major=1 --provider=paddle --price-id=pri_xxxxx`
The verifier uses the raw body, `Paddle-Signature`, timestamp plus `:` plus raw body, HMAC-SHA256 and a constant-time comparison. The default replay tolerance in this implementation is 300 seconds and can be changed with `PADDLE_WEBHOOK_TOLERANCE`.
## Lemon Squeezy webhook
Set:
`LEMON_SQUEEZY_WEBHOOK_SECRET=YOUR_SIGNING_SECRET`
Webhook URL:
`https://licensing.example.com/api/v1/webhooks/lemon-squeezy`
Map the Lemon Squeezy variant ID as the external price ID:
`php artisan rsync-studio:product professional --name="Rsync Studio Professional" --edition=Professional --activations=3 --max-major=1 --provider=lemon-squeezy --price-id=YOUR_VARIANT_ID`
The raw request body is HMAC-SHA256 signed with the configured secret and compared with the `X-Signature` header before processing.
## Webhook delivery of the licence key
Purchase-created plaintext keys are placed into `license_delivery_outbox` encrypted with Laravel's application encryption key. They are not logged and are not stored in plaintext. Stage 5 will consume that outbox to send the transactional licence email/customer portal response and then mark or purge the delivery record. Manual `rsync-studio:license:issue` keys are printed once and are not queued. This prevents Stage 4 purchases from creating an unrecoverable key before the delivery layer exists.
## Deployment minimum
- HTTPS only.
- Production Laravel settings and `APP_DEBUG=false`.
- MySQL/PostgreSQL with backups.
- Redis-backed rate limiting/cache recommended.
- Webhook routes excluded from CSRF only when placed in the API route stack.
- Restrict server/private-key permissions.
- Do not expose database/admin ports to the public Internet.
- Alert on failed webhooks and repeated activation abuse.
- Back up licence/customer data and the signing seed separately.
## Stage boundary
Stage 4 creates and protects the commercial licence source of truth. Stage 5 should add customer delivery and self-service: checkout website, transactional licence email, customer portal/resend/deactivate-all flow, support/admin UI, and a clean purchase button inside Rsync Studio.
+47
View File
@@ -0,0 +1,47 @@
# Rsync Studio Commercial Release — Stage 5
Stage 5 adds the authenticated customer and administrator experience to the Stage 4 licensing API.
## Security model
- Mac activation remains licence-key based and rate limited.
- Customer portal uses Laravel sessions, CSRF protection, password hashing, session regeneration and email verification.
- Portal licence ownership is matched only to the authenticated **verified** email address.
- Admin access requires an explicit `is_admin` database flag.
- Login and registration endpoints are throttled by the licensing service provider.
- Password reset uses Laravel's signed/reset-token flow.
- Full purchase-created licence keys remain encrypted at rest in `license_delivery_outbox`; they are never shown in the portal and may only be mailed to the verified owner.
- Manual licence keys remain non-recoverable by design.
## Install / update
Run the overlay from the Rsync Studio repository root:
`./"Licensing Server"/install-overlay.sh /path/to/rsync-studio-licensing`
Then run:
`php artisan migrate && php artisan optimize:clear`
Create the first administrator:
`php artisan rsync-studio:admin admin@example.com --name="Rsync Studio Admin"`
The temporary password is printed once for a newly created admin. Sign in and change/reset it before production use.
## Required production environment
Set HTTPS `APP_URL`, production mail settings, database/session/cache/queue settings, the Stage 4 signing keys and payment webhook secrets. Add:
- `RSYNC_STUDIO_PURCHASE_URL`
- `RSYNC_STUDIO_PORTAL_URL`
- `RSYNC_STUDIO_SUPPORT_URL`
The macOS app can embed those HTTPS addresses in its generated Info.plist.
## Customer flow
1. Checkout provider posts a verified webhook.
2. Stage 4 provisions the licence and encrypts its one-time key in the delivery outbox.
3. Delivery mail sends the key to the purchase email.
4. Customer creates a portal account with that same email and verifies it.
5. Portal exposes licence status and active installations, allows seat deactivation and secure key resend when the encrypted delivery record exists.
## Admin flow
The admin dashboard is behind `auth`, `verified`, and `AdminOnly` middleware. It supports search, licence inspection, activations, revoke and restore.
+5
View File
@@ -112,3 +112,8 @@ The exact signed bytes are UTF-8: `version + "\n" + build + "\n" + downloadURL`.
Stage 2 adds `Scripts/release.sh`, which automates archive/export, signed-app verification, drag-to-Applications DMG creation, Apple notarization, stapling, Gatekeeper verification, SHA-256 generation and release metadata. It intentionally does **not** publish artifacts.
See `Docs/COMMERCIAL_RELEASE_STAGE2.md` for first-time setup and usage.
## Stage 4 licensing backend
See `Docs/COMMERCIAL_RELEASE_STAGE4.md` and the `Licensing Server/` Laravel overlay for production activation, purchase provisioning and webhook handling.