Skip to content
← Back to projects

Laravel Satis

A Laravel package for managing private Composer repositories with Satis

Laravel Satis

#Laravel Satis

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status PHPStan Total Downloads Buy Me A Coffee

A Laravel package for managing private Composer repositories powered by Satis.

#Features

  • Credential Management — Dedicated Credential model for centralized authentication (URL, email, password)
  • Package Management — Add and manage Composer & GitHub package sources linked to credentials
  • Token-Based Auth — Secure access with per-token package scoping
  • Automated Builds — Queue-driven Satis builds with configurable scheduling
  • Credential Grouping — Separate builds per credential with snapshot merging
  • Inline Auth URLs — RFC 3986 percent-encoded credentials in repository URLs
  • Rate-Limit Retry — Exponential backoff on HTTP 429 responses during builds
  • GitHub Webhooks — Auto-rebuild on push, release and create events with signature verification
  • Download Tracking — Per-version download statistics
  • Dependency Tracking — Public/private dependency classification with automatic processing
  • Multi-Tenancy — Tenant-isolated data with configurable resolver
  • Credential Validation — Verify package and credential accessibility before building
  • Intelligent Validation — Timestamp-based comparison to skip unnecessary rebuilds
  • Credential Sanitization — Remove transport-options and inline credentials from Satis JSON files
  • Dev Packages — Mark packages as development-only with is_dev flag
  • Composer V2 Protocol — Full support for packages.json, p2/ and include files

#Requirements

  • PHP 8.2+
  • Laravel 11, 12 or 13
  • Satis (composer/satis — included as dependency)

Note on stability: this package depends on composer/satis at version 3.x-dev, which does not yet have a stable release. For this reason, when installing the package in your application, Composer will require your consuming project to allow development packages. Configure this in your application's composer.json:

{
    "minimum-stability": "dev",
    "prefer-stable": true
}

#Installation

composer require jeffersongoncalves/laravel-satis

Publish and run migrations:

php artisan vendor:publish --tag="satis-migrations"
php artisan migrate

Publish the config (optional):

php artisan vendor:publish --tag="satis-config"

#Configuration

The config file (config/satis.php) covers:

#Multi-Tenancy

'tenancy' => [
    'enabled' => false,
    'model' => null,
    'foreign_key' => null,
    'ownership_relationship' => null,
    'resolver' => null, // callable that returns the current tenant ID
],

The resolver accepts any callable that returns the current tenant ID. Example:

// In a service provider or middleware
config(['satis.tenancy.enabled' => true]);
config(['satis.tenancy.model' => \App\Models\Team::class]);
config(['satis.tenancy.foreign_key' => 'team_id']);
config(['satis.tenancy.resolver' => fn () => auth()->user()?->current_team_id]);

#Table Prefix

'table_prefix' => 'satis_',

Set to null to use table names without a prefix.

#Custom Models

Override any model to extend the default behavior:

'models' => [
    'credential' => \App\Models\SatisCredential::class,
    'package' => \App\Models\SatisPackage::class,
    'token' => \App\Models\SatisToken::class,
    // ...
],

#Storage

'storage_disk' => 'local',
'storage_path' => 'satis',

#Queue

'queue' => [
    'connection' => null,  // null = default connection
    'queue_name' => null,  // null = default queue
    'timeout' => 86400,    // 24 hours (in seconds)
],

#Scheduling

'schedule' => [
    'build' => 'weekly',        // any Laravel Schedule method or null
    'token_build' => 'weekly',
    'validate' => 'hourly',
    'sanitize' => 'daily',
    'dependencies' => 'weekly',
],

#Routes

'routes' => [
    'api_prefix' => 'api/satis',
    'composer_prefix' => 'satis',
    'middleware' => ['api'],
],

#Usage

#Managing Credentials and Packages Programmatically

use JeffersonGoncalves\LaravelSatis\Support\ModelResolver;

// Create a credential
$credentialModel = ModelResolver::credential();
$credential = $credentialModel::create([
    'name' => 'My Private Repo',
    'url' => 'https://repo.example.com',
    'email' => 'user',
    'password' => 'secret',
]);

// Create a package using the credential
$packageModel = ModelResolver::package();
$package = $packageModel::create([
    'name' => 'vendor/package-name',
    'type' => 'composer',
    'credential_id' => $credential->id,
]);

// Create a GitHub credential and package
$githubCredential = $credentialModel::create([
    'name' => 'GitHub',
    'url' => 'https://github.com/vendor/repo.git',
    'email' => 'github-user',
    'password' => 'github-token',
]);

$githubPackage = $packageModel::create([
    'name' => 'vendor/github-package',
    'type' => 'github',
    'credential_id' => $githubCredential->id,
]);

// Create a dev package (reusing same credential)
$devPackage = $packageModel::create([
    'name' => 'vendor/dev-tool',
    'type' => 'composer',
    'credential_id' => $credential->id,
    'is_dev' => true,
]);

// Validate a credential
$result = app(\JeffersonGoncalves\LaravelSatis\Actions\ValidateCredential::class)
    ->execute($credential);
// $result = ['success' => true, 'message' => 'Credential validated successfully.']

// Create a token
$tokenModel = ModelResolver::token();
$token = $tokenModel::create([
    'name' => 'My Token',
    'email' => 'user@example.com',
]);

// Assign packages to token
$token->packages()->attach($package->id);

#Running Builds

# Build all packages (tenant-based)
php artisan satis:build

# Build for a specific tenant
php artisan satis:build --tenant=1

# Build per token (all tokens with packages)
php artisan satis:token-build

# Build for a specific token
php artisan satis:token-build --token=5

# Validate credentials and trigger rebuilds if needed
php artisan satis:validate

# Process dependencies
php artisan dependency:packages

# Remove credentials from Satis JSON files
php artisan satis:sanitize

# Clean all Satis builds from storage
php artisan satis:clean

# Force clean without confirmation
php artisan satis:clean --force

#Composer Client Configuration

After building, clients can use your private repository:

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://your-app.com/satis"
        }
    ]
}

Authenticate using the token as a password with any username:

composer config http-basic.your-app.com/satis "any-username" "your-token-here"

#GitHub Webhooks

Each package gets a unique reference for webhook URLs:

POST /api/satis/webhooks/github/{package-reference}

Set the Content type to application/json and optionally configure a Secret using the package's webhook_secret.

Supported events: push, release, create — all other events are ignored with HTTP 200.

The webhook handler:

  1. Validates the package is a GitHub type (returns 400 otherwise)
  2. Filters supported events
  3. Verifies HMAC-SHA256 signature when a secret is configured
  4. Dispatches SyncTenantPackages for the tenant rebuild
  5. Dispatches SyncTokenPackages for each token associated with the package

#API Endpoints

#Composer Protocol (requires token auth)

Method Endpoint Description
GET /satis/packages.json Root packages file
GET /satis/include/{include}.json Include files
GET /satis/p2/{vendor}/{package}.json V2 protocol metadata
GET /satis/archives/{vendor}/{package}/{file} Package archives

#API

Method Endpoint Description
POST /api/satis/composer/downloads Download notifications
POST /api/satis/webhooks/github/{reference} GitHub webhook

#Commands

Command Description
satis:build Build Satis repository (tenant-based)
satis:token-build Build Satis repository (token-based)
satis:validate Validate package credentials and trigger rebuilds if needed
satis:clean Clean all Satis builds from storage
satis:sanitize Remove credentials from Satis JSON files
dependency:packages Process and sync package dependencies

#Upgrading from v1.x to v2.0

#Breaking Changes

  1. Credential model: Credentials are now stored in a dedicated credentials table instead of directly on the packages table.

  2. Package model: The url, username, and password columns have been removed. Packages now reference a credential_id foreign key (required).

  3. CreateAuthJson removed: Authentication is now handled via inline auth URLs (RFC 3986) instead of a separate auth.json file.

  4. Code lengths: webhook_secret changed from 40 to 64 characters, reference from 20 to 32 characters.

#Migration Steps

  1. Update your dependency:
composer require jeffersongoncalves/laravel-satis:^2.0
  1. Publish and run the new migrations:
php artisan vendor:publish --tag="satis-migrations"
  1. Before running migrations, migrate existing data to the credentials table:
use JeffersonGoncalves\LaravelSatis\Models\Credential;

$packages = DB::table('satis_packages')->get();
$credentialMap = [];

foreach ($packages as $package) {
    $key = $package->url . '|' . $package->username;

    if (! isset($credentialMap[$key])) {
        $credential = Credential::create([
            'name' => parse_url($package->url, PHP_URL_HOST) ?? $package->url,
            'url' => $package->url,
            'email' => $package->username,
            'password' => $package->password,
            'is_validated' => $package->is_credentials_validated,
            'validated_at' => $package->credentials_validated_at,
        ]);
        $credentialMap[$key] = $credential->id;
    }

    DB::table('satis_packages')
        ->where('id', $package->id)
        ->update(['credential_id' => $credentialMap[$key]]);
}
  1. Run migrations:
php artisan migrate
  1. Update code references:
    • $package->url$package->credential->url
    • $package->username$package->credential->email
    • $package->password$package->credential->password
    • CreateAuthJson → removed, no longer needed

#Testing

composer test

#Changelog

Please see CHANGELOG for more information on what has changed recently.

#Contributing

Please see CONTRIBUTING for details.

#Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

#Credits

#License

The MIT License (MIT). Please see License File for more information.

New version available.