paddel setup new verifier
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
# Rsync Studio — Security / Release Audit
|
||||
|
||||
This project targets direct macOS distribution using Developer ID signing and Apple notarisation. App Sandbox is intentionally disabled because the product launches `rsync`/SSH and operates on arbitrary user-configured filesystem paths.
|
||||
|
||||
## Security controls present
|
||||
|
||||
- Hardened Runtime enabled for Debug and Release target configurations.
|
||||
- No shell (`/bin/sh -c`) command construction; `Process` uses an absolute executable path and argument array.
|
||||
- SSH host-key checking is not disabled.
|
||||
- Automated SSH uses BatchMode, timeout and keepalive limits.
|
||||
- SSH password authentication/storage is not supported; legacy stored password data is deleted.
|
||||
- Remote host, username, port, key path and remote destination are validated.
|
||||
- Remote paths reject shell metacharacters before rsync can invoke the remote shell.
|
||||
- Advanced arguments reject transport replacement, remote-command, credential, arbitrary list/log/batch path and destructive overrides.
|
||||
- Malformed Advanced argument quoting/trailing escapes are rejected.
|
||||
- `--` terminates rsync option parsing before source/destination operands.
|
||||
- Local source/destination overlap is rejected.
|
||||
- File-watch and exclusion values reject control characters.
|
||||
- Scheduled/watch delete requires explicit per-job authorisation.
|
||||
- Run logs are ANSI-cleaned, likely credentials redacted and size-capped before persistence.
|
||||
- Run history has bounded retention.
|
||||
- Privacy manifest declares no tracking/no collected data and the app-specific UserDefaults required reason.
|
||||
- Launch at login uses `SMAppService` and remains user-controlled.
|
||||
- No analytics/telemetry or application HTTP client code is present.
|
||||
|
||||
## Required release verification on a Mac
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
./Scripts/release-preflight.sh
|
||||
```
|
||||
|
||||
Then archive with Developer ID, submit with Apple `notarytool` (or Xcode Organizer), staple the ticket and run the signature/Gatekeeper checks in `Docs/RELEASE.md`.
|
||||
|
||||
A Linux source audit cannot substitute for Xcode's macOS SDK typecheck, code signing, notarisation or Gatekeeper assessment.
|
||||
@@ -1,88 +0,0 @@
|
||||
# Rsync Studio — Commercial Release Stage 1
|
||||
|
||||
This document freezes the commercial distribution identity and release-security baseline for Rsync Studio 1.0.0.
|
||||
|
||||
## Product identity
|
||||
|
||||
- Product: **Rsync Studio**
|
||||
- Bundle identifier: **`net.absdev.RsyncStudio`**
|
||||
- Marketing version: **`1.0.0`**
|
||||
- Build number: **`1`**
|
||||
- Minimum macOS: **14.0**
|
||||
- Category: **Utilities**
|
||||
- Distribution model: **Direct distribution outside the Mac App Store**
|
||||
|
||||
The bundle identifier is now treated as permanent product identity. Do not change it between commercial releases. Changing it creates a different application identity for macOS, Keychain access, preferences, update logic and future licensing.
|
||||
|
||||
## Apple signing baseline
|
||||
|
||||
Rsync Studio must ship with:
|
||||
|
||||
1. An active Apple Developer Program membership.
|
||||
2. A **Developer ID Application** certificate belonging to the publisher's Apple Developer Team.
|
||||
3. Hardened Runtime enabled.
|
||||
4. No `get-task-allow` entitlement in a Release archive.
|
||||
5. Successful Apple notarization.
|
||||
6. A stapled notarization ticket before publication.
|
||||
7. Successful Gatekeeper assessment of the exact artifact customers will download.
|
||||
|
||||
The Apple Team ID and certificate's full identity are deliberately not committed into the repository. Select the correct team in Xcode on the controlled release Mac. This avoids embedding developer-account-specific credentials into source control.
|
||||
|
||||
## App Sandbox
|
||||
|
||||
The App Sandbox remains intentionally disabled. Rsync Studio launches command-line synchronization/SSH tooling and must work with arbitrary user-selected filesystem locations. This is a direct-distribution architecture decision, not an accidental security relaxation.
|
||||
|
||||
Hardened Runtime remains mandatory.
|
||||
|
||||
## Versioning policy
|
||||
|
||||
Use semantic product versions:
|
||||
|
||||
- `1.0.0`, `1.0.1`, `1.1.0`, `2.0.0`
|
||||
|
||||
Increment `CURRENT_PROJECT_VERSION` for every build that may be distributed. Never publish two different binaries using the same version/build pair.
|
||||
|
||||
## Required Stage 1 checks
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
./Scripts/verify-release-config.sh
|
||||
./Scripts/security-check.sh
|
||||
./Scripts/release-preflight.sh
|
||||
```
|
||||
|
||||
`release-preflight.sh` must be run on macOS with Xcode installed. It performs a clean Release build without signing so compiler/SDK failures are caught before creating the signed archive.
|
||||
|
||||
## First signed archive checklist
|
||||
|
||||
On the release Mac:
|
||||
|
||||
1. Open `Rsync Studio.xcodeproj` in Xcode.
|
||||
2. Select the **Rsync Studio** target → **Signing & Capabilities**.
|
||||
3. Select the publisher's Apple Developer Team.
|
||||
4. Confirm Hardened Runtime is enabled.
|
||||
5. Archive a **Release** build for macOS.
|
||||
6. In Organizer choose direct/Developer ID distribution.
|
||||
7. Confirm the archive is signed with **Developer ID Application**, not Apple Development.
|
||||
8. Notarize the exported app/container with Apple.
|
||||
9. Staple the ticket.
|
||||
10. Verify the exact final artifact before uploading it anywhere.
|
||||
|
||||
## Signed artifact verification
|
||||
|
||||
For the exported application:
|
||||
|
||||
```bash
|
||||
codesign --verify --deep --strict --verbose=2 "/path/to/Rsync Studio.app"
|
||||
codesign -dv --verbose=4 "/path/to/Rsync Studio.app"
|
||||
codesign -d --entitlements :- "/path/to/Rsync Studio.app"
|
||||
spctl --assess --type execute --verbose=4 "/path/to/Rsync Studio.app"
|
||||
xcrun stapler validate "/path/to/Rsync Studio.app"
|
||||
```
|
||||
|
||||
The signing output must identify a Developer ID Application certificate belonging to the intended publisher. The entitlements must not contain `com.apple.security.get-task-allow`.
|
||||
|
||||
## Stage boundary
|
||||
|
||||
Stage 1 does **not** add customer licensing, payment handling, trial enforcement, DMG artwork, automatic installation of updates, or a licensing server. Those are intentionally separate stages so signing/distribution identity is stable before commercial logic is introduced.
|
||||
@@ -1,104 +0,0 @@
|
||||
# Rsync Studio — Commercial Release Stage 2
|
||||
|
||||
Stage 2 adds a reproducible, non-publishing direct-distribution release pipeline for macOS.
|
||||
|
||||
## What the pipeline does
|
||||
|
||||
`Scripts/release.sh` performs these steps in order:
|
||||
|
||||
1. Runs the existing release/security preflight.
|
||||
2. Creates a clean Xcode Release archive.
|
||||
3. Exports the archive using Apple's `developer-id` distribution method.
|
||||
4. Verifies that the exported application is signed by **Developer ID Application** and has Hardened Runtime enabled.
|
||||
5. Rejects `get-task-allow` and an unexpectedly sandboxed Release app.
|
||||
6. Builds a compressed drag-to-Applications DMG using `hdiutil`.
|
||||
7. Validates the configured `notarytool` Keychain profile.
|
||||
8. Notarizes the exported app inside a temporary ZIP, staples its ticket directly to the app, validates it, then removes the temporary ZIP.
|
||||
9. Builds the final DMG from that already-notarized/stapled app and validates the DMG container.
|
||||
10. Submits the final DMG to Apple's notarization service and waits for the result.
|
||||
11. Staples and validates the DMG notarization ticket.
|
||||
12. Re-runs Gatekeeper/signature checks after notarization.
|
||||
13. Produces a SHA-256 checksum and release metadata file.
|
||||
14. Stops without uploading or publishing anything.
|
||||
|
||||
Existing release directories are immutable: the script refuses to overwrite `build/releases/<version>-<build>`. Increment the build number for a replacement build.
|
||||
|
||||
## One-time Mac setup
|
||||
|
||||
Install/sign into the current Xcode release and make sure a **Developer ID Application** certificate for your Apple Developer team is available in Keychain Access/Xcode.
|
||||
|
||||
Store notarization credentials once:
|
||||
|
||||
```bash
|
||||
xcrun notarytool store-credentials "Rsync Studio Notary"
|
||||
```
|
||||
|
||||
Apple will prompt for the required account/team credentials. The release script never stores those credentials in the source repository.
|
||||
|
||||
If Xcode cannot infer your team, run releases with your 10-character Apple Developer Team ID:
|
||||
|
||||
```bash
|
||||
DEVELOPMENT_TEAM=YOURTEAMID ./Scripts/release.sh
|
||||
```
|
||||
|
||||
Do not commit the Team ID solely because a local machine needs it; Xcode signing configuration can supply it.
|
||||
|
||||
## Produce a commercial build
|
||||
|
||||
From the repository root on the release Mac:
|
||||
|
||||
```bash
|
||||
./Scripts/release.sh
|
||||
```
|
||||
|
||||
To use another notarization Keychain profile:
|
||||
|
||||
```bash
|
||||
./Scripts/release.sh --notary-profile MyNotaryProfile
|
||||
```
|
||||
|
||||
For a signing/package smoke test without Apple notarization:
|
||||
|
||||
```bash
|
||||
./Scripts/release.sh --skip-notarization
|
||||
```
|
||||
|
||||
A skipped-notarization build is **not** suitable for customer distribution.
|
||||
|
||||
## Output
|
||||
|
||||
For version `1.0.0`, build `1`, the output is:
|
||||
|
||||
```text
|
||||
build/releases/1.0.0-1/
|
||||
├── Rsync Studio.xcarchive
|
||||
├── export/
|
||||
│ └── Rsync Studio.app
|
||||
├── Rsync Studio-1.0.0.dmg
|
||||
├── Rsync Studio-1.0.0.dmg.sha256
|
||||
└── release-info.txt
|
||||
```
|
||||
|
||||
The release directory deliberately includes the archive and exported application so a shipped artifact can be traced back to the exact signed archive.
|
||||
|
||||
## Mandatory clean-Mac test before publishing
|
||||
|
||||
Do not publish immediately after the script succeeds. Copy or download the final DMG onto a Mac/account that has not built Rsync Studio in Xcode, then:
|
||||
|
||||
1. Open the DMG.
|
||||
2. Drag **Rsync Studio** into Applications.
|
||||
3. Launch it from Applications.
|
||||
4. Confirm Gatekeeper opens it normally with no unidentified-developer warning.
|
||||
5. Create a harmless local dry-run job.
|
||||
6. Verify SSH/local workflows required for the release.
|
||||
7. Verify About reports the intended version/build.
|
||||
8. Verify the app icon, Help menu, notifications and Launch at Login behaviour.
|
||||
9. Eject/delete the DMG and repeat once from the actual hosted download before announcing the release.
|
||||
|
||||
## Release artifact policy
|
||||
|
||||
Never replace `Rsync Studio-1.0.0.dmg` with different bytes after it has been published. If the build changes, increment `CURRENT_PROJECT_VERSION` and create a new release artifact. Keep the published SHA-256 alongside the download.
|
||||
|
||||
## What Stage 2 intentionally does not do
|
||||
|
||||
It does not upload to a website/CDN, update the signed update manifest, publish release notes, create licences, take payments or activate customers. Those operations are deliberately separated so a successful local build cannot accidentally become a public release.
|
||||
@@ -1,97 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,125 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,47 +0,0 @@
|
||||
# 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.
|
||||
@@ -42,3 +42,10 @@ The app is configured for:
|
||||
- The current stage Ed25519 licence public key is embedded in `Info.plist`.
|
||||
|
||||
The signing private seed remains server-only.
|
||||
|
||||
|
||||
## Remote portal deactivation
|
||||
|
||||
Rsync Studio revalidates an installed licence against `POST /api/v1/validate` on launch/resume and every 24 hours while running. The validation endpoint reads only the local licensing database; it does **not** call Paddle and therefore does not add Paddle API usage or transaction charges.
|
||||
|
||||
A successful validation refreshes the signed licence token so subscription entitlement changes are picked up. If an installation was deactivated in the customer portal, the server returns HTTP 403 and the app removes its local token immediately. Temporary network/server failures retain the locally signed licence for up to three days; after that grace period the app requires an online validation.
|
||||
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
# Rsync Studio — Direct Distribution Release
|
||||
|
||||
Rsync Studio is designed for direct macOS distribution rather than the Mac App Store because it intentionally launches `rsync`/`ssh` and accesses user-selected arbitrary paths.
|
||||
|
||||
## Release security baseline
|
||||
|
||||
- Developer ID Application signing.
|
||||
- Hardened Runtime enabled.
|
||||
- App Sandbox intentionally disabled for this product architecture.
|
||||
- No Hardened Runtime exception entitlements.
|
||||
- SSH host-key verification is never disabled.
|
||||
- SSH uses BatchMode, connection timeout and keepalive limits.
|
||||
- Password storage/authentication is not supported; use a private key or the macOS SSH agent.
|
||||
- Advanced rsync arguments that can replace the SSH transport, run remote commands, load passwords, read/write arbitrary option/list/log/batch files or bypass controlled delete handling are rejected.
|
||||
- Scheduled/file-watch `--delete` requires explicit per-job authorisation.
|
||||
- Run logs are sanitised and capped before storage and expire according to Settings.
|
||||
- `PrivacyInfo.xcprivacy` declares no tracking/no collected data and the UserDefaults required-reason API.
|
||||
|
||||
## Mandatory preflight
|
||||
|
||||
Before every release archive, run on the release Mac:
|
||||
|
||||
```bash
|
||||
./Scripts/release-preflight.sh
|
||||
```
|
||||
|
||||
This performs the static security policy checks, validates the privacy manifest, and performs a clean unsigned **Release** build with the installed macOS SDK so compiler/typecheck warnings or SDK incompatibilities are caught before signing.
|
||||
|
||||
## Archive
|
||||
|
||||
In Xcode select **Any Mac (Apple Silicon, Intel)**, choose **Product > Archive**, then distribute using **Developer ID**.
|
||||
|
||||
Alternatively:
|
||||
|
||||
```bash
|
||||
xcodebuild -project "Rsync Studio.xcodeproj" -scheme "Rsync Studio" -configuration Release -archivePath "build/Rsync Studio.xcarchive" archive
|
||||
```
|
||||
|
||||
Choose your Developer ID Application certificate/team in Xcode signing settings before archiving.
|
||||
|
||||
## Verify signature
|
||||
|
||||
```bash
|
||||
codesign --verify --deep --strict --verbose=2 "/path/to/Rsync Studio.app"
|
||||
codesign -d --entitlements :- "/path/to/Rsync Studio.app"
|
||||
```
|
||||
|
||||
The Release build should show Hardened Runtime and should not contain `com.apple.security.get-task-allow`.
|
||||
|
||||
## Notarise
|
||||
|
||||
Store notarisation credentials in a Keychain profile once:
|
||||
|
||||
```bash
|
||||
xcrun notarytool store-credentials "Rsync Studio Notary"
|
||||
```
|
||||
|
||||
Submit a signed ZIP/DMG/PKG:
|
||||
|
||||
```bash
|
||||
ditto -c -k --keepParent "/path/to/Rsync Studio.app" "Rsync Studio.zip"
|
||||
xcrun notarytool submit "Rsync Studio.zip" --keychain-profile "Rsync Studio Notary" --wait
|
||||
```
|
||||
|
||||
Staple and validate:
|
||||
|
||||
```bash
|
||||
xcrun stapler staple "/path/to/Rsync Studio.app"
|
||||
xcrun stapler validate "/path/to/Rsync Studio.app"
|
||||
spctl --assess --type execute --verbose=4 "/path/to/Rsync Studio.app"
|
||||
```
|
||||
|
||||
## Pre-release functional security tests
|
||||
|
||||
1. Local source nested inside destination is rejected.
|
||||
2. Local destination nested inside source is rejected.
|
||||
3. SSH host beginning with `-` is rejected.
|
||||
4. SSH username containing spaces/control characters is rejected.
|
||||
5. A `.pub` key cannot be selected as the effective private key.
|
||||
6. Advanced arguments `-e`, `--rsh`, `--rsync-path`, `--password-file`, `--files-from`, `--log-file`, `--write-batch`, `--remove-source-files`, `--delete*` and `-M` are rejected, and malformed quoting is rejected.
|
||||
7. Remote destination shell metacharacters are rejected before rsync is launched.
|
||||
8. Scheduled/watch job using `--delete` cannot run until **Allow unattended delete** is explicitly enabled.
|
||||
9. SSH to a previously unknown host does not silently bypass host-key checking.
|
||||
10. Scheduled SSH job with unavailable authentication fails rather than prompting indefinitely.
|
||||
11. History expiry removes old completed runs according to Settings.
|
||||
12. A failed persistent SwiftData store opens Recovery Mode instead of immediately crashing.
|
||||
13. `Scripts/security-check.sh` passes before every signed build.
|
||||
|
||||
|
||||
## Signed update feed
|
||||
|
||||
The application contains a dependency-free signed update checker. It remains disabled until these generated Info.plist values are populated in the Release configuration:
|
||||
|
||||
- `RsyncStudioUpdateManifestURL`: HTTPS URL to the JSON manifest.
|
||||
- `RsyncStudioUpdatePublicKey`: Base64 raw Ed25519/Curve25519.Signing public key.
|
||||
|
||||
Manifest schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"build": 120,
|
||||
"downloadURL": "https://example.com/releases/Rsync Studio-1.2.0.dmg",
|
||||
"signature": "BASE64_ED25519_SIGNATURE"
|
||||
}
|
||||
```
|
||||
|
||||
The exact signed bytes are UTF-8: `version + "\n" + build + "\n" + downloadURL`. The checker refuses HTTP feeds, unsigned manifests, invalid signatures, and non-HTTPS download URLs. It opens the verified signed download location; it does not silently install software. Keep the private signing key outside the source repository and build machine where practical.
|
||||
|
||||
## Automated commercial release pipeline
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user