DEV Community

jiahui dong
jiahui dong

Posted on Originally published at resume.tensorview.cc

How I Ship a Controlled Download Site: HTML + Private Object Storage + VPS

This article was first published on my site: https://resume.tensorview.cc/blog/secure-download-site-flask-qiniu.html
This is the real flow I used — front-end page, private object-storage URLs, then a VPS. Screenshots are from an anonymized demo brand.

Your outline is right: page → object storage → wire uploads → rent a VPS → git and run. What usually fails in freelance delivery is not “can you write HTML,” but the edges: where secrets live, how links expire, chat-app cache, and the reverse proxy. Below is build, then deploy, plus the traps I hit.

Demo download page (desktop): brand nav, Windows button, feature cards.
Demo download page (desktop): brand nav, Windows button, feature cards.

1. Lock acceptance before you code

Agree what “done” means before coding. Otherwise you become their unpaid upload clerk every time the app binary changes. At minimum, put these acceptance checks in the quote.

  • Can download: PC and Android each get a package; the button gives clear feedback.
  • Can expire: signed URLs time out (often 24h) and stop working.
  • No secrets in the browser: keys only in server env/config.
  • Chat cache does not stick: opening in WeChat/Line-like in-apps shows the latest page, not last week’s copy.
  • Re-upload rounds: how many free binary updates; extras billed per push.

2. Build: the front-end pages

First deliverable is a download-center lookalike: download, FAQ, support. The download page switches desktop/mobile; FAQ locks install steps with screenshots; support lists regional contacts. Technically: HTML/CSS plus a little JS. The backend only mints download URLs.

  • Download button: POST for a URL, then navigate; 5s cooldown stops double-taps.
  • Asset busting: version query on CSS/JS/images so clients see CSS updates.
  • Anonymize for portfolio: swap company name, ICP, real support contacts before blogging screenshots.

Same page on the mobile tab: QR + Android button.
Same page on the mobile tab: QR + Android button.

Narrow viewport: brand + nav stack for phone acceptance checks.
Narrow viewport: brand + nav stack for phone acceptance checks.

3. Build: object storage (Qiniu in this case)

Do not host installers as naked files on the VPS — bandwidth and leak control both suffer. Put binaries in a private object bucket; the backend SDK mints time-limited signed URLs. This case used Qiniu; overseas, the same pattern maps to S3 or R2.

  • Bucket: create a private space; bind a download CDN domain.
  • Keys: create Access/Secret keys for the server only — never commit, never ship to JS.
  • Object keys: agree paths like exe/app-windows.exe and exe/app-android.apk; same names front and back.
  • Optional CDN locks: timestamp hotlink protection / per-IP rate limits on top of signed URLs.

4. Build: upload binaries and wire the API

After uploading to the right keys, the backend (Flask here) exposes POST /api/download with type=pc|android. It reads client IP, applies a short rate window, calls the private URL API, and returns { success, url }. The front-end navigates to that URL. GET /api/health helps smoke-test deploys.

  • Smoke test: curl the API — expect a long URL with e= and token=; opening it should start the download.
  • Rate limit: e.g. 2 requests / 5s per IP; over limit → 429 + wait_time for the UI.
  • Config: prefer env vars; commit examples only, never real secrets.

5. Deploy: rent a VPS

A small VPS is enough (1C2G often fine for this guide site). Ubuntu LTS, open 80/443, SSH keys only. Point DNS A records at the public IP. If the client owns the domain, they add the record; otherwise accept on IP / temp domain first.

6. Deploy: git, run, reverse-proxy

One chain: git clone/pull → venv → pip install → env vars → python app.py for dev, gunicorn bound to 127.0.0.1:5000 in prod. Nginx in front: static files + /api reverse-proxy. Finish with certbot HTTPS.

  • systemd: enable on boot and restart on failure — fewer midnight WeChat pings.
  • Logs: keep gunicorn/nginx error paths; first ask if it is 502 or 429.
  • Permissions: service user needs read on the app tree; tighten secret file modes.

FAQ page: screenshot-backed install steps cut repeat support.
FAQ page: screenshot-backed install steps cut repeat support.

Support page (demo data): regional mail placeholders — swap for client contacts in production.
Support page (demo data): regional mail placeholders — swap for client contacts in production.

7. Details that bite in production

  • In-app cache: Cache-Control: no-store on HTML; append _t= timestamps. Small detail, big for WeChat-like clients.
  • Wrong object key: bucket has exe/foo.exe but code says foo.exe → Document not found. Match the console path.
  • Domain vs CDN: site domain and file CDN domain can differ; renew certs before expiry.
  • Written checklist: health URL, download URL, expired-link failure, in-app screenshot — send it to the client.

8. End-to-end flow (quote appendix)

Build: three pages → private bucket + CDN → upload binaries → Flask mint API wired to buttons. Deploy: VPS → git → venv → env → gunicorn + Nginx + HTTPS → accept in-app and expired-link cases. Clear edges and screenshotable checks make fixed-price gigs viable — if re-upload rounds are priced.

Screenshots are anonymized demos, not a real client brand. Treat this as a checklist if you take similar gigs — same road as my Taobao-group and AI side-job notes, different slice.


Original post: https://resume.tensorview.cc/blog/secure-download-site-flask-qiniu.html

Top comments (0)