HexHub is a complete implementation of the Hex API specification for managing Elixir packages privately. It provides package hosting, documentation serving, and repository management capabilities.
- Complete Hex API Compatibility: Drop-in replacement for hex.pm
- Package Management: Upload, download, and manage Elixir packages
- Documentation Hosting: Automatic documentation generation and serving
- User Management: API key authentication and user accounts
- Repository Support: Private repositories with access control
- Live Documentation: Real-time documentation updates
- Mnesia Storage: In-memory database with disk persistence (no PostgreSQL required)
- Flexible Storage: Support for local filesystem or S3-compatible storage
- Zero Database Setup: Uses Mnesia for data storage, no external database required
- Anonymous Publishing: Optional mode for publishing packages without authentication (configurable via admin dashboard)
- Admin Dashboard: Web-based administration interface for managing users, packages, and configuration
âś… Development Complete: All core Hex API functionality has been implemented with Mnesia storage.
Completed Features:
- âś… Complete Hex API implementation
- âś… Mnesia database with full CRUD operations
- âś… User management with authentication
- âś… Package publishing and retrieval
- âś… Documentation hosting
- âś… API key management
- âś… Package ownership management
- âś… Comprehensive test suite (389+ tests, 100% passing)
- âś… Local file storage for packages and documentation
- âś… Anonymous publishing mode (admin configurable)
- âś… Admin dashboard with user/package management
The application is ready for production use. See the development plan for detailed implementation summary.
- Elixir 1.15+ and Erlang/OTP 26+
- Node.js 18+ (for assets)
- No database required (uses Mnesia for storage)
# Clone the repository
git clone https://github.com/gsmlg-dev/hex_hub.git
cd hex_hub
# Install dependencies
mix setup
# Start the development server
mix phx.serverThe application will be available at http://localhost:4000
# Build for production
MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix release
# Run the release
_build/prod/rel/hex_hub/bin/hex_hub startAll API endpoints require authentication via API key:
curl -H "Authorization: Bearer YOUR_API_KEY" https://your-domain.com/api/packages# Create a package first
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"repository": "hexpm", "meta": {"description": "My awesome package"}}' \
https://your-domain.com/api/packages/my_package
# Then publish a release
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @my_package-1.0.0.tar \
https://your-domain.com/api/publish?name=my_package&version=1.0.0Add to your mix.exs:
defp deps do
[
{:my_package, "~> 1.0", repo: "gsmlg-dev", organization: "gsmlg-dev"}
]
endConfigure your repository:
# In config/config.exs
config :hex, repos: [
"gsmlg-dev": "https://your-domain.com"
]The application implements the complete Hex API specification:
POST /api/users- Create new userGET /api/users/:username_or_email- Get user profileGET /api/users/me- Get current authenticated userPOST /api/users/:username_or_email/reset- Reset password
GET /api/repos- List repositoriesGET /api/repos/:name- Get repository details
GET /api/packages- List packages (with pagination and search)GET /api/packages/:name- Get package detailsPOST /api/publish- Publish new package/release
POST /api/packages/:name/releases/:version/docs- Upload documentationDELETE /api/packages/:name/releases/:version/docs- Remove documentation
GET /api/packages/:name/owners- List package ownersPUT /api/packages/:name/owners/:email- Add package ownerDELETE /api/packages/:name/owners/:email- Remove package owner
GET /api/keys- List API keysPOST /api/keys- Create API key (requires Basic Auth)GET /api/keys/:name- Get API key detailsDELETE /api/keys/:name- Delete API key
SECRET_KEY_BASE: 64-byte secret for sessionsPHX_HOST: Hostname for URL generationMIX_ENV: Environment (dev, test, prod)
Configure storage in config/dev.exs:
config :hex_hub,
storage_type: :local, # or :s3
storage_path: "priv/storage"For S3 storage:
config :hex_hub,
storage_type: :s3,
s3_bucket: "your-bucket-name",
s3_region: "us-east-1"
# S3 Configuration
config :ex_aws,
access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"),
region: System.get_env("AWS_REGION", "us-east-1")
config :ex_aws, :s3,
scheme: "https://",
host: System.get_env("AWS_S3_HOST"), # For custom S3-compatible services
port: (if port = System.get_env("AWS_S3_PORT"), do: String.to_integer(port), else: 443),
path_style: System.get_env("AWS_S3_PATH_STYLE", "false") == "true"-
Create an S3 bucket:
aws s3 mb s3://your-hex-packages-bucket
-
Create an IAM user with programmatic access and attach the following policy:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::your-hex-packages-bucket", "arn:aws:s3:::your-hex-packages-bucket/*" ] } ] } -
Set environment variables:
export STORAGE_TYPE=s3 export S3_BUCKET=your-hex-packages-bucket export AWS_ACCESS_KEY_ID=your-access-key export AWS_SECRET_ACCESS_KEY=your-secret-key export AWS_REGION=us-east-1
For S3-compatible services, use additional environment variables:
export AWS_S3_HOST=your-minio-server.com
export AWS_S3_PORT=9000
export AWS_S3_PATH_STYLE=true # Required for MinIO
export AWS_S3_SCHEME=http # Use http for local MinIO| Variable | Description | Default |
|---|---|---|
STORAGE_TYPE |
Storage backend (local or s3) |
local |
S3_BUCKET |
S3 bucket name | - |
AWS_ACCESS_KEY_ID |
AWS access key ID | - |
AWS_SECRET_ACCESS_KEY |
AWS secret access key | - |
AWS_REGION |
AWS region | us-east-1 |
AWS_S3_HOST |
Custom S3 host (for S3-compatible services) | - |
AWS_S3_PORT |
Custom S3 port | 443 |
AWS_S3_PATH_STYLE |
Use path-style addressing (for MinIO) | false |
AWS_S3_SCHEME |
URL scheme (http or https) |
https |
Configure repositories in config/runtime.exs:
config :hex_hub, :repositories, [
%{
name: "my-org",
private: true,
public_key: "base64-encoded-public-key"
}
]# Run all tests
mix test
# Run with coverage
mix test --cover
# Run specific test file
mix test test/hex_hub_web/controllers/api/package_controller_test.exsNo database setup required - HexHub uses Mnesia for data storage. Mnesia will automatically initialize on first run.
For testing:
# Mnesia will be automatically configured for tests
# No manual setup needed# Format code
mix format
# Check for issues
mix credo
# Type checking (if using dialyzer)
mix dialyzerFor production deployment:
- Configure Mnesia clustering if needed
- Set up persistent storage for Mnesia data
- Configure SSL/TLS
- Set up monitoring and logging
- Configure file storage (local or S3)
HexHub uses Mnesia for data storage. Data is stored in:
Mnesia.<node_name>/directory for Mnesia tablespriv/storage/directory for package and documentation files
For persistence, ensure these directories are backed up and restored as needed.
Pre-built Docker images are available on GitHub Container Registry:
# Pull the latest image
docker pull ghcr.io/gsmlg-dev/hex-hub:main
# Run with basic configuration
docker run -d \
--name hex-hub \
-p 4000:4000 \
-p 4001:4001 \
-e SECRET_KEY_BASE=$(openssl rand -base64 48) \
-e PHX_HOST=localhost \
-v hex_hub_data:/app/priv/storage \
-v hex_hub_mnesia:/app/Mnesia.hex_hub@localhost \
ghcr.io/gsmlg-dev/hex-hub:mainOr use Docker Compose:
version: '3.8'
services:
hex-hub:
image: ghcr.io/gsmlg-dev/hex-hub:main
ports:
- "4000:4000" # Main API
- "4001:4001" # Admin dashboard
environment:
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
- PHX_HOST=${PHX_HOST:-localhost}
volumes:
- hex_hub_data:/app/priv/storage
- hex_hub_mnesia:/app/Mnesia.hex_hub@localhost
volumes:
hex_hub_data:
hex_hub_mnesia:For building from source:
FROM elixir:1.18-alpine
WORKDIR /app
COPY . .
RUN mix local.hex --force && \
mix local.rebar --force && \
mix deps.get && \
mix compile && \
mix assets.deploy
EXPOSE 4000 4001
CMD ["mix", "phx.server"]- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -am 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
- Use strong API keys
- Enable HTTPS in production
- Implement rate limiting
- Regular security audits
- Monitor access logs
MIT License - see LICENSE for details.