Patch releases of laravel/boost, pestphp/pest, phpunit/phpunit and filp/whoops, plus refreshed transitive npm packages. Boost's update refreshed its laravel-best-practices skill. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SealShare
A simple, self-hosted file sharing solution built with Laravel. Upload files, get a shareable link, done. All files are encrypted at rest with AES-256-GCM.
Website: sealshare.nonameweb.ch
Screenshots
Features
- File Uploading — Drag & drop or browse to upload single/multiple files and folders with real-time progress; large files go up in chunks, each retried on its own if the connection drops
- Shareable Links — Each upload generates a unique link for recipients, also as a QR code (saved as a PNG) or through the device's share sheet
- Encryption at Rest — Files are encrypted in the uploader's browser, chunk by chunk with AES-256-GCM, before they are sent, and are stored only in encrypted form; with a share password the share's key is wrapped with a key derived from it (Argon2id) and never stored as it is. It is not end-to-end encryption: the server issues the key, checks each chunk, and decrypts the files for downloads
- Password Protection — Optionally protect shares with a password, typed or generated (random characters or a passphrase, as the admin configures) and copied on the upload page or next to the new link
- Expiration — Shares auto-expire after a configurable duration (1 hour to 30 days)
- Download Limits — Set a maximum number of downloads per share
- ZIP Downloads — Download all files in a share as a single ZIP archive, streamed as it is built, whatever the files' size
- Auto-Cleanup — Expired shares and files are automatically deleted (hourly)
- Admin Dashboard — View, manage, and delete all shares
- Admin Settings — Configure upload limits, storage quotas, branding, and more
- Site Branding — Custom logo, title, and description
- Colour Profiles — Eight Material 3 colour profiles (Indigo, Blue, Teal, Green, Amber, Rose, Violet, Graphite); the admin picks one for every page, mail and error page
- System Password — Optional global password gate to restrict upload access
- User Authentication — Login, password reset, email verification
- Two-Factor Authentication — TOTP-based 2FA via Laravel Fortify
- Light and Dark Themes — Material 3 Expressive design that follows the system theme, or light or dark by choice
- Setup Wizard — First-run wizard to create the initial admin account
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Laravel 13 |
| Application Server | FrankenPHP (via Laravel Octane) |
| Frontend | Livewire 4, Livewire Material (Material 3 Expressive) |
| Authentication | Laravel Fortify |
| Encryption | Chunked AES-256-GCM (WebCrypto in the browser), keys wrapped with Argon2id |
| ZIP Downloads | ZipStream-PHP |
| Testing | Pest 5 with browser tests (Playwright) |
| Code Style | Laravel Pint |
| Build Tool | Vite |
Installation — Development
Docker (recommended)
docker-compose.dev.yml extends the production stack (docker-compose.yml, app and scheduler): the checkout mounted at /app, Octane reloading on PHP changes, and a Vite dev server with HMR. .env selects it through COMPOSE_FILE, so plain docker compose commands work.
cp .env.example .env
# Set APP_KEY (composer setup generates one) and the values for your setup (below)
# Build and start the app, the scheduler and Vite
docker compose up -d --build
# View logs
docker compose logs -f
With OrbStack, no ports are published: set these in .env and open https://app.sealshare.orb.local. Uploads need HTTPS or localhost, because browsers only encrypt files there.
COMPOSE_FILE=docker-compose.dev.yml
APP_URL=https://app.sealshare.orb.local
VITE_DEV_SERVER_URL=https://vite.sealshare.orb.local
Without OrbStack, publish the ports on 127.0.0.1 and open http://localhost:8000 (change the ports with APP_PORT and VITE_PORT):
COMPOSE_FILE=docker-compose.dev.yml:docker-compose.ports.yml
APP_URL=http://localhost:8000
The containers read .env when they are created: run docker compose up -d again after changing it.
Installation — Production
Docker (recommended)
mkdir sealshare && cd sealshare
curl -O https://gitea.nonameweb.ch/noNameWEB/SealShare/raw/branch/main/docker-compose.example.yml
cp docker-compose.example.yml docker-compose.yml
# Generate an app key and paste it into docker-compose.yml
docker run --rm gitea.nonameweb.ch/nonameweb/sealshare:latest php artisan key:generate --show
# Edit docker-compose.yml — set APP_KEY and APP_URL, and choose how HTTPS is served (below)
# Then start:
docker compose up -d
Migrations run automatically on startup. Open your configured domain — the Setup Wizard will create the first admin account.
Key environment variables:
| Variable | Required | Description |
|---|---|---|
APP_KEY |
Yes | Laravel encryption key |
APP_URL |
Yes | Full URL (e.g. https://share.example.com) |
AUTO_HTTPS |
No | true to fetch a Let's Encrypt certificate for SERVER_NAME and serve HTTPS on port 443 (port 80 redirects); default false, plain HTTP on port 80 for a reverse proxy |
SERVER_NAME |
With AUTO_HTTPS |
The domain to fetch the certificate for (e.g. share.example.com) |
UPLOAD_CHUNK_SIZE_MB |
No | Size of each encrypted chunk the browser sends; default 16 |
HTTPS is required for uploads. Files are encrypted in the uploader's browser with WebCrypto, which browsers only offer over HTTPS or on localhost; over plain HTTP the upload page says so and takes no files (downloads keep working). Either set AUTO_HTTPS: "true" with SERVER_NAME — ports 80 and 443 must be reachable from the internet — or put a reverse proxy that terminates TLS in front of port 80.
Volumes:
| Volume | Path | Purpose |
|---|---|---|
sealshare_storage |
/app/storage/app |
Encrypted uploaded files |
sealshare_database |
/app/database/sqlite |
SQLite database (DB_DATABASE: /app/database/sqlite/database.sqlite) |
caddy_data |
/data |
TLS certificates |
caddy_config |
/config |
Caddy configuration |
A docker-compose.yml from before 2.1.1 mounts sealshare_database at /app/database, which also hides the image's migrations; the container adds the ones the volume is missing on startup, so it keeps working. To move to the layout above, mount the same volume at /app/database/sqlite and set DB_DATABASE: /app/database/sqlite/database.sqlite in both services — the existing database is at that path then, and nothing is lost.
Large files:
Files go up in chunks of UPLOAD_CHUNK_SIZE_MB, one request each, so PHP's upload limits and a proxy's request timeout do not limit a file's size. What does:
| Limit | Where | Default |
|---|---|---|
| Max file size / Max size per share | Admin → Settings | 100 MB / 2 GB |
| Storage quota | Admin → Settings | 20 GB — files still uploading count towards it |
UPLOAD_CHUNK_SIZE_MB |
Environment | 16 |
Behind a reverse proxy, its request body limit must be a little larger than a chunk (nginx: client_max_body_size 32m;), and proxy_request_buffering off; keeps nginx from writing each chunk to its own temporary files. PHP_UPLOAD_MAX_FILESIZE / PHP_POST_MAX_SIZE (default 64M) only apply to the admin's logo upload. An upload no chunk reached for 4 hours is deleted by the hourly cleanup.
Manual (without Docker)
git clone https://gitea.nonameweb.ch/noNameWEB/SealShare.git
cd SealShare
composer install --no-dev --optimize-autoloader
npm install && npm run build
cp .env.example .env
php artisan key:generate
# Edit .env — set APP_ENV=production, APP_DEBUG=false, APP_URL=https://your-domain.com
touch database/database.sqlite
php artisan migrate --force
php artisan storage:link
php artisan config:cache
php artisan route:cache
php artisan view:cache
Start with Octane:
php artisan octane:frankenphp --host=0.0.0.0 --port=80
Or point your web server (Nginx/Apache) to the public/ directory for a traditional PHP-FPM setup.
Add the scheduler to your crontab:
* * * * * cd /path-to-sealshare && php artisan schedule:run >> /dev/null 2>&1
License
This project is open-source software licensed under the MIT License.
Generated passphrases draw from the EFF Large Wordlist by the Electronic Frontier Foundation, licensed under CC BY 3.0 US (resources/wordlists/eff-large-wordlist.txt, without its four hyphenated words).


