This commit is contained in:
24c02 2026-01-30 13:36:50 -05:00
parent 4258ae17e9
commit 77ce6315a5
2 changed files with 142 additions and 114 deletions

View file

@ -1,14 +1,58 @@
# S3 Config CF in this example
AWS_ACCESS_KEY_ID=1234567890abcdef
AWS_SECRET_ACCESS_KEY=abcdef1234567890
AWS_BUCKET_NAME=my-cdn-bucket
AWS_REGION=auto
AWS_ENDPOINT=https://<accountid>.r2.cloudflarestorage.com
AWS_CDN_URL=https://cdn.beans.com
# =============================================================================
# Cloudflare R2 Storage (S3-compatible)
# =============================================================================
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_BUCKET_NAME=your-bucket-name
R2_ENDPOINT=https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com
# API
API_TOKEN=beans # Set a secure random string
PORT=3000
# Public hostname for CDN URLs (used in generated links)
CDN_HOST=cdn.hackclub.com
# Sentry
SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id
# =============================================================================
# Hack Club OAuth
# =============================================================================
# Get credentials from Hack Club Auth (https://auth.hackclub.com)
HACKCLUB_CLIENT_ID=your_client_id
HACKCLUB_CLIENT_SECRET=your_client_secret
# Optional: Override auth URL (defaults to staging in dev, production in prod)
# HACKCLUB_AUTH_URL=https://auth.hackclub.com
# =============================================================================
# Encryption Keys
# =============================================================================
# Generate with: ruby -e "require 'securerandom'; puts SecureRandom.hex(32)"
LOCKBOX_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000
BLIND_INDEX_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000
# Active Record Encryption (generate with: bin/rails db:encryption:init)
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=your_primary_key
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=your_deterministic_key
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=your_key_derivation_salt
# =============================================================================
# Database (production only - dev uses cdn_development/cdn_test)
# =============================================================================
# DATABASE_HOST=localhost
# DATABASE_USER=cdn
# DATABASE_PASSWORD=your_password
# DATABASE_NAME=cdn_production
# Solid Cache/Queue/Cable databases (optional, defaults provided)
# CACHE_DATABASE_HOST=localhost
# QUEUE_DATABASE_HOST=localhost
# CABLE_DATABASE_HOST=localhost
# =============================================================================
# Optional
# =============================================================================
# Sentry error tracking
# SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id
# HashID salt (defaults to SECRET_KEY_BASE if not set)
# HASHID_SALT=your_hashid_salt
# Rails configuration
# PORT=3000
# RAILS_MAX_THREADS=5
# SECRET_KEY_BASE=your_secret_key_base

188
README.md
View file

@ -14,122 +14,106 @@
</a>
</div>
---
## 📡 API Usage
A Rails 8 application for hosting and managing CDN uploads, with OAuth authentication via Hack Club.
- All API endpoints require authentication via `Authorization: Bearer api-token` header
- Use the API_TOKEN from your environment configuration
- Failure to include a valid token will result in 401 Unauthorized responses
## Prerequisites
### V3 API (Latest)
<img alt="Version 3" src="https://files.catbox.moe/e3ravk.png" align="right" width="300">
- Ruby 3.4.4 (see `.ruby-version`)
- PostgreSQL
- Node.js + Yarn (for Vite frontend)
- A Cloudflare R2 bucket (or S3-compatible storage)
**Endpoint:** `POST https://cdn.hackclub.com/api/v3/new`
## Setup
**Headers:**
```
Authorization: Bearer api-token
Content-Type: application/json
```
1. **Clone and install dependencies:**
```bash
git clone https://github.com/hackclub/cdn.git
cd cdn
bundle install
yarn install
```
**Request Example:**
2. **Configure environment variables:**
```bash
cp .env.example .env
```
Edit `.env` with your credentials (see below for details).
3. **Setup the database:**
```bash
bin/rails db:create db:migrate
```
4. **Generate encryption keys** (for API key encryption):
```bash
# Generate a 32-byte hex key for Lockbox
ruby -e "require 'securerandom'; puts SecureRandom.hex(32)"
# Generate a 32-byte hex key for BlindIndex
ruby -e "require 'securerandom'; puts SecureRandom.hex(32)"
# Generate Active Record encryption keys
bin/rails db:encryption:init
```
5. **Start the development servers:**
```bash
# In one terminal, run the Vite dev server:
bin/vite dev
# In another terminal, run the Rails server:
bin/rails server
```
## Environment Variables
See `.env.example` for the full list. Key variables:
| Variable | Description |
|----------|-------------|
| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key |
| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret key |
| `R2_BUCKET_NAME` | R2 bucket name |
| `R2_ENDPOINT` | R2 endpoint URL |
| `CDN_HOST` | Public hostname for CDN URLs |
| `HACKCLUB_CLIENT_ID` | OAuth client ID from Hack Club Auth |
| `HACKCLUB_CLIENT_SECRET` | OAuth client secret |
| `LOCKBOX_MASTER_KEY` | 64-char hex key for encrypting API keys |
| `BLIND_INDEX_MASTER_KEY` | 64-char hex key for searchable encryption |
## API
The API uses bearer token authentication. Create an API key from the web dashboard after logging in.
**Upload a file:**
```bash
curl --location 'https://cdn.hackclub.com/api/v3/new' \
--header 'Authorization: Bearer beans' \
--header 'Content-Type: application/json' \
--data '[
"https://assets.hackclub.com/flag-standalone.svg",
"https://assets.hackclub.com/flag-orpheus-left.png"
]'
curl -X POST https://cdn.hackclub.com/api/v4/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@image.png"
```
**Response:**
```json
{
"files": [
{
"deployedUrl": "https://hc-cdn.hel1.your-objectstorage.com/s/v3/64a9472006c4472d7ac75f2d4d9455025d9838d6_flag-standalone.svg",
"file": "0_64a9472006c4472d7ac75f2d4d9455025d9838d6_flag-standalone.svg",
"sha": "64a9472006c4472d7ac75f2d4d9455025d9838d6",
"size": 4365
},
{
"deployedUrl": "https://hc-cdn.hel1.your-objectstorage.com/s/v3/d926bfd9811ebfe9172187793a171a5cbcc61992_flag-orpheus-left.png",
"file": "1_d926bfd9811ebfe9172187793a171a5cbcc61992_flag-orpheus-left.png",
"sha": "d926bfd9811ebfe9172187793a171a5cbcc61992",
"size": 8126
}
],
"cdnBase": "https://hc-cdn.hel1.your-objectstorage.com"
}
**Upload from URL:**
```bash
curl -X POST https://cdn.hackclub.com/api/v4/upload_from_url \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/image.png"}'
```
<details>
<summary>V2 API</summary>
See `/docs` in the running app for full API documentation.
<img alt="Version 2" src="https://files.catbox.moe/uuk1vm.png" align="right" width="300">
## Architecture
**Endpoint:** `POST https://cdn.hackclub.com/api/v2/new`
**Headers:**
```
Authorization: Bearer api-token
Content-Type: application/json
```
**Request Example:**
```json
[
"https://assets.hackclub.com/flag-standalone.svg",
"https://assets.hackclub.com/flag-orpheus-left.png"
]
```
**Response:**
```json
{
"flag-standalone.svg": "https://cdn.example.dev/s/v2/flag-standalone.svg",
"flag-orpheus-left.png": "https://cdn.example.dev/s/v2/flag-orpheus-left.png"
}
```
</details>
<details>
<summary>V1 API</summary>
<img alt="Version 1" src="https://files.catbox.moe/tnzdfe.png" align="right" width="300">
**Endpoint:** `POST https://cdn.hackclub.com/api/v1/new`
**Headers:**
```
Authorization: Bearer api-token
Content-Type: application/json
```
**Request Example:**
```json
[
"https://assets.hackclub.com/flag-standalone.svg",
"https://assets.hackclub.com/flag-orpheus-left.png"
]
```
**Response:**
```json
[
"https://cdn.example.dev/s/v1/0_flag-standalone.svg",
"https://cdn.example.dev/s/v1/1_flag-orpheus-left.png"
]
```
</details>
# Technical Details
- **Storage Structure:** `/s/v3/{HASH}_{filename}`
- **File Naming:** `/s/{slackUserId}/{unix}_{sanitizedFilename}`
- **Rails 8** with **Vite** for frontend assets
- **Phlex** + **Primer ViewComponents** for UI
- **Active Storage** with Cloudflare R2 backend
- **Solid Queue/Cache/Cable** for background jobs and caching (production)
- **Pundit** for authorization
- **Lockbox + BlindIndex** for API key encryption
<div align="center">
<br>
<p>Made with 💜 for Hack Club</p>
</div>
</div>