-
Notifications
You must be signed in to change notification settings - Fork 1
Getting Started
This walkthrough goes from "you have a Linux/macOS shell" to "you have a single-binary HTTP server serving an authenticated CRUD endpoint." It assumes no prior tep knowledge.
You need:
- A C compiler (
cc,gcc, orclang). - GNU
make. - CRuby >= 3.4 — used only by the
bin/teptranslator at build time. The compiled binaries have no Ruby dependency at runtime. -
libsqlite3-devif you want to useTep::SQLite(most non-trivial apps do).
Linux:
sudo apt-get install build-essential libsqlite3-devmacOS:
xcode-select --install # cc + make
brew install sqlite ruby # if you don't already have themtep compiles via spinel, an ahead-of-time Ruby-to-C compiler:
git clone https://github.com/matz/spinel
cd spinel && make all
export PATH="$PWD:$PATH" # so `spinel` is on PATHmake all produces three binaries: spinel_parse, spinel_analyze,
spinel_codegen, plus a spinel shell wrapper that runs all three.
git clone https://github.com/OriPekelman/tep
cd tep && makemake builds the C runtime helper (lib/tep/sphttp.c) and the
bundled demo binaries. It also runs make test if you add the
test target. The first build takes ~15 seconds on a modern laptop.
Create hello.rb:
require 'sinatra'
get '/' do
"hello from tep"
end
get '/hi/:name' do
"hi, " + params[:name] + "!"
endBuild it:
tep build hello.rb # writes ./hello (an executable)Serve it:
./hello -p 4567 # default 1 worker; --workers N for preforkIn another shell:
curl localhost:4567/
curl localhost:4567/hi/worldThe binary is around 80 KB. It links against libc and (if you used
Tep::SQLite anywhere) libsqlite3 — that's it.
Add a counter that survives restarts:
require 'sinatra'
DB = "/tmp/hello_counter.sqlite"
on_start do
db = Tep::SQLite.new
if db.open(DB)
db.exec("CREATE TABLE IF NOT EXISTS hits (n INTEGER)")
db.exec("INSERT OR IGNORE INTO hits SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM hits)")
db.close
end
end
get '/' do
db = Tep::SQLite.new
if !db.open(DB)
halt 500, "db open failed"
end
db.exec("UPDATE hits SET n = n + 1")
n = db.first_int("SELECT n FROM hits", "")
db.close
"hits: " + n.to_s
endon_start runs once per worker before serving; it's the right place
for schema setup. See Tep::SQLite for the full surface.
Sessions are HMAC-SHA256-signed cookies — no server-side storage.
Set the secret once at boot and use the session[...] hash:
require 'sinatra'
Tep.session_secret = ENV.fetch("TEP_SESSION_SECRET")
post '/login' do
if params[:password] == ENV.fetch("APP_PASSWORD")
session["user"] = params[:user]
redirect "/"
else
halt 401, "nope"
end
end
get '/' do
who = session["user"]
if who.length == 0
redirect "/login"
else
"logged in as " + who
end
endFor password hashing (rather than a literal == against an env
var), use Tep::Password. For JWT-based session-less
auth, see Tep::Jwt.
For the full principal+delegate identity surface (human + agent
identities, provider chain, OAuth2-grant issuance), see
Tep::Auth.
Form bodies.
params[:name]picks up form fields from bothapplication/x-www-form-urlencodedandmultipart/form-data. File-upload parts (<input type="file">) are skipped in v1; a separatereq.filessurface lands later.
Add CORS to every route, plus a security-header tail:
require 'sinatra'
before do
cors = Tep::Security::Cors.new
cors.set_origin("*")
cors.set_allowed_verbs("GET,POST")
cors.before(request, response)
end
after do
Tep::Security::Headers.new.after(request, response)
endThe full filter API lives in Tep::Security.
For request logging or rate limiting, you write your own
Tep::Filter subclass and chain it from a before block.
The binary is statically linked against everything but libc and (if
used) libsqlite3, so deploys are usually scp ./your-app server:/srv/
- a tiny systemd unit. There's no Ruby runtime to install on the server.
A minimal systemd unit:
# /etc/systemd/system/your-app.service
[Unit]
Description=Your tep app
After=network.target
[Service]
ExecStart=/srv/your-app -p 4567 --workers 4
Restart=on-failure
User=www-data
[Install]
WantedBy=multi-user.targetThe --workers N flag preforks N worker processes; with
SO_REUSEPORT on Linux 3.9+ the kernel load-balances new
connections across them. See the README's perf section.
- The Sinatra compatibility matrix lists every Sinatra idiom and how tep handles it (or doesn't).
- The batteries reference covers each
Tep::*module with full API and examples. - The
examples/directory has end-to-end apps you can read straight through: the four-batteryagentic_chat(sub-second WS push, multi-user, agent-spawn), the largerchatbotagainst any OpenAI-compatible endpoint, plushello,blog,chat,websocket_echo. - For the lower-level
Tep::HandlerAPI and a peek at what the translator generates, see Spinel-direct in the main README.
If something breaks, file an issue with a minimal reproduction — tep deliberately exists to find spinel's edges, so "your app doesn't build" is a useful data point.