shrt

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package shrt shortens long URLs into terminal-safe luko.to forms and serves the redirector that expands them back. Static rules here are the single source of truth for both directions; the CLI shortens offline with them and the server expands with the same table, so they must ship from one commit.

Index

Constants

View Source
const DefaultBase = "https://luko.to"

DefaultBase is the public redirector origin.

View Source
const MintThreshold = 40

MintThreshold is the URL length below which minting is skipped: shorter URLs never wrap in the panes this tool exists for (decision 0002 item 7), and a code buys nothing over a URL that is already short.

Variables

View Source
var ErrRuleExists = errors.New("rule already exists")

ErrRuleExists reports a create colliding with an existing name.

View Source
var ErrRuleNotFound = errors.New("rule not found")

ErrRuleNotFound reports an update/delete of a missing name.

View Source
var ErrTokenExists = errors.New("token already exists")

ErrTokenExists reports an issue colliding with an existing name.

View Source
var ErrTokenNotFound = errors.New("token not found")

ErrTokenNotFound reports a revoke of a missing name.

Functions

func Code

func Code(long string) string

Code derives the permanent, idempotent short code for a URL.

func CodeN

func CodeN(long string, n int) string

CodeN is Code at an explicit prefix length (collision extension).

func ExpandDynamic

func ExpandDynamic(rule Rule, tail string) string

ExpandDynamic resolves /<name>[/tail] back to the long URL.

func ExpandStatic

func ExpandStatic(path string) (long string, ok bool)

ExpandStatic resolves a static short path (no leading slash) back to its long URL. ok is false when the path matches no rule or an unknown alias.

func LoadToken

func LoadToken() string

LoadToken resolves the mint token: $LUKO_TOKEN first, then the macOS Keychain (service luko.to, account mint). Empty means static-only mode.

func OSC8

func OSC8(label, target string) string

OSC8 wraps label as a terminal hyperlink to target (Warp, iTerm2, kitty…).

func ParseEnrollCIDRs

func ParseEnrollCIDRs(value string) ([]netip.Prefix, error)

ParseEnrollCIDRs parses the comma-separated LUKO_ENROLL_CIDRS value. An empty input returns nil — enrollment disabled, fail closed.

func RuleCachePath

func RuleCachePath(origin string) (string, error)

RuleCachePath is where the CLI caches a redirector's dynamic rules for offline matching — one file PER ORIGIN, so a staging --base never overwrites the production cache.

func SaveRuleCache

func SaveRuleCache(origin string, rules []Rule) error

SaveRuleCache writes the origin's rule cache.

func ShortenDynamic

func ShortenDynamic(rules []Rule, long string) string

ShortenDynamic returns the short path for a URL covered by a dynamic rule (longest matching prefix wins), or "" when none matches. Prefixes end with "/" (validated), so the tail never starts with one and expansion is exact concatenation.

func ShortenStatic

func ShortenStatic(long string) string

ShortenStatic returns the short path (no leading slash) for a URL covered by a static rule, or "" when the URL needs a minted code instead.

func StoreToken

func StoreToken(token string) error

StoreToken writes the mint token into the macOS Keychain.

func ValidTarget

func ValidTarget(raw string) error

ValidTarget rejects mint inputs that are not absolute http(s) URLs.

func ValidateRuleName

func ValidateRuleName(name string) error

ValidateRuleName rejects names that could collide with fixed routes.

func ValidateRulePrefix

func ValidateRulePrefix(prefix string) error

ValidateRulePrefix rejects prefixes that are not absolute http(s) URLs or don't end with "/" — the trailing slash is what makes shorten/expand an exact concatenation with no separator guessing.

Types

type Client

type Client struct {
	Base     string // redirector origin, DefaultBase when empty
	Token    string // mint bearer token; empty means static-only
	DynRules []Rule // cached dynamic rules for offline matching (see LoadRuleCache)
	HTTP     *http.Client
}

Client shortens URLs: static rules offline first, the mint API otherwise.

func (Client) CreateRule

func (c Client) CreateRule(name, prefix string) (Rule, error)

CreateRule adds a dynamic rule server-side and refreshes the cache.

func (Client) DeleteRule

func (c Client) DeleteRule(name string) error

DeleteRule removes a rule server-side.

func (Client) Enroll

func (c Client) Enroll(name, via string) (TokenIssue, error)

Enroll self-issues a named token over the mesh. via is the mesh gateway IP to dial; the TLS handshake still verifies against the origin's hostname, so a wrong gateway fails closed rather than talking to an impostor.

func (Client) FetchRules

func (c Client) FetchRules() ([]Rule, error)

FetchRules lists the server's dynamic rules. Callers that want the offline cache updated call SaveRuleCache themselves and report failures — a silent half-refresh must not masquerade as success.

func (Client) IssueToken

func (c Client) IssueToken(name string) (TokenIssue, error)

IssueToken mints a named member token (admin only). The returned value is the only copy that will ever exist.

func (Client) ListTokens

func (c Client) ListTokens() ([]MemberToken, error)

ListTokens lists member token names (admin only) — never values.

func (Client) RevokeToken

func (c Client) RevokeToken(name string) error

RevokeToken deletes a named member token (admin only).

func (Client) Shorten

func (c Client) Shorten(long string) (Result, error)

Shorten resolves one URL. URLs already shorter than the short form (or not shortenable without a token) come back unchanged with Static/Minted false — printing something clickable always beats erroring mid-pipeline.

func (Client) UpdateRule

func (c Client) UpdateRule(name, prefix string) (Rule, error)

UpdateRule replaces a rule's prefix server-side and refreshes the cache.

type MemberToken

type MemberToken struct {
	Name      string    `json:"name"`
	Hash      string    `json:"hash"` // hex sha256 of the token value
	CreatedAt time.Time `json:"created_at"`
}

MemberToken is a named API credential. Only the SHA-256 of the value is stored — the value itself exists once, in the issue response.

type Result

type Result struct {
	Long   string `json:"long"`
	Short  string `json:"short"`
	Static bool   `json:"static"`
	Minted bool   `json:"minted"`
}

Result is one shortened URL, JSON-stable for automation.

type Rule

type Rule struct {
	Name      string    `json:"name"`
	Prefix    string    `json:"prefix"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

Rule is a dynamic prefix mapping: any URL starting with Prefix shortens to /<Name>/<tail>, and /<Name>/<tail> expands back to Prefix+tail verbatim.

func LoadRuleCache

func LoadRuleCache(origin string) []Rule

LoadRuleCache reads the origin's cached rules; a missing or corrupt cache is just an empty list — the mint path self-heals online.

type RuleStore

type RuleStore struct {
	// contains filtered or unexported fields
}

RuleStore persists dynamic rules as one JSON file, rewritten atomically on every change — rules are few and updatable, so append-only buys nothing.

func OpenRuleStore

func OpenRuleStore(path string) (*RuleStore, error)

OpenRuleStore loads (or creates) the rule store at path.

func (*RuleStore) Create

func (s *RuleStore) Create(name, prefix string) (Rule, error)

Create adds a new rule; a name already present is an error (use Update).

func (*RuleStore) Delete

func (s *RuleStore) Delete(name string) error

Delete removes a rule. Existing short links under the name stop resolving — that is the caller's deliberate choice, not an accident this API prevents.

func (*RuleStore) Get

func (s *RuleStore) Get(name string) (Rule, bool)

Get resolves one rule by name.

func (*RuleStore) List

func (s *RuleStore) List() []Rule

List returns all rules sorted by name.

func (*RuleStore) Update

func (s *RuleStore) Update(name, prefix string) (Rule, error)

Update replaces an existing rule's prefix.

type Server

type Server struct {
	Base      string // public origin used in mint responses
	MintToken string // ADMIN bearer token; empty disables the whole API
	Store     *Store
	Rules     *RuleStore
	Tokens    *TokenStore // named member tokens (mint + rules, not token management)
	// EnrollCIDRs are the WireGuard ranges allowed to self-enroll; empty
	// means the enroll endpoint is disabled (fail closed).
	EnrollCIDRs []netip.Prefix
	// TrustedProxies are the ONLY peers whose X-Real-IP is honored; empty
	// means the header is ignored and the TCP peer is the client.
	TrustedProxies []netip.Prefix
	Log            *log.Logger
	// contains filtered or unexported fields
}

Server is the luko.to redirector.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler routes: static namespaces, then 7-char minted codes, then 404.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store persists minted links as append-only JSONL with an in-memory map. Codes are deterministic hashes of the URL, so concurrent mints of the same URL are naturally idempotent.

func OpenStore

func OpenStore(path string) (*Store, error)

OpenStore loads (or creates) the JSONL store at path.

func (*Store) Lookup

func (s *Store) Lookup(code string) (string, bool)

Lookup resolves a minted code.

func (*Store) Mint

func (s *Store) Mint(url string, taken func(string) bool) (code string, created bool, err error)

Mint records url under its deterministic code and reports whether the code was newly created. A 7-char prefix that already maps to a DIFFERENT url (or spells a reserved path segment) extends until it is free or matches — a truncated-hash collision must never redirect to the wrong target. taken reports path segments claimed elsewhere (dynamic rule names); nil means no extra claims.

type TokenIssue

type TokenIssue struct {
	Name  string `json:"name"`
	Token string `json:"token"`
}

TokenIssue mirrors the server's issue response.

type TokenStore

type TokenStore struct {
	// contains filtered or unexported fields
}

TokenStore persists member tokens as one JSON file, rewritten atomically.

func OpenTokenStore

func OpenTokenStore(path string) (*TokenStore, error)

OpenTokenStore loads (or creates) the token store at path.

func (*TokenStore) Identify

func (s *TokenStore) Identify(value string) (string, bool)

Identify resolves a presented token value to its member name.

func (*TokenStore) Issue

func (s *TokenStore) Issue(name string) (string, error)

Issue creates a named token and returns its value — the only time the value ever exists outside the caller's hands.

func (*TokenStore) List

func (s *TokenStore) List() []MemberToken

List returns token names and creation times — never hashes or values.

func (*TokenStore) Revoke

func (s *TokenStore) Revoke(name string) error

Revoke deletes a named token; its holder loses API access immediately.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL