Skip to content

Commit 965afe1

Browse files
edgrosvenorEd Grosvenorclaude
authored
PR-B — enable the credential API on the crate app + real READMEs (#7)
* PR-B — enable credential API and READMEs Enable Crate's admin-token credential API and document the shipped registry/client surfaces for launch readiness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix README operator/customer flow ordering and token:create note Order the CRATE_URL export before the composer config line so a copy-paste customer setup does not configure an empty repository URL, and clarify that token:create dispatches to the environment via the Cloud CLI (or --execute to run on the environment directly). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Ed Grosvenor <ed@artisan.build> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4f260b1 commit 965afe1

10 files changed

Lines changed: 653 additions & 126 deletions

File tree

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ APP_MAINTENANCE_DRIVER=file
1515

1616
BCRYPT_ROUNDS=12
1717

18+
# Crate exposes built-for-cloud's admin-token gated credential API.
19+
BUILT_FOR_CLOUD_CREDENTIAL_API_ENABLED=true
20+
1821
LOG_CHANNEL=stack
1922
LOG_STACK=single
2023
LOG_DEPRECATIONS_CHANNEL=null

.phpstorm.meta.php

Lines changed: 170 additions & 121 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,94 @@
1-
# crate
2-
Crate — self-hosted, unmetered private Composer registry for Laravel, on Laravel Cloud. A fork-and-deploy Satis wrapper with API-issued/revocable credentials. (private during build; flips public at launch)
1+
# Crate
2+
3+
Crate is a self-hosted, unmetered private Composer registry for Laravel, built to run on Laravel Cloud.
4+
5+
It is a fork-and-deploy Satis wrapper behind a credential gate: register the private repositories you want to serve, build Composer metadata and mirrored dist archives, issue credentials over an admin-token guarded API, then point customer apps at your Crate host.
6+
7+
Crate is single-tenant by construction. Each deployment lives in your own Laravel Cloud account, with your own compute, database, queue, and object storage. It is the self-hosted floor below hosted products like Private Packagist and Anystack: no per-seat registry bill, no third-party registry holding your private packages, and no built-in team dashboard or per-package ACLs. Access is credential-level in this release.
8+
9+
Crate is MIT licensed.
10+
11+
## What Ships
12+
13+
- `artisan-build/crate-contracts`: framework-free DTOs and enums shared by the client and server packages.
14+
- `artisan-build/crate-client`: a consumer auth helper plus an issuer SDK for `/api/credentials`.
15+
- `artisan-build/crate-server`: served-repo storage, Satis config/build orchestration, and gated Composer registry routes.
16+
- `artisan-build/built-for-cloud`: consumed by the app for token storage, token commands, admin-token middleware, and the credential API.
17+
18+
Crate is headless. There is no admin web UI in this release.
19+
20+
## Test Drive
21+
22+
After deploying the app and provisioning Laravel Cloud resources, run the operator flow against your deployed Crate environment:
23+
24+
```bash
25+
php artisan crate:repos:add vendor/pkg https://github.com/vendor/pkg.git --source-token=...
26+
php artisan crate:build
27+
php artisan token:create ci --abilities=admin
28+
```
29+
30+
`crate:repos:add` stores the served package and encrypts the source credential. `crate:build` generates `satis.json` from the database and dispatches the Satis build job. The build writes Composer metadata and mirrored dist archives to the configured storage disk, served back through Crate rather than public object-storage URLs. `token:create` dispatches to the target environment through the Cloud CLI; add `--execute` to run it directly on the environment (for example from a Cloud SSH session).
31+
32+
Use the admin token from `token:create --abilities=admin` to issue customer credentials:
33+
34+
```bash
35+
curl -X POST "$CRATE_URL/api/credentials" \
36+
-H "Authorization: Bearer $CRATE_ADMIN_TOKEN" \
37+
-H "Accept: application/json" \
38+
-H "Content-Type: application/json" \
39+
-d '{"name":"build-bot"}'
40+
```
41+
42+
You can also issue, list, and revoke credentials from another Laravel app with `ArtisanBuild\CrateClient\CrateIssuer`.
43+
44+
In the customer app, install `artisan-build/crate-client`, configure Composer to use the Crate registry, set the credential, and write Composer auth:
45+
46+
```bash
47+
export CRATE_URL="https://crate.example.com"
48+
export CRATE_TOKEN="the-issued-credential"
49+
50+
composer config repositories.crate composer "$CRATE_URL"
51+
52+
php artisan crate:auth
53+
composer require vendor/pkg
54+
```
55+
56+
`crate:auth` writes or merges Composer `auth.json` HTTP Basic credentials for the Crate host. Composer then reads `/packages.json`, `/p2/...`, and `/dist/...` through Crate's credential gate.
57+
58+
## Configuration
59+
60+
The app publishes `config/built-for-cloud.php` and enables the credential API by default for Crate:
61+
62+
```php
63+
'credential_api' => [
64+
'enabled' => env('BUILT_FOR_CLOUD_CREDENTIAL_API_ENABLED', true),
65+
'prefix' => env('BUILT_FOR_CLOUD_CREDENTIAL_API_PREFIX', 'api/credentials'),
66+
],
67+
```
68+
69+
Crate-specific server config lives in `config/crate-server.php`:
70+
71+
- `CRATE_URL`: the public registry URL used as Satis `homepage` and archive prefix.
72+
- `CRATE_ARCHIVE_DISK`: disk for Composer metadata and mirrored dist archives.
73+
- `CRATE_SATIS_PATH`: path to the isolated Satis binary.
74+
- `CRATE_OUTPUT_DIR`: storage prefix for generated registry output.
75+
76+
Do not hand-set Laravel Cloud managed resource credentials for database, queue, cache, or object storage. Let Cloud inject them.
77+
78+
## Credential API
79+
80+
The app exposes built-for-cloud's admin-token gated routes:
81+
82+
- `GET /api/credentials`: list token metadata. Plaintext is never returned here.
83+
- `POST /api/credentials`: issue a credential and return plaintext once.
84+
- `DELETE /api/credentials/{name}`: revoke credentials by name.
85+
86+
Only tokens with the `admin` ability can call these routes. Registry credentials without `admin` can pull packages but cannot issue or revoke other credentials.
87+
88+
## Non-Goals
89+
90+
- No hosted control plane.
91+
- No web dashboard.
92+
- No mirroring of packagist.org public packages.
93+
- No per-package or per-vendor access control in this release.
94+
- No search, download stats, team management, or billing logic.

composer.lock

Lines changed: 81 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/built-for-cloud.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
return [
6+
7+
/*
8+
|--------------------------------------------------------------------------
9+
| Fallback Token
10+
|--------------------------------------------------------------------------
11+
|
12+
| A single plaintext "fallback" token, read straight from the environment.
13+
| Any caller presenting it authenticates without a database token row. It
14+
| exists for bootstrap and low-friction internal use only — delete it from
15+
| the environment to disable it, and provision per-app database tokens for
16+
| production workloads.
17+
|
18+
| When null, fallback authentication is disabled entirely.
19+
|
20+
*/
21+
22+
'fallback_token' => env('FALLBACK_TOKEN'),
23+
24+
/*
25+
|--------------------------------------------------------------------------
26+
| Token Prefix
27+
|--------------------------------------------------------------------------
28+
|
29+
| A short, human-recognisable prefix prepended to generated plaintext
30+
| tokens. Purely cosmetic — it has no bearing on how a token resolves.
31+
|
32+
*/
33+
34+
'token_prefix' => env('BUILT_FOR_CLOUD_TOKEN_PREFIX', 'tok_'),
35+
36+
/*
37+
|--------------------------------------------------------------------------
38+
| Credential API
39+
|--------------------------------------------------------------------------
40+
|
41+
| Enabled by default for Crate. A token-admin guarded JSON API can
42+
| issue, list, and revoke plain access tokens without sessions or CSRF.
43+
|
44+
*/
45+
46+
'credential_api' => [
47+
'enabled' => env('BUILT_FOR_CLOUD_CREDENTIAL_API_ENABLED', true),
48+
'prefix' => env('BUILT_FOR_CLOUD_CREDENTIAL_API_PREFIX', 'api/credentials'),
49+
],
50+
51+
/*
52+
|--------------------------------------------------------------------------
53+
| Cloud CLI
54+
|--------------------------------------------------------------------------
55+
|
56+
| The Laravel Cloud CLI binary used to resolve the target environment and
57+
| to run administration commands remotely. The environment itself is
58+
| resolved at runtime via `cloud environment:list`, never hard-coded.
59+
|
60+
*/
61+
62+
'cloud' => [
63+
'binary' => env('BUILT_FOR_CLOUD_BINARY', 'cloud'),
64+
'application' => env('BUILT_FOR_CLOUD_APPLICATION'),
65+
],
66+
67+
];

packages/crate-client/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,90 @@
11
# Crate Client
2+
3+
Client-side helpers for Crate.
4+
5+
This package has two surfaces:
6+
7+
- Consumer auth helper: write Composer HTTP Basic auth for a Crate registry.
8+
- Issuer SDK: call a Crate deployment's admin-token gated `/api/credentials` endpoint.
9+
10+
## Consumer Helper
11+
12+
Configure the customer app with the Crate registry URL and an issued registry credential:
13+
14+
```bash
15+
CRATE_URL=https://crate.example.com
16+
CRATE_TOKEN=issued-registry-credential
17+
```
18+
19+
Generate Composer auth:
20+
21+
```bash
22+
php artisan crate:auth
23+
```
24+
25+
By default, `crate:auth` writes or merges `auth.json` in the current working directory. It adds this shape:
26+
27+
```json
28+
{
29+
"http-basic": {
30+
"crate.example.com": {
31+
"username": "token",
32+
"password": "issued-registry-credential"
33+
}
34+
}
35+
}
36+
```
37+
38+
Use a different path with `--path`:
39+
40+
```bash
41+
php artisan crate:auth --path=/path/to/auth.json
42+
```
43+
44+
Print the JSON instead of writing a file with `--print`, which is useful for `COMPOSER_AUTH` in CI:
45+
46+
```bash
47+
export COMPOSER_AUTH="$(php artisan crate:auth --print)"
48+
```
49+
50+
Then configure Composer to use the Crate registry and require packages normally:
51+
52+
```bash
53+
composer config repositories.crate composer https://crate.example.com
54+
composer require vendor/pkg
55+
```
56+
57+
## Issuer SDK
58+
59+
Use `CrateIssuer` in the operator's own Laravel app to issue and revoke credentials around billing, onboarding, or access logic that you own.
60+
61+
Configure the issuer client:
62+
63+
```bash
64+
CRATE_ISSUER_URL=https://crate.example.com
65+
CRATE_ADMIN_TOKEN=admin-ability-token
66+
CRATE_ISSUER_RETRIES=2
67+
CRATE_ISSUER_RETRY_SLEEP=100
68+
```
69+
70+
`CRATE_ISSUER_URL` defaults to `CRATE_URL` when omitted.
71+
72+
Example:
73+
74+
```php
75+
use ArtisanBuild\CrateClient\CrateIssuer;
76+
77+
$issuer = CrateIssuer::fromConfig();
78+
79+
$credential = $issuer->issue('build-bot');
80+
$tokens = $issuer->list();
81+
$issuer->revoke('build-bot');
82+
```
83+
84+
The SDK calls:
85+
86+
- `POST /api/credentials` for `issue(...)`, returning an `ArtisanBuild\CrateContracts\Credential` with plaintext shown once.
87+
- `GET /api/credentials` for `list()`, returning metadata without plaintext.
88+
- `DELETE /api/credentials/{name}` for `revoke(...)`.
89+
90+
Requests use Bearer auth with the configured admin token, accept JSON, retry transient failures according to config, and throw on non-2xx responses.

0 commit comments

Comments
 (0)