{"id":49693955,"url":"https://github.com/tredmann/repahead","last_synced_at":"2026-05-07T21:01:28.520Z","repository":{"id":356369906,"uuid":"1232209005","full_name":"tredmann/repahead","owner":"tredmann","description":"A private Composer (Packagist-compatible) repository server.","archived":false,"fork":false,"pushed_at":"2026-05-07T19:25:32.000Z","size":137,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T20:31:12.886Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tredmann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-07T17:42:40.000Z","updated_at":"2026-05-07T19:25:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tredmann/repahead","commit_stats":null,"previous_names":["tredmann/repahead"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/tredmann/repahead","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tredmann%2Frepahead","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tredmann%2Frepahead/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tredmann%2Frepahead/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tredmann%2Frepahead/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tredmann","download_url":"https://codeload.github.com/tredmann/repahead/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tredmann%2Frepahead/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32755926,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-07T02:14:30.463Z","status":"ssl_error","status_checked_at":"2026-05-07T02:14:29.405Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-07T21:00:58.440Z","updated_at":"2026-05-07T21:01:28.503Z","avatar_url":"https://github.com/tredmann.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Private Composer Server\n\nA small PHP service that exposes a private Composer (Packagist-compatible) repository whose content is driven by **dropping ZIP files into a folder**. Storage is pluggable via Flysystem (local disk or S3).\n\nSee [docs/superpowers/specs/2026-05-06-composer-server-design.md](docs/superpowers/specs/2026-05-06-composer-server-design.md) for the full design.\n\n## Quick start (Docker)\n\n```bash\ncp .env.example .env\n# edit AUTH_PASS at minimum\nAUTH_PASS=$(grep AUTH_PASS .env | cut -d= -f2) docker compose up -d --build\n```\n\nThe service listens on `http://localhost:8080`. Drop ZIPs into the `composer-zips` volume:\n\n```bash\ndocker compose cp ./acme-billing-1.2.0.zip composer:/var/www/html/zips/acme/billing/1.2.0.zip\ncurl -u ci:secret -X POST http://localhost:8080/rebuild\n```\n\n## Folder layout for ZIPs\n\n```\nzips/\n  vendor/\n    package/\n      1.0.0.zip\n      1.1.0.zip\n```\n\nThe `composer.json` inside each ZIP is the source of truth for `require`, `autoload`, etc. The folder path determines `vendor/package`; the filename determines the version.\n\n## Endpoints\n\n| Method | Route | Purpose |\n|--------|-------|---------|\n| GET | `/packages.json` | Composer repository index (cached) |\n| GET | `/dist/{vendor}/{package}/{version}.zip` | Streams the ZIP |\n| POST | `/rebuild` | Force cache rebuild; returns `{packages, versions, skipped, duration_ms}` |\n\nAll endpoints use HTTP basic auth (`AUTH_USER` / `AUTH_PASS`).\n\n## Consumer setup\n\nIn the consuming project:\n\n```bash\ncomposer config repositories.private composer https://composer.your-domain.com\ncomposer config http-basic.composer.your-domain.com ci \u003cpassword\u003e\ncomposer require acme/billing:^1.0\n```\n\n## Configuration\n\nSee `.env.example`. Key vars:\n\n- `STORAGE_DSN=local:./zips` or `s3:my-bucket/composer/zips`\n- `LISTING_TTL_SECONDS=30` — how long the cache lives between storage listings (`0` = list every request)\n- `AUTH_USER`, `AUTH_PASS` — single shared HTTP basic credential\n\n## S3 storage\n\nSet `STORAGE_DSN` to `s3:bucket-name` or `s3:bucket-name/optional/prefix`, then supply the three AWS credentials:\n\n```\nSTORAGE_DSN=s3:my-bucket/composer/zips\nAWS_ACCESS_KEY_ID=AKIA...\nAWS_SECRET_ACCESS_KEY=...\nAWS_REGION=eu-central-1\n```\n\nThe service only reads from S3 (list + download). The minimum IAM policy for the bucket is:\n\n```json\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\"s3:ListBucket\", \"s3:GetObject\"],\n  \"Resource\": [\n    \"arn:aws:s3:::my-bucket\",\n    \"arn:aws:s3:::my-bucket/composer/zips/*\"\n  ]\n}\n```\n\nUpload ZIPs to S3 in the same `vendor/package/version.zip` layout used for local storage, then `POST /rebuild` to refresh the index.\n\n## Docker Hub\n\nImage: [`tredmann/repahead`](https://hub.docker.com/r/tredmann/repahead)\n\n```bash\n# minimal — only AUTH_PASS is required\ndocker run -d -p 8080:8080 -e AUTH_PASS=secret tredmann/repahead\n\n# production — set the public URL so dist links resolve correctly\ndocker run -d \\\n  -p 8080:8080 \\\n  -e AUTH_PASS=secret \\\n  -e APP_BASE_URL=https://composer.your-domain.com \\\n  -v /path/to/zips:/var/www/html/zips \\\n  tredmann/repahead\n```\n\n### Environment variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `AUTH_PASS` | — | **Required.** HTTP basic auth password |\n| `AUTH_USER` | `ci` | HTTP basic auth username |\n| `APP_BASE_URL` | `http://localhost:8080` | Public base URL; used in `packages.json` dist download links |\n| `STORAGE_DSN` | `local:/var/www/html/zips` | Storage backend — `local:\u003cpath\u003e` or `s3:\u003cbucket\u003e/\u003cprefix\u003e` |\n| `CACHE_DIR` | `/var/www/html/cache` | Directory for the `packages.json` cache and hash files |\n| `LISTING_TTL_SECONDS` | `30` | Seconds before the storage listing cache expires; `0` = list on every request |\n| `AWS_ACCESS_KEY_ID` | — | S3 only — AWS access key ID |\n| `AWS_SECRET_ACCESS_KEY` | — | S3 only — AWS secret access key |\n| `AWS_REGION` | — | S3 only — AWS region, e.g. `eu-central-1` |\n| `SERVER_NAME` | `:8080` | Listen address and port (base image) |\n| `AUTOMATIC_HTTPS` | `off` | Auto-HTTPS via FrankenPHP/Caddy; keep `off` behind a reverse proxy (base image) |\n| `PHP_OPCACHE_ENABLE` | `1` | Enable PHP OPcache (base image) |\n\n## Local development\n\n```bash\ncomposer install\ncp .env.example .env\nphp -S 127.0.0.1:8080 -t public\ncomposer test     # full suite (also: composer stan, composer pint, composer rector)\n```\n\n## Failure modes\n\nZIPs that are corrupt, missing `composer.json`, or whose `composer.json` `name` field doesn't match the folder path are **skipped** (logged to stderr). The skipped count is included in `POST /rebuild` responses.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftredmann%2Frepahead","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftredmann%2Frepahead","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftredmann%2Frepahead/lists"}