technicpack/solder
TechnicPack Solder is a Laravel-based repository and API for Minecraft modpacks, helping you manage packs, builds, and launcher downloads from a web admin interface and serve them to Technic Platform clients.
This is an application, not a library. technicpack/solder is the Technic Solder server itself — a full Laravel app (PHP 8.4 / Laravel 13 / Fortify / Sanctum) that serves modpack metadata to the Technic Launcher. The 0 dependents and the repo layout confirm it: you do not composer require it into an existing project. You clone/fork the repo, deploy it as its own service, and integrate over its HTTP API. Budget for that operational cost before adopting it.
Minimal path to a working instance:
git clone the repo and start the dev stack: docker compose -f compose.dev.yml up -d (PostgreSQL, mounted .env)..env.example → .env, set APP_URL, APP_KEY (php artisan key:generate), DB creds, and the mirror settings.php artisan migrate --seed, then log into /admin with the seeded admin account (check database/seeders/ for the credential; create additional users via the bundled artisan command rather than INSERTing rows).npm ci && npm run build — the admin panel is Tailwind v4 + Alpine and will throw a Vite manifest exception until built.Where to look first, in order:
routes/api.php — the entire contract with the launcher lives here; it is small enough to read in one sitting.docs/api/ — per-endpoint request/response documentation (including read/root.md, which carries the version string).app/Models/ — Modpack → Build → Modversion → Mod is the whole domain model..env.example and config/ — mirror disk, rate limiting, cache, auth drivers.agents/rules/ — the repo ships its own conventions (docker, testing, pint, releasing). Follow them; they are accurate and opinionated.First use case: create a modpack, add a mod, add a modversion pointing at a file on your mirror (MD5 + filesize required), assemble a build containing that modversion, publish it, then curl {APP_URL}/api/modpack/{slug}/{build} and confirm the launcher-shaped JSON comes back. That end-to-end loop is the fastest way to understand the package.
Treat the content hierarchy as your deploy artifact. A Build is an immutable snapshot: a set of Modversion rows pinned at exact versions. Do not mutate a published build to "fix" a mod; cut a new build version and move the modpack's recommended/latest pointers. Launchers and users cache by build version, so in-place edits produce silent divergence.
Mirror layout is part of the contract. Files are referenced by a relative path plus md5 and filesize. Whether the bytes live on S3 (signed/public URLs) or a plain web mirror, the launcher verifies the hash before installing. After any manual upload, re-run the rehash artisan command (php artisan list | grep solder for the exact name) so the DB hashes match the bytes on disk — this is the single most common reason a build "works in the admin panel but fails in the launcher."
Private distribution uses client API keys. Create a client in /admin, hand the key to the consumer, and it is passed on each request (?k=… in the shipped docs; confirm the accepted parameter/header in the request middleware). The /api/verify/{key} endpoint is the cheap way for a client to validate a key at boot instead of failing mid-download.
Automate the pipeline, don't click it. For a real modpack you'll be importing dozens of mods per release. Write an artisan command that reads your source-of-truth (CurseForge/Modrinth export, git repo of manifests), upserts Mod/Modversion rows idempotently keyed on (mod slug, version), creates the Build, attaches pivots in a transaction, then publishes. Put it behind a queued job with retries; wrap the whole thing in DB::transaction() so a partial import never leaves an unpublished half-build.
Client-side integration pattern. Consumers should poll GET /api/modpack/{slug}/{build} with a concrete build version (or the recommended alias), diff the returned mod list against local state, download only changed files, and verify MD5 locally. Cache the response aggressively keyed on build version — the metadata is immutable per build, so there is no reason to re-fetch it on every launcher start.
Extending the app. Add endpoints in routes/api.php behind the existing auth middleware, return Eloquent API Resources (match the existing JSON envelope exactly — third-party launchers are strict), validate with Form Requests, and authorize with Policies. Keep new controllers next to their siblings in app/Http/Controllers/Api/. Use database-schema before writing a migration and search-docs before reaching for a framework idiom — this repo is on Laravel 13, where plenty of blog-era advice is stale.
Testing. All tests are PHPUnit (convert any Pest you find). Feature tests run against in-memory/stock sqlite via phpunit.xml + .env.testing; never point tests at the Postgres dev container. Use RefreshDatabase and the existing factories, and assert on the exact JSON shape — API stability is the product here. Run a single test with --filter= before the suite.
Release hygiene (if you fork). The repo's releasing rule is load-bearing: bump SOLDER_VERSION in app/Providers/AppServiceProvider.php, update CHANGELOG.md, update the version in docs/api/read/root.md, then tag. Clients read that root endpoint to version-gate themselves.
composer require is the wrong mental model. If you were hoping to embed Solder's logic in an existing Laravel app, expect to vendor the models/routes by hand instead. Forking the app is the supported path, which means you own upgrades forever.php artisan cache:clear after any out-of-band mutation.compose.dev.yml (PostgreSQL) and compose.yml (MariaDB) are independent. Merging pulls MariaDB and the production env-injection block into dev — the pgsql driver then hits MariaDB, two databases run at once, and test isolation breaks. Also never run bare docker compose up.ILIKE will bite you in one of the three.Build migration for exact column names rather than guessing.k= from your reverse-proxy logs and rotate keys per client.migrate:fresh in prod equals total data loss (modpacks, builds, keys are all in the DB). Use migrate and take a dump before upgrades.database-query/database-schema over raw SQL, route:list --path=api to confirm the middleware stack, browser-logs for admin-panel JS errors, Debugbar for N+1s on the modpack listing page (it is the slowest screen in the app).CHANGELOG.md and the API docs diff — the server version, the documented example response, and SOLDER_VERSION are supposed to move together, and a mismatch is your early warning that a client will break.How can I help you explore Laravel packages today?