Add landing page + Caddy download host (site/)

Self-contained, no-JS landing page (tinygrad-minimal) that doubles as the
artifact download host, served by Caddy on an internal :80 box.

- site/index.html: static page, "Same tool, only a little different", feature
  highlights (remote->remote, symmetric panels, ssh_config awareness, edit-in-
  place, self-copy guard, ...), fixed stable download links, FAQ (unsigned notes).
- site/Caddyfile: one flat /dl/ folder with browse + range requests and per-type
  Cache-Control (patches immutable, update.json no-cache, installers short).
- site/deploy.sh: drop a build's artifacts/stable-* into /dl/ (renames the win
  portable zip to a stable name).
- Flat folder matches electrobun's artifacts/ layout, so it's also the future
  updater baseUrl; per-build folders/symlink dropped in favor of it.
- docs/build-and-release.md: document the download host + deploy flow.

Verified: caddy validate + live smoke test (links, cache headers, 206 range,
404 fallback, deploy.sh copy/rename) against a temp root.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-14 15:59:05 +02:00
co-authored by Claude
parent a0f56822cd
commit 43f1168d6a
6 changed files with 325 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# miniSCP landing page + artifact host.
# Local / internal, plain HTTP on :80 (no domain, no TLS).
# Deploy target is /srv/miniscp — see site/README.md.
:80 {
root * /srv/miniscp
encode zstd gzip
# Flat artifact feed: these files are BOTH the human downloads AND (later) the
# electrobun updater baseUrl. Publishing a build = drop its artifacts/ files here;
# stable-named installers overwrite in place, hashed .patch files accumulate.
handle /dl/* {
# Incremental update patches have unique hashed names → cache forever.
@patch path *.patch
header @patch Cache-Control "public, max-age=31536000, immutable"
# The updater feed must always reflect the newest published version.
@feed path *update.json
header @feed Cache-Control "no-cache"
# Installers/payloads keep stable names but get overwritten each release.
@pkg path *.dmg *.zip *.tar.zst
header @pkg Cache-Control "public, max-age=300"
# browse => directory listing; file_server serves range requests
# (resumable large .dmg downloads) by default.
file_server browse
}
# Static landing page + assets. No SPA index fallback: unknown paths 404.
handle {
file_server
}
}
+69
View File
@@ -0,0 +1,69 @@
# miniSCP site
The all-in-one landing page **and** download host for miniSCP. One static page, one flat
download folder, served by [Caddy](https://caddyserver.com). No JS, no build step.
## Layout
```
site/
index.html # the landing page (pure static HTML/CSS)
Caddyfile # local :80 server config
deploy.sh # copy a build's artifacts into the download folder
assets/ # images (logo, screenshots) — dropped in as they're ready
```
Deploys to `/srv/miniscp` on the server:
```
/srv/miniscp/
index.html
assets/
dl/ # flat folder of all artifacts (the downloads + future updater feed)
```
## Run it
```bash
# from the repo root, on the server
sudo mkdir -p /srv/miniscp/dl
sudo cp site/index.html /srv/miniscp/
sudo cp -r site/assets /srv/miniscp/ # once assets exist
caddy run --config site/Caddyfile --adapter caddyfile
# or install as a service: sudo caddy start --config site/Caddyfile
```
Then open `http://<host>/`.
## Publish a build
electrobun can't cross-compile, so each OS/arch is built on its own machine. On each build
machine, after producing artifacts, drop them into the shared `dl/` folder:
```bash
bun scripts/release.ts # or --target mac-x64 / win-x64
site/deploy.sh # -> /srv/miniscp/dl
```
Serving on a different box than you build on? Stage locally, then sync:
```bash
MINISCP_DL_DIR=./_dl site/deploy.sh
rsync -av ./_dl/ user@host:/srv/miniscp/dl/
```
That's the whole workflow: **build, drop the artifacts, done.** Installers keep stable names and
overwrite in place, so the download links on the page never change.
> **Don't delete old `*.patch` files.** They're the incremental-update trail for the (upcoming)
> built-in updater — removing them forces older installs to re-download the full app.
## Notes
- Downloads live under `/dl/` and are browsable at `http://<host>/dl/`.
- The page links to fixed, stable filenames (`stable-macos-arm64-miniSCP.dmg`, …) so it stays
static and always points at the latest build.
- Builds are currently unsigned — the FAQ on the page covers the macOS `xattr` step and the
Windows SmartScreen prompt.
- `/dl/` is also the intended `release.baseUrl` for the future electrobun auto-updater.
+11
View File
@@ -0,0 +1,11 @@
# Image assets
Drop images here as they're ready; the page references them by relative path.
Planned slots (commented into `../index.html`):
- `logo.svg` — hero logo above the wordmark (`.logo`, 128×128). Source lives at repo
`assets/logo.svg`.
- `screenshot.png` — product shot under the intro paragraphs (`.shot`).
To wire one up, uncomment the matching `<img>` in `index.html`.
Executable
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
# Publish this machine's built artifacts into the served flat download folder.
#
# Run from the repo root AFTER building for this host's target, e.g.:
# bun scripts/release.ts # or --target mac-x64 / win-x64
# site/deploy.sh
#
# electrobun cannot cross-compile, so each OS/arch is built (and deployed) on its
# own machine; every run just adds/overwrites that target's files in one flat dir.
# Installers keep stable names (overwrite in place); .patch files accumulate — never
# delete old ones, or users on older versions lose their incremental update trail.
#
# Serving locally? Leave MINISCP_DL_DIR unset (defaults to /srv/miniscp/dl).
# Serving on a remote box? Point MINISCP_DL_DIR at a local staging dir, then rsync:
# MINISCP_DL_DIR=./_dl site/deploy.sh
# rsync -av ./_dl/ user@host:/srv/miniscp/dl/
dl="${MINISCP_DL_DIR:-/srv/miniscp/dl}"
mkdir -p "$dl"
# electrobun's flat artifacts (stable-<os>-<arch>-*): installers, update payloads,
# update.json, and (once release.baseUrl is set) .patch files.
if compgen -G 'artifacts/stable-*' >/dev/null; then
cp -f artifacts/stable-* "$dl"/
else
echo "deploy: WARN — no artifacts/stable-* found (did scripts/release.ts run?)" >&2
fi
# Windows portable zip is a release.ts extra (build-numbered) → give it a stable name.
port=$(ls -1 release/win-x64/*-win-x64-portable.zip 2>/dev/null | head -n1 || true)
if [ -n "$port" ]; then
cp -f "$port" "$dl/stable-win-x64-miniSCP-portable.zip"
fi
echo "Published to $dl:"
ls -1 "$dl"
+149
View File
@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>miniSCP: a dual-pane SFTP file manager</title>
<style>
body {
font-family: 'Lucida Console', Menlo, Consolas, monospace;
font-size: 18px;
line-height: 1.5;
color: #111;
background-color: white;
max-width: 1000px;
margin: 0 auto;
padding: 0 50px;
box-sizing: border-box;
}
a { color: inherit; }
.landing {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh; min-height: 100dvh;
text-align: center;
}
.wordmark { font-size: 64px; font-weight: bold; letter-spacing: -2px; margin: 0; }
.wordmark .lo { color: #999; }
.tagline { color: #555; margin: 10px 0 0; }
/* Image slot for the logo — drop assets/logo.svg in later.
<img class="logo" src="assets/logo.svg" alt="miniSCP"> */
.logo { width: 128px; height: 128px; display: block; margin: 0 auto 10px; }
.links-container { font-size: 22px; margin: 24px; }
.links-container a { text-decoration: none; margin: 0 6px; }
.separator { letter-spacing: -9px; color: #ccc; }
hr { border: none; height: 2px; background-color: lightgrey; width: 100%; margin: 40px auto; }
h2 { font-weight: bold; }
.feat { margin: 0 0 14px; }
.feat b { display: inline; }
/* Screenshot slot — drop assets/screenshot.png in later.
<img class="shot" src="assets/screenshot.png" alt="miniSCP screenshot"> */
.shot { display: block; max-width: 100%; margin: 20px auto; border: 1px solid #ddd; }
.downloads { display: flex; flex-wrap: wrap; gap: 12px; }
.dl {
flex: 1 1 200px;
text-decoration: none;
border: 2px solid #111;
padding: 14px 18px;
text-align: center;
}
.dl:hover { background: #111; color: #fff; }
.dl small { display: block; color: #888; font-size: 13px; margin-top: 4px; }
.dl:hover small { color: #bbb; }
.browse { color: #888; font-size: 14px; margin-top: 14px; }
.faqtable dt { margin-bottom: 0.4em; font-style: italic; }
.faqtable dd { margin-left: 0; margin-bottom: 1.6em; }
code { background: #f2f2f2; padding: 1px 5px; }
</style>
</head>
<body>
<div class="landing">
<!-- logo image slot goes here later -->
<h1 class="wordmark">mini<span class="lo">SCP</span></h1>
<p class="tagline">A dual-pane SFTP file manager.<br>Same tool, only a little different.</p>
<div class="links-container">
<a href="#different">what's different</a><span class="separator">|</span>
<a href="#download">download</a><span class="separator">|</span>
<a href="#faq">FAQ</a>
</div>
</div>
<hr>
<h2 id="different">Same tool, only a little different</h2>
<p>If you've ever used a two-pane file manager to move files over SFTP, you already know how to
use miniSCP. Two panels, a connection on each side, copy between them. Nothing to relearn.</p>
<p>The difference is in the corners. Both panels are fully symmetric &mdash; either side can be
your local disk or any saved server &mdash; so a copy isn't only up or down. You can move files
<b>straight from one server to another</b>, remote to remote, without staging them on your
machine first.</p>
<p>It's the same familiar tool, sanded down where the old ones snag.</p>
<!-- screenshot slot goes here later -->
<p><b>What's different:</b></p>
<p class="feat"><b>Remote &rarr; remote.</b> Copy straight between two servers &mdash; nothing
staged on your machine.</p>
<p class="feat"><b>Symmetric panels.</b> Either side is your local disk or any saved server. Every
panel is equal.</p>
<p class="feat"><b>Knows your servers already.</b> Reads the hosts from your
<code>~/.ssh/config</code>, and imports the saved sessions from your previous client.</p>
<p class="feat"><b>Tabs that keep working.</b> Open several sessions at once; transfers keep
running in background tabs.</p>
<p class="feat"><b>Edit in place.</b> Open a remote file in your editor &mdash; every save uploads
itself, automatically, until you disconnect.</p>
<p class="feat"><b>Won't eat your files.</b> A multi-layer guard catches when the source and
destination are secretly the same file, even behind NAT or split-DNS, before it can truncate
anything.</p>
<p class="feat"><b>Honest overwrites.</b> Compares size and date, tells you which copy is newer,
and lets you replace-all or skip-all in one go.</p>
<p class="feat"><b>A queue with an ETA.</b> Per-file speed and estimate, cancel anytime.</p>
<p class="feat"><b>Permissions.</b> Rename, delete, create folders and files, and change
permissions (chmod) on remote files.</p>
<p class="feat"><b>Themes.</b> Light, dark, or follow the system &mdash; with a set of accent
tones.</p>
<p class="feat"><b>macOS and Windows.</b> macOS is the primary target; Windows 11+ (x64) is in
beta.</p>
<hr>
<h2 id="download">Download</h2>
<p>Always the latest stable build.</p>
<div class="downloads">
<a class="dl" href="/dl/stable-macos-arm64-miniSCP.dmg">macOS<small>Apple Silicon</small></a>
<a class="dl" href="/dl/stable-macos-x64-miniSCP.dmg">macOS<small>Intel</small></a>
<a class="dl" href="/dl/stable-win-x64-miniSCP-Setup.zip">Windows<small>Installer</small></a>
<a class="dl" href="/dl/stable-win-x64-miniSCP-portable.zip">Windows<small>Portable</small></a>
</div>
<p class="browse">Looking for everything? <a href="/dl/">browse all files</a>.</p>
<hr>
<h2 id="faq">FAQ</h2>
<dl class="faqtable">
<dt>Which platforms are supported?</dt>
<dd>macOS (Apple Silicon and Intel) and Windows 11+ (x64). macOS is the primary target;
Windows is currently in beta.</dd>
<dt>macOS says the app is "damaged and can't be opened".</dt>
<dd>The builds aren't code-signed yet, so macOS quarantines them on download. Clear the
quarantine flag once and it opens normally:
<br><code>xattr -cr /Applications/miniSCP.app</code></dd>
<dt>Windows warns me with a blue SmartScreen box.</dt>
<dd>The Windows builds aren't signed yet either, so SmartScreen flags them on first run. Choose
"More info" &rarr; "Run anyway". The installer unpacks to your local app data; the portable
zip runs from anywhere.</dd>
<dt>Can I bring my existing servers over?</dt>
<dd>Yes. miniSCP reads the hosts from your <code>~/.ssh/config</code> automatically, and it can
import the saved sessions exported from your previous file-transfer client.</dd>
<dt>Installer or portable on Windows?</dt>
<dd>The installer sets miniSCP up in your user profile with a desktop shortcut. The portable zip
is just the app &mdash; unzip it anywhere and run it, no install.</dd>
</dl>
</body>
</html>