Skip to main content

Self-hosted static site hosting. Upload HTML, get a URL.

Project description

sitedrop

Upload an HTML file. Get a URL. That's it.

Self-hosted static site hosting. Deploy single HTML pages to your own server with one command.

pip install sitedrop
sitedrop login
sitedrop upload portfolio.html
# => Live at https://yourserver.com/portfolio

Features

  • One command deploysitedrop upload file.html and it's live
  • Clean URLsportfolio.html becomes /portfolio
  • Self-hosted — runs on your own VM, you own your data
  • Single package — same pip install for both server and client
  • Simple auth — password login with JWT tokens, managed by the CLI
  • Full control — upload, update, delete, and list your sites
  • No build step — upload raw HTML, no frameworks or config needed
  • Public viewing — anyone can view deployed sites, only you can manage them

Quick Start

Server (on your VM)

pip install sitedrop
sitedrop setup
sitedrop start

Client (on your laptop)

pip install sitedrop
sitedrop login
sitedrop upload portfolio.html
# => Live at https://yourserver.com/portfolio

Same package, different commands.

CLI Reference

Server Commands

Run these on your VM.

sitedrop setup

Interactive setup. Sets the admin password and configures the server bind address.

sitedrop setup
Set admin password: ********
Confirm password: ********
Bind host [127.0.0.1]:
Bind port [8000]:
Config saved to ~/.sitedrop/server.json

Use --dev to skip host/port prompts and default to 127.0.0.1:8000:

sitedrop setup --dev

sitedrop start

Start the server.

sitedrop start

This starts the server as a background process. Use --dev to run in the foreground with auto-reload:

sitedrop start --dev

For direct HTTPS (without a reverse proxy), pass your certificate and key:

sitedrop start --ssl-certfile cert.pem --ssl-keyfile key.pem

SSL paths are saved in server config so sitedrop restart preserves them.

sitedrop stop

Stop the server. Sends SIGTERM for a graceful shutdown, falling back to SIGKILL after 5 seconds.

sitedrop stop

sitedrop restart

Restart the server (stop + start). Supports --dev and SSL options.

sitedrop restart

sitedrop status

Check if the server is running.

sitedrop status

Client Commands

Run these from anywhere.

sitedrop login

Authenticate with your server. Saves credentials locally.

sitedrop login
# Server URL [http://localhost:8000]: https://yourserver.com
# Password: ********
# Logged in to https://yourserver.com

Credentials are saved to ~/.sitedroprc (chmod 600) so you only need to do this once. The CLI automatically refreshes expired tokens using the saved password.

sitedrop upload <file>

Upload an HTML file. The site name is derived from the filename.

sitedrop upload portfolio.html        # => /portfolio
sitedrop upload my-blog.html          # => /my-blog
sitedrop upload index.html            # => /index

Override the name with --name:

sitedrop upload page.html --name about   # => /about

If a site with that name already exists, the CLI prompts for confirmation. Use --force to skip the prompt and overwrite:

sitedrop upload page.html --force

sitedrop list

List all deployed sites.

sitedrop list
Name                     Size  Modified
------------------------------------------------------------
portfolio                4200  2026-02-20T10:30:00+00:00
my-blog                 12100  2026-02-19T08:15:00+00:00

sitedrop delete <name>

Remove a deployed site. Prompts for confirmation before deleting. Use --yes / -y to skip the prompt.

sitedrop delete portfolio
# Delete portfolio? [y/N]: y
# Deleted portfolio

sitedrop delete portfolio --yes
# Deleted portfolio

sitedrop info

Show the configured server URL and token status (valid, expired, or not logged in).

sitedrop info

sitedrop password

Change the server password. Prompts for your current and new password, then updates saved credentials. All previously issued tokens are invalidated.

sitedrop password

API Reference

The CLI is a wrapper around a REST API. You can also use it directly with curl.

All management endpoints require a JWT token in the Authorization header. Get a token by authenticating with your password.

Authentication

POST /api/auth

Get a JWT token.

curl -X POST https://yourserver.com/api/auth \
  -H "Content-Type: application/json" \
  -d '{"password": "your-password"}'
{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Tokens expire after 24 hours.

POST /api/password

Change your password. Requires authentication.

curl -X POST https://yourserver.com/api/password \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"current_password": "current", "new_password": "new-one"}'

GET /api/health

Health check. No authentication required.

curl https://yourserver.com/api/health
{
  "status": "ok"
}

Sites

PUT /api/sites/{name}

Upload or update a site. Send the HTML as the request body.

curl -X PUT https://yourserver.com/api/sites/portfolio \
  -H "Authorization: Bearer <token>" \
  --data-binary @portfolio.html

To prevent accidental overwrites, send If-None-Match: * — the server returns 412 Precondition Failed if the site already exists:

curl -X PUT https://yourserver.com/api/sites/portfolio \
  -H "Authorization: Bearer <token>" \
  -H "If-None-Match: *" \
  --data-binary @portfolio.html
{
  "name": "portfolio",
  "size": 4200,
  "modified": "2026-02-20T10:30:00+00:00"
}

Site names must match ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ and be at most 240 characters. Maximum upload size is 10 MB.

GET /api/sites

List all deployed sites. Requires authentication.

curl https://yourserver.com/api/sites \
  -H "Authorization: Bearer <token>"
{
  "sites": [
    {
      "name": "portfolio",
      "size": 4200,
      "modified": "2026-02-20T10:30:00+00:00"
    }
  ]
}

DELETE /api/sites/{name}

Delete a site. Requires authentication.

curl -X DELETE https://yourserver.com/api/sites/portfolio \
  -H "Authorization: Bearer <token>"

Viewing Sites

GET /{name}

View a deployed site. No authentication required.

curl https://yourserver.com/portfolio

Returns the HTML content directly. Non-existent sites return 404 Not Found as plain text.

Served sites include ETag (SHA-256 of content) and Cache-Control: no-cache headers. Browsers cache the content but revalidate on each request — unchanged content returns 304 Not Modified.

Server Prerequisites

  • Python 3.10+
  • A VM (Google Cloud, AWS, DigitalOcean, etc.)
  • A reverse proxy (nginx, caddy, etc.) for HTTPS and public access, or use --ssl-certfile/--ssl-keyfile for direct TLS

Configuration

Server

Server configuration is stored in ~/.sitedrop/server.json:

{
  "password_hash": "$2b$12$...",
  "jwt_secret": "auto-generated-on-first-run",
  "host": "127.0.0.1",
  "port": 8000,
  "sites_dir": "~/.sitedrop/sites",
  "ssl_certfile": null,
  "ssl_keyfile": null
}

Client

Client configuration is stored in ~/.sitedroprc (chmod 600):

{
  "server_url": "https://yourserver.com",
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "password": "saved-for-auto-refresh"
}

The CLI automatically refreshes expired tokens using the saved password.

Limits

  • File size: 10 MB per upload
  • Site name: 240 characters max, alphanumeric plus hyphens and underscores, must start with alphanumeric
  • File format: Must be valid UTF-8 text
  • Auth rate limit: 5 login attempts per minute per IP

API Error Codes

Code Meaning
400 Invalid site name or non-UTF-8 content
401 Wrong password or expired/invalid token
404 Site not found
412 Site already exists (when If-None-Match: * is sent)
413 File exceeds 10 MB limit
429 Too many login attempts
500 Server not configured (run sitedrop setup)

Security

  • Always use HTTPS in production. Either use --ssl-certfile/--ssl-keyfile for direct TLS, or put a reverse proxy (nginx, caddy) in front of sitedrop.
  • Protect ~/.sitedroprc. The client stores your password in plaintext to enable automatic token refresh. The file is created with chmod 600 (owner read/write only). Treat it like your SSH keys.
  • Uploaded HTML is served as-is. Sitedrop does not sanitize HTML content. If you upload a page with malicious scripts, visitors will run them.
  • Passwords are hashed with bcrypt on the server. The JWT secret is auto-generated (256-bit) on first setup and rotated on every password change, invalidating all existing tokens.
  • Login attempts are rate-limited to 5 per minute per IP to protect against brute-force attacks.
  • The server sets X-Content-Type-Options: nosniff and X-Frame-Options: DENY on all responses.
  • API responses include a Content-Security-Policy header (default-src 'none'; frame-ancestors 'none'). This header is not applied to served sites so their content renders normally.

Environment Variables

Variable Default Description
SITEDROP_CONFIG ~/.sitedrop/server.json Override server config file path
SITEDROP_CLIENT_CONFIG ~/.sitedroprc Override client config file path

Useful for Docker deployments or running multiple instances.

How It Works

User                    CLI                     Server                  Visitor
 |                       |                       |                       |
 |-- sitedrop upload f.html-->|                    |                       |
 |                       |-- PUT /api/sites/f -->|                       |
 |                       |                       |-- save f.html to disk |
 |                       |<-- 200 OK ------------|                       |
 |<-- Live at /f --------|                       |                       |
 |                       |                       |                       |
 |                       |                       |<-- GET /f ------------|
 |                       |                       |-- serve f.html ------>|

The server is a single FastAPI application that handles both the management API and site serving:

  • API routes (/api/*) require JWT authentication and handle CRUD operations
  • Site routes (/{name}) are public and serve stored HTML files directly
  • HTML files are stored on the filesystem at ~/.sitedrop/sites/{name}.html
  • Passwords are hashed with bcrypt, JWTs are signed with a server-generated secret

Development

Run the server locally for development:

git clone https://github.com/williamtao345/sitedrop.git
cd sitedrop

python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"

# Set up with a test password
sitedrop setup --dev

# Run the server with auto-reload
sitedrop start --dev

Test the CLI against your local server:

sitedrop login
# Server URL [http://localhost:8000]:
# Password: your-test-password

sitedrop upload test.html
# => Live at http://localhost:8000/test

Run tests:

pytest

Troubleshooting

"Cannot connect to server" — The client can't reach the server. Check that the server is running (sitedrop status) and that the URL in ~/.sitedroprc is correct.

"Authentication failed" — Wrong password or expired token. The CLI auto-refreshes tokens, but if your password changed, run sitedrop login again.

"Too many login attempts" — You've been rate-limited. Wait 60 seconds and try again.

"File too large" — Upload exceeds the 10 MB limit.

"File must be valid UTF-8" — You tried to upload a binary file. Sitedrop only hosts UTF-8 HTML.

"Server failed to start" — Usually means the port is already in use. Check with lsof -i :8000 or change the port with sitedrop setup.

Server appears started but isn't responding — Check sitedrop status. If it says "not running (stale PID file)", the server crashed after starting. Check your reverse proxy logs or run sitedrop start --dev to see errors in the foreground.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sitedrop-0.0.5.tar.gz (33.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sitedrop-0.0.5-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

File details

Details for the file sitedrop-0.0.5.tar.gz.

File metadata

  • Download URL: sitedrop-0.0.5.tar.gz
  • Upload date:
  • Size: 33.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for sitedrop-0.0.5.tar.gz
Algorithm Hash digest
SHA256 249e3fc2869d598e011658c5df8485252edef8db3526beb6c20968e91ce800ae
MD5 4b09a978084f0227f47936259e2e97d4
BLAKE2b-256 28627d4dc177f672a382464422e493ec45691d34dce72c2e5381173a5ecb5753

See more details on using hashes here.

File details

Details for the file sitedrop-0.0.5-py3-none-any.whl.

File metadata

  • Download URL: sitedrop-0.0.5-py3-none-any.whl
  • Upload date:
  • Size: 22.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for sitedrop-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 89689c6d2a2a5be2659d103a881885cab8d16373c0e61b75e2ef961e8e7e8342
MD5 7c064f083c784744788f29f1294b77c4
BLAKE2b-256 39e7b949792a10693074fb97aeda8c88dc0df959985f5b8242d003b59dd7f157

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page