Agora is an ASP.NET Core 10 file sharing service. Upload one or more files, generate a ZIP archive on disk, and share a URL for recipients to access a branded download page and download the archive.
| New Share | Download Page | Download Page Editor |
|---|---|---|
![]() |
![]() |
![]() |
- Multi-file upload with ZIP archive generation
- Share URL with download page before download
- Share-created success screen with one-click link copy
- Previous shares support reopening the Share Ready link screen
- Previous shares Details modal lists archived filenames and sizes
- Share links default to unique 8-character alphanumeric tokens and can be customized
- Download page designer supports configurable download card position (corners, edges, centered)
- Account settings include email and password update forms
- Registration requires email confirmation before first login
- Email and password changes require confirmation before they take effect
- Forgot password and password reset flows are supported
- Share defaults have a dedicated settings page
- New accounts default download page subtitle is set to
by <account email> - Signed-in downloads are excluded from download totals
- Expiry options: date-based or indefinite
- Download notifications (
none,once,every_time) - Download event metadata: IP, user-agent, timestamp
- Resend-compatible email integration with configurable API base URL
- Daily rolling Serilog file logs with 30-day retention
- Built-in rate limiting for auth, authenticated user traffic, and share downloads
- CSRF protection on unsafe HTTP methods (forms, fetch, and XHR)
- Login brute-force protection with temporary account lockout after repeated failures
src/Agora.Web- HTTP API and hosted servicesrc/Agora.Application- contracts, models, utilitiessrc/Agora.Infrastructure- EF Core persistence and service implementationssrc/Agora.Domain- domain entitiestests/Agora.Application.Tests- unit teststests/e2e- Playwright end-to-end tests
Requirements:
- .NET SDK 10.0+
Commands:
dotnet restore Agora.slnx
dotnet build Agora.slnx
dotnet test tests/Agora.Application.Tests/Agora.Application.Tests.csproj
dotnet run --project src/Agora.Web/Agora.Web.csproj --urls http://127.0.0.1:18080Then open http://127.0.0.1:18080.
Use the provided scripts to run local development with a dedicated SQL Server container and tmux orchestration:
./run-dev.shThis starts:
- tmux session
agora-dev - SQL Server container
agora-dev-sqlonlocalhost:18033 - Agora app on
http://127.0.0.1:18080
During development, emails are written to the filesystem instead of being sent:
emails/
Logs are written to:
logs/(Serilog rolling files).dev/agora-web.log(app runtime output).dev/tailwind.log(Tailwind watch output)
Stop everything:
./stop-dev.shStop and delete dev SQL container:
./stop-dev.sh --deletePlaywright tests run against a dedicated app instance on port 18090 with an isolated SQLite database:
cd tests/e2e
npm install
npx playwright install --with-deps chromium
npx playwright testThe test runner starts the app automatically. Test data is stored in .e2e-data/.
This repository includes build.csando.
Run validation pipeline (restore -> build -> test):
ando runAuthenticate to GHCR first:
echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdinRun publish profile (build/test, publish artifacts, build and push multi-arch image to GHCR):
ando run -p publish --dindBuild image:
docker build -t agora:latest .Run container (only env vars + 1 volume required):
docker run -d \
--name agora \
-p 18080:18080 \
-e Email__Resend__ApiToken="<your_token>" \
-e Email__Resend__FromDisplayName="<display_name>" \
-e Email__Resend__FromAddress="no-reply@yourdomain.com" \
-e Email__Resend__ApiUrl="https://api.resend.com" \
-e Agora__PublicBaseUrl="https://files.yourdomain.com" \
-e ConnectionStrings__Default="Data Source=/app/data/uploads/agora.db" \
-e Serilog__WriteTo__0__Args__path="/app/data/logs/agora-.log" \
-v agora_data:/app/data \
agora:latestCreate one dataset for all persistent app data, for example:
/mnt/YOUR_POOL/apps/agora/data
Agora will create and use subfolders inside this mount (for example uploads, logs, and other runtime data).
The default uploads path is /app/data/uploads.
Generate a secure password first:
openssl rand -base64 32Connect to your SQL Server (for example from sqlcmd) and run:
-- Create database
CREATE DATABASE Agora;
GO
-- Create SQL login
CREATE LOGIN agora WITH PASSWORD = 'REPLACE_WITH_STRONG_PASSWORD';
GO
-- Create DB user + grant permissions
USE Agora;
GO
CREATE USER agora FOR LOGIN agora;
GO
ALTER ROLE db_owner ADD MEMBER agora;
GOExample sqlcmd connection:
sqlcmd -S YOUR_TRUENAS_IP,1433 -U sa -P 'YOUR_SA_PASSWORD' -CIn TrueNAS:
- Open
Apps -> Discover Apps. - Click the three-dot menu (
...) and selectInstall via YAML. - Set application name to
agora. - Paste and adjust this YAML:
services:
agora:
image: ghcr.io/aduggleby/agora:latest
pull_policy: always
ports:
- "18080:18080"
environment:
- ConnectionStrings__Default=Server=YOUR_TRUENAS_IP,1433;Database=Agora;User Id=agora;Password=YOUR_AGORA_PASSWORD;TrustServerCertificate=true
- Email__Provider=Resend
- Email__Resend__ApiToken=YOUR_RESEND_API_TOKEN
- Email__Resend__FromDisplayName=YOUR_FROM_DISPLAY_NAME
- Email__Resend__FromAddress=YOUR_VERIFIED_FROM_EMAIL
- Email__Resend__ApiUrl=https://api.resend.com
- Agora__PublicBaseUrl=https://files.YOUR_DOMAIN
volumes:
- /mnt/YOUR_POOL/apps/agora/data:/app/data
restart: unless-stoppedThese settings are optional and only needed if you want to override defaults:
Serilog__WriteTo__0__Args__path(default:logs/agora-.log)Email__Resend__FromDisplayName(default: empty; uses just address if unset)Agora__PublicBaseUrl(default: request host, for examplehttps://files.example.com)Agora__MaxFilesPerShare(default:20)Agora__MaxFileSizeBytes(default:262144000/ 250 MB)Agora__MaxTotalUploadBytes(default:1073741824/ 1 GB)Agora__DownloadEventRetentionDays(default:90)Agora__ZombieUploadRetentionHours(default:24)
Rate limiting defaults (built in):
- Auth endpoints (
POST /login,POST /register,POST /login/development):10 requests/minuteper source IP - Authenticated requests (global):
120 requests/minuteper authenticated account - Download endpoint (
GET /s/{token}/download):20 requests/minuteper(token, source IP)pair
YOUR_TRUENAS_IP: IP of your TrueNAS host.YOUR_AGORA_PASSWORD: password used inCREATE LOGIN.YOUR_POOL: your TrueNAS pool name.YOUR_RESEND_API_TOKEN: Resend (or compatible provider) token.YOUR_FROM_DISPLAY_NAME: friendly sender name shown in recipient inboxes.YOUR_VERIFIED_FROM_EMAIL: sender address verified in your provider.
After install, open:
http://YOUR_TRUENAS_IP:18080/
To update, edit the app and bump image tag (or keep latest).
Agora uses reserved range 18000-18099.
Default HTTP port: 18080.


