Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<https://github.com/megalabs-dev/rustle/security/advisories/new> and write "conduct" in the title, or contact a maintainer
<https://github.com/megabuild-dev/Rustle/security/advisories/new> 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
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<div align="center">

# Rustle
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/brand/logo-horizontal-dark.svg">
<img src="assets/brand/logo-horizontal.svg" alt="Rustle" height="84">
</picture>

**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)
Expand Down Expand Up @@ -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
```

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
(<https://github.com/megalabs-dev/rustle/security/advisories/new>).
(<https://github.com/megabuild-dev/Rustle/security/advisories/new>).
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
Expand Down
34 changes: 34 additions & 0 deletions assets/brand/README.md
Original file line number Diff line number Diff line change
@@ -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).
Binary file added assets/brand/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/brand/favicon-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/brand/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 92 additions & 0 deletions assets/brand/generate.py
Original file line number Diff line number Diff line change
@@ -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'<path d="{d}" fill="none" stroke="{fill}" stroke-width="{w}" stroke-linecap="round" opacity="{o}"/>' for d, w, o in WIND)
veins = "".join(f'<path d="{d}"/>' for d in VEINS)
defs = "" if mono else f'<linearGradient id="g{uid}" x1="60" y1="210" x2="210" y2="40" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="{GREEN_LO}"/><stop offset="1" stop-color="{GREEN_HI}"/></linearGradient>'
defs += f'<mask id="m{uid}" maskUnits="userSpaceOnUse" x="0" y="0" width="256" height="256"><rect width="256" height="256" fill="#fff"/>' \
f'<g fill="none" stroke="#000" stroke-linecap="round"><path d="{RIB}" stroke-width="7"/><g stroke-width="4.5">{veins}</g></g></mask>'
return (f"<defs>{defs}</defs>{wind}"
f'<g mask="url(#m{uid})"><path d="{LEAF}" fill="{fill}"/></g>'
f'<path d="{STEM}" fill="none" stroke="{fill}" stroke-width="9" stroke-linecap="round"/>')


def svg(w, h, vb, body, title):
return f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="{vb}" role="img" aria-label="{title}"><title>{title}</title>{body}</svg>\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'<g transform="translate(0 0)">{mark_body("h" + suffix)}</g><path d="{d}" fill="{ink}"/>'
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'<rect width="256" height="256" rx="56" fill="{NAVY}"/><g transform="translate(22 20) scale(.88)">{mark_body("i")}</g>')
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"))
Binary file added assets/brand/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/brand/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/brand/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/brand/logo-horizontal-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading