diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 07e90ee..5586e51 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Report a security vulnerability - url: https://github.com/megalabs-dev/rustle/security/advisories/new + url: https://github.com/megabuild-dev/Rustle/security/advisories/new about: Private reporting. Please do not open a public issue. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 1dc134b..4970b5a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -29,7 +29,7 @@ Maintainers may remove, edit or reject comments, commits, issues and other contr and may temporarily or permanently ban a contributor for behaviour they judge harmful. To report a problem, open a private security advisory at - and write "conduct" in the title, or contact a maintainer + and write "conduct" in the title, or contact a maintainer directly through GitHub. Reports are handled confidentially and reviewed promptly and fairly. This code applies in all project spaces (issues, pull requests, discussions) and when someone represents the project diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03695dc..004d90c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ must stay under 8 MB idle (hard cap 48 MB) on a 1 vCPU / 1 GB VPS. A feature tha You need Rust 1.80+ (stable), and for the dashboard `trunk` and the `wasm32-unknown-unknown` target. ```bash -git clone https://github.com/megalabs-dev/rustle && cd rustle +git clone https://github.com/megabuild-dev/Rustle && cd rustle cargo test -p rustle-types -p rustle-server # about 5 s with dependencies optimized scripts/build-ui.sh # builds the dashboard into ui-dist/ cargo run -p rustle-server --features dev-fakes # in-memory Docker/Caddy/GitHub, see docs/DEV.md diff --git a/Cargo.toml b/Cargo.toml index 3400fbc..47309ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ license = "MIT" rust-version = "1.80" authors = ["megalabs-dev and Rustle contributors"] description = "Ultra-light self-hosted PaaS for small VPSs: deploy stateless Next.js/Node apps from GHCR images behind Caddy, with the platform itself under 8 MB of RAM." -repository = "https://github.com/megalabs-dev/rustle" -homepage = "https://github.com/megalabs-dev/rustle" +repository = "https://github.com/megabuild-dev/Rustle" +homepage = "https://github.com/megabuild-dev/Rustle" readme = "README.md" keywords = ["paas", "self-hosted", "deployment", "caddy", "docker"] categories = ["command-line-utilities", "web-programming::http-server"] diff --git a/README.md b/README.md index e94df02..f5995bf 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@
-# Rustle + + + Rustle + **An ultra-light, self-hosted PaaS for small VPSs.** Build on GitHub Actions, deploy to your own server, keep the platform itself under 8 MB of RAM. -[![CI](https://github.com/megalabs-dev/rustle/actions/workflows/ci.yml/badge.svg)](https://github.com/megalabs-dev/rustle/actions/workflows/ci.yml) +[![CI](https://github.com/megabuild-dev/Rustle/actions/workflows/ci.yml/badge.svg)](https://github.com/megabuild-dev/Rustle/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ![Idle RAM](https://img.shields.io/badge/idle%20RAM-5.8%20MB-brightgreen) ![Binary](https://img.shields.io/badge/binary-5%20MB-brightgreen) @@ -72,7 +75,7 @@ Screenshots come from the headless-browser test (`scripts/ui_e2e.js`) running ag Ubuntu 22.04 / 24.04, as root on the VPS: ```bash -curl -fsSL https://github.com/megalabs-dev/rustle/releases/latest/download/rustle-installer.tar.gz | tar xz +curl -fsSL https://github.com/megabuild-dev/Rustle/releases/latest/download/rustle-installer.tar.gz | tar xz sudo ./install/install.sh --domain admin.example.com --email you@example.com ``` @@ -93,7 +96,7 @@ GitHub secret, and push. ## Build from source ```bash -git clone https://github.com/megalabs-dev/rustle && cd rustle +git clone https://github.com/megabuild-dev/Rustle && cd rustle scripts/build-ui.sh # dashboard -> ui-dist/ (needs trunk and the wasm32 target) cargo build --release -p rustle-server # target/release/rustle cargo test -p rustle-types -p rustle-server @@ -121,6 +124,11 @@ cargo test -p rustle-types -p rustle-server | Releasing and signing | [docs/RELEASING.md](docs/RELEASING.md) | | Implementation plan and design changes | [docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md), [docs/DECISIONS_ADDENDUM.md](docs/DECISIONS_ADDENDUM.md) | +## Brand + +The logo is a leaf lifted by the wind: light enough to ride on a 1 GB server. The name is the sound leaves make. Logo, icons +and the social preview are in [assets/brand](assets/brand) (MIT, like the rest of the project). + ## Contributing Contributions are welcome, especially verification reports from real servers. Read [CONTRIBUTING.md](CONTRIBUTING.md) and the diff --git a/SECURITY.md b/SECURITY.md index d567cf3..47e4a36 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ variables, and sits in front of your sites. Security reports are taken seriously **Do not open a public issue.** Use GitHub's private reporting instead: 1. Go to the repository's **Security** tab, then **Report a vulnerability** - (). + (). 2. Describe what you found, how to reproduce it, and the impact you expect. You will get a first answer within **3 working days**. We aim to ship a fix within **30 days** for high-severity issues diff --git a/assets/brand/README.md b/assets/brand/README.md new file mode 100644 index 0000000..ca0daf6 --- /dev/null +++ b/assets/brand/README.md @@ -0,0 +1,34 @@ +# Rustle brand assets + +A leaf lifted by the wind: light enough to ride on a 1 GB server. "Rustle" is also the sound leaves make. + +| File | Use | +|---|---| +| `logo-mark.svg` | The leaf alone, transparent; works on light and dark backgrounds | +| `logo-mark-mono.svg` | One colour (`currentColor`), for print or single-colour contexts | +| `logo-horizontal.svg` / `logo-horizontal-dark.svg` | Leaf + wordmark for light / dark backgrounds (text is outlined, no font needed) | +| `icon.svg`, `favicon.svg` | App icon on the dashboard navy; `favicon.svg` is also the dashboard favicon | +| `icon-512.png`, `icon-192.png`, `apple-touch-icon.png`, `favicon-32.png`, `logo-mark-512.png` | Raster exports | +| `social-preview.png` | 1280 x 640 image for the repository (Settings, Social preview) and link cards | + +## Colours + +| | | +|---|---| +| Leaf gradient | `#10b981` to `#5eead4` | +| Dashboard accent | `#34d399` | +| Navy | `#0b1020` | +| Ink on light / dark | `#0b1020` / `#f1f5f9` | + +Typeface: [Inter](https://rsms.me/inter/) (SIL Open Font License). Keep clear space around the mark of at least the width of the +widest wind stroke, and do not recolour the leaf outside the palette. + +## Regenerating + +```bash +python3 -m pip install fonttools +python3 assets/brand/generate.py # SVGs +node assets/brand/render.js # PNGs and the social preview (playwright, Inter installed) +``` + +The social preview must be uploaded by hand: repository Settings, General, Social preview (GitHub has no API for it). diff --git a/assets/brand/apple-touch-icon.png b/assets/brand/apple-touch-icon.png new file mode 100644 index 0000000..9ed4a4f Binary files /dev/null and b/assets/brand/apple-touch-icon.png differ diff --git a/assets/brand/favicon-32.png b/assets/brand/favicon-32.png new file mode 100644 index 0000000..9e90206 Binary files /dev/null and b/assets/brand/favicon-32.png differ diff --git a/assets/brand/favicon.svg b/assets/brand/favicon.svg new file mode 100644 index 0000000..f7a2be1 --- /dev/null +++ b/assets/brand/favicon.svg @@ -0,0 +1 @@ +Rustle diff --git a/assets/brand/generate.py b/assets/brand/generate.py new file mode 100644 index 0000000..f05260c --- /dev/null +++ b/assets/brand/generate.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Generates the Rustle brand assets (SVG mark, wordmarks, icons) into this directory. + + python3 -m pip install fonttools + python3 assets/brand/generate.py + node assets/brand/render.js # PNGs and the social preview (needs playwright) + +The wordmark is converted to outlines (Inter, SIL OFL) so the SVGs look the same everywhere. +""" +import os +from fontTools.ttLib import TTFont +from fontTools.pens.svgPathPen import SVGPathPen +from fontTools.pens.transformPen import TransformPen + +HERE = os.path.dirname(os.path.abspath(__file__)) +INTER = "/usr/share/fonts/opentype/inter/InterDisplay-Bold.otf" + +GREEN_HI, GREEN_LO = "#5eead4", "#10b981" # leaf gradient (matches the dashboard accent family) +NAVY = "#0b1020" # dashboard background +INK_LIGHT, INK_DARK = "#0b1020", "#f1f5f9" # wordmark colours on light / dark backgrounds + +# --- the mark, 256 x 256 ------------------------------------------------------------------------ +# A single leaf tilted as if lifted by a gust, with three wind strokes trailing behind it. +LEAF = "M72 196 C58 122 112 58 206 46 C204 138 148 196 72 196 Z" +import math +_P0, _P1 = (80.0, 188.0), (186.0, 74.0) # rib: from the base almost to the tip (never cuts the edge) +RIB = f"M{_P0[0]:.0f} {_P0[1]:.0f} L{_P1[0]:.0f} {_P1[1]:.0f}" +_dx, _dy = _P1[0] - _P0[0], _P1[1] - _P0[1] +_len = math.hypot(_dx, _dy) +_ux, _uy = _dx / _len, _dy / _len # unit vector along the rib +VEINS = [] +for t, ln in ((.32, 40), (.55, 34), (.76, 24)): # vein pairs sweeping toward the tip + px, py = _P0[0] + _dx * t, _P0[1] + _dy * t + for side in (-1, 1): + ang = math.radians(48) * side + vx = _ux * math.cos(ang) - _uy * math.sin(ang) + vy = _ux * math.sin(ang) + _uy * math.cos(ang) + VEINS.append(f"M{px:.1f} {py:.1f} L{px + vx * ln:.1f} {py + vy * ln:.1f}") +WIND = [("M16 104 C46 96 74 108 98 94", 7, 1.0), ("M30 138 C54 132 70 142 90 134", 6, .75), ("M10 72 C30 66 46 74 60 68", 5, .55)] +STEM = "M72 196 L50 222" + + +def mark_body(uid="a", mono=None): + fill = mono or f"url(#g{uid})" + wind = "".join(f'' for d, w, o in WIND) + veins = "".join(f'' for d in VEINS) + defs = "" if mono else f'' + defs += f'' \ + f'{veins}' + return (f"{defs}{wind}" + f'' + f'') + + +def svg(w, h, vb, body, title): + return f'{title}{body}\n' + + +def text_path(text, size, x, y): + font = TTFont(INTER) + gs, cmap, upm = font.getGlyphSet(), font.getBestCmap(), font["head"].unitsPerEm + scale, cur, out = size / upm, 0, [] + hmtx = font["hmtx"] + for ch in text: + name = cmap[ord(ch)] + pen = SVGPathPen(gs) + gs[name].draw(TransformPen(pen, (scale, 0, 0, -scale, x + cur * scale, y))) + out.append(pen.getCommands()) + cur += hmtx[name][0] - (size * 0.012 / scale) # slightly tighter tracking + return " ".join(out), cur * scale + + +def write(name, content): + with open(os.path.join(HERE, name), "w") as f: + f.write(content) + print("wrote", name) + + +# mark: works on light and dark backgrounds +write("logo-mark.svg", svg(256, 256, "0 0 256 256", mark_body("m"), "Rustle")) +write("logo-mark-mono.svg", svg(256, 256, "0 0 256 256", mark_body("o", mono="currentColor"), "Rustle")) + +# horizontal lockups (mark + wordmark outlines), light and dark backgrounds +for suffix, ink in (("", INK_LIGHT), ("-dark", INK_DARK)): + d, width = text_path("Rustle", 150, 292, 176) + body = f'{mark_body("h" + suffix)}' + write(f"logo-horizontal{suffix}.svg", svg(int(292 + width + 8), 256, f"0 0 {int(292 + width + 8)} 256", body, "Rustle")) + +# app icon: mark on the dashboard's navy, rounded +icon_body = (f'{mark_body("i")}') +write("icon.svg", svg(256, 256, "0 0 256 256", icon_body, "Rustle")) +write("favicon.svg", svg(64, 64, "0 0 256 256", icon_body, "Rustle")) diff --git a/assets/brand/icon-192.png b/assets/brand/icon-192.png new file mode 100644 index 0000000..73e8aa3 Binary files /dev/null and b/assets/brand/icon-192.png differ diff --git a/assets/brand/icon-512.png b/assets/brand/icon-512.png new file mode 100644 index 0000000..b8d387d Binary files /dev/null and b/assets/brand/icon-512.png differ diff --git a/assets/brand/icon.svg b/assets/brand/icon.svg new file mode 100644 index 0000000..5a7c197 --- /dev/null +++ b/assets/brand/icon.svg @@ -0,0 +1 @@ +Rustle diff --git a/assets/brand/logo-horizontal-dark.svg b/assets/brand/logo-horizontal-dark.svg new file mode 100644 index 0000000..c033ded --- /dev/null +++ b/assets/brand/logo-horizontal-dark.svg @@ -0,0 +1 @@ +Rustle diff --git a/assets/brand/logo-horizontal.svg b/assets/brand/logo-horizontal.svg new file mode 100644 index 0000000..b5694f1 --- /dev/null +++ b/assets/brand/logo-horizontal.svg @@ -0,0 +1 @@ +Rustle diff --git a/assets/brand/logo-mark-512.png b/assets/brand/logo-mark-512.png new file mode 100644 index 0000000..a600027 Binary files /dev/null and b/assets/brand/logo-mark-512.png differ diff --git a/assets/brand/logo-mark-mono.svg b/assets/brand/logo-mark-mono.svg new file mode 100644 index 0000000..39fb9ed --- /dev/null +++ b/assets/brand/logo-mark-mono.svg @@ -0,0 +1 @@ +Rustle diff --git a/assets/brand/logo-mark.svg b/assets/brand/logo-mark.svg new file mode 100644 index 0000000..59ce5ae --- /dev/null +++ b/assets/brand/logo-mark.svg @@ -0,0 +1 @@ +Rustle diff --git a/assets/brand/render.js b/assets/brand/render.js new file mode 100644 index 0000000..4a8e5fe --- /dev/null +++ b/assets/brand/render.js @@ -0,0 +1,22 @@ +// Renders PNG icons and the GitHub social preview from the SVG/HTML sources in this directory. +// usage: node assets/brand/render.js (needs playwright; fonts: Inter) +const { chromium } = require('playwright'); const fs = require('fs'); const path = require('path'); +const D = __dirname; const rd = n => fs.readFileSync(path.join(D, n), 'utf8'); +(async () => { + const b = await chromium.launch(); const p = await b.newPage(); + for (const [name, size] of [['icon-512', 512], ['icon-192', 192], ['apple-touch-icon', 180], ['favicon-32', 32]]) { + await p.setViewportSize({ width: size, height: size }); + await p.setContent(`${rd('icon.svg').replace('${rd('logo-mark.svg').replace(']*>/, ''))); + await p.waitForTimeout(300); + await p.screenshot({ path: path.join(D, 'social-preview.png') }); + await b.close(); +})(); diff --git a/assets/brand/social-preview.html b/assets/brand/social-preview.html new file mode 100644 index 0000000..f27d9c3 --- /dev/null +++ b/assets/brand/social-preview.html @@ -0,0 +1,32 @@ + + +
+ + + + + + +
__MARK__
+
+
Rustle
+
Deploy on a 1 GB VPS.
The platform stays under 8The platform uses 8 MB.nbsp;MB.
+
Self-hosted PaaS for stateless Next.js and Node apps. Build on GitHub Actions, serve behind Caddy.
+
RustCaddyDockerMIT
+
+
github.com/megabuild-dev/Rustle
+
diff --git a/assets/brand/social-preview.png b/assets/brand/social-preview.png new file mode 100644 index 0000000..a346271 Binary files /dev/null and b/assets/brand/social-preview.png differ diff --git a/crates/rustle-server/src/config.rs b/crates/rustle-server/src/config.rs index f95334d..1fbce04 100644 --- a/crates/rustle-server/src/config.rs +++ b/crates/rustle-server/src/config.rs @@ -102,7 +102,7 @@ impl Config { network: str_or("RUSTLE_NETWORK", "rustle-net"), cgroup_root: path_or("RUSTLE_CGROUP_ROOT", "/sys/fs/cgroup"), github_api: str_or("RUSTLE_GITHUB_API", "https://api.github.com"), - release_repo: str_or("RUSTLE_RELEASE_REPO", "megalabs-dev/rustle"), + release_repo: str_or("RUSTLE_RELEASE_REPO", "megabuild-dev/Rustle"), update_pubkey: option_env!("RUSTLE_UPDATE_PUBKEY_HEX").and_then(parse_pubkey), bin_dir: state_dir.join("bin"), proc_root: path_or("RUSTLE_PROC_ROOT", "/proc"), diff --git a/crates/rustle-ui/favicon.svg b/crates/rustle-ui/favicon.svg new file mode 100644 index 0000000..f7a2be1 --- /dev/null +++ b/crates/rustle-ui/favicon.svg @@ -0,0 +1 @@ +Rustle diff --git a/crates/rustle-ui/index.html b/crates/rustle-ui/index.html index b5c4d6f..3f10ba4 100644 --- a/crates/rustle-ui/index.html +++ b/crates/rustle-ui/index.html @@ -5,6 +5,7 @@ Rustle + diff --git a/deploy/rustle.service b/deploy/rustle.service index 131ebf9..818f9cf 100644 --- a/deploy/rustle.service +++ b/deploy/rustle.service @@ -1,6 +1,6 @@ [Unit] Description=Rustle admin (ultra-light PaaS) -Documentation=https://github.com/megalabs-dev/rustle +Documentation=https://github.com/megabuild-dev/Rustle After=docker.service network-online.target Wants=docker.service network-online.target diff --git a/docs/DEV.md b/docs/DEV.md index 00ad392..b3a198f 100644 --- a/docs/DEV.md +++ b/docs/DEV.md @@ -41,7 +41,7 @@ Use the same topology as production, never a mounted `docker.sock`: | `RUSTLE_NETWORK` | `rustle-net` | Docker network created by the installer | | `RUSTLE_PUBLIC_IP` / `RUSTLE_ADMIN_DOMAIN` / `RUSTLE_ACME_EMAIL` | none | read by `rustle init` | | `RUSTLE_CGROUP_ROOT`, `RUSTLE_PROC_ROOT` | `/sys/fs/cgroup`, `/proc` | overridable for tests | -| `RUSTLE_GITHUB_API`, `RUSTLE_RELEASE_REPO` | `https://api.github.com`, `megalabs-dev/rustle` | backups and updates | +| `RUSTLE_GITHUB_API`, `RUSTLE_RELEASE_REPO` | `https://api.github.com`, `megabuild-dev/Rustle` | backups and updates | | `RUST_LOG` | `info` | `error`, `warn`, `info`, `debug`, `trace` | ## CLI diff --git a/install/install.sh b/install/install.sh index 8519e76..b79d742 100755 --- a/install/install.sh +++ b/install/install.sh @@ -10,7 +10,7 @@ # # Environment overrides: # RUSTLE_VERSION release tag to install (default: latest) -# RUSTLE_REPO GitHub repo hosting releases (default: megalabs-dev/rustle) +# RUSTLE_REPO GitHub repo hosting releases (default: megabuild-dev/Rustle) # RUSTLE_BINARY path to a local rustle binary (skips the download; for testing) # RUSTLE_ADMIN_DOMAIN dashboard domain (same as --domain) RUSTLE_ACME_EMAIL (same as --email) # RUSTLE_ACK_KEY=1 confirm non-interactively that you will save the printed master key @@ -19,7 +19,7 @@ # RUSTLE_INSTALL_DRY_RUN=1 print every step, change nothing set -euo pipefail -REPO="${RUSTLE_REPO:-megalabs-dev/rustle}" +REPO="${RUSTLE_REPO:-megabuild-dev/Rustle}" VERSION="${RUSTLE_VERSION:-latest}" PROXY_IMAGE="${PROXY_IMAGE:-tecnativa/docker-socket-proxy:0.3.0}" CADDY_IMAGE="${CADDY_IMAGE:-caddy:2-alpine}" diff --git a/scripts/build-ui.sh b/scripts/build-ui.sh index 424e862..9eecb9e 100755 --- a/scripts/build-ui.sh +++ b/scripts/build-ui.sh @@ -12,7 +12,7 @@ trap 'rm -rf "$OUT"' EXIT # Keep the committed placeholder out of the way, stage the real build. find ui-dist -mindepth 1 ! -name '.gitkeep' -delete -cp "$OUT"/*.js "$OUT"/*.wasm "$OUT"/*.css ui-dist/ +cp "$OUT"/*.js "$OUT"/*.wasm "$OUT"/*.css "$OUT"/*.svg ui-dist/ # The page is served with a strict CSP (no inline scripts): move Trunk's inline bootstrap into boot.js. python3 - "$OUT/index.html" ui-dist <<'PY' import re, sys, pathlib diff --git a/scripts/github-setup.sh b/scripts/github-setup.sh index cbd45cd..a90d5c7 100755 --- a/scripts/github-setup.sh +++ b/scripts/github-setup.sh @@ -5,7 +5,7 @@ # scripts/github-setup.sh # configure, keep the repository private # scripts/github-setup.sh --make-public # also publish it (cannot be fully undone: forks and caches persist) set -euo pipefail -REPO="${REPO:-megalabs-dev/rustle}" +REPO="${REPO:-megabuild-dev/Rustle}" api() { gh api -H "Accept: application/vnd.github+json" "$@"; } echo "== description, homepage, features" @@ -24,13 +24,22 @@ api -X PUT "repos/$REPO/topics" \ -f 'names[]=ghcr' -f 'names[]=github-actions' -f 'names[]=zero-downtime' -f 'names[]=low-memory' \ -f 'names[]=ci-cd' -f 'names[]=open-source' >/dev/null +if [ "${1:-}" = "--make-public" ]; then + # Before protection: branch protection on a private repository needs a paid plan. + echo "== making the repository public" + api -X PATCH "repos/$REPO" -F private=false >/dev/null +fi + +# Each of these can be refused (plan limits, feature not available); report and continue. +try() { local what="$1"; shift; if "$@" >/dev/null 2>&1; then echo " ok: $what"; else echo " skipped: $what (not available for this repository or plan)"; fi; } + echo "== security features" -api -X PUT "repos/$REPO/vulnerability-alerts" >/dev/null || true -api -X PUT "repos/$REPO/automated-security-fixes" >/dev/null || true -api -X PUT "repos/$REPO/private-vulnerability-reporting" >/dev/null || true +try "vulnerability alerts" api -X PUT "repos/$REPO/vulnerability-alerts" +try "automated security fixes" api -X PUT "repos/$REPO/automated-security-fixes" +try "private vulnerability reporting" api -X PUT "repos/$REPO/private-vulnerability-reporting" echo "== branch protection for main (after CI has run once, add the job names under required_status_checks)" -api -X PUT "repos/$REPO/branches/main/protection" --input - >/dev/null <<'JSON' +try "branch protection" api -X PUT "repos/$REPO/branches/main/protection" --input - <<'JSON' { "required_status_checks": null, "enforce_admins": false, @@ -41,9 +50,4 @@ api -X PUT "repos/$REPO/branches/main/protection" --input - >/dev/null <<'JSON' "allow_deletions": false } JSON - -if [ "${1:-}" = "--make-public" ]; then - echo "== making the repository public" - api -X PATCH "repos/$REPO" -F private=false >/dev/null -fi echo "done"