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
- Variables
- func Code(long string) string
- func CodeN(long string, n int) string
- func ExpandDynamic(rule Rule, tail string) string
- func ExpandStatic(path string) (long string, ok bool)
- func LoadToken() string
- func OSC8(label, target string) string
- func ParseEnrollCIDRs(value string) ([]netip.Prefix, error)
- func RuleCachePath(origin string) (string, error)
- func SaveRuleCache(origin string, rules []Rule) error
- func ShortenDynamic(rules []Rule, long string) string
- func ShortenStatic(long string) string
- func StoreToken(token string) error
- func ValidTarget(raw string) error
- func ValidateRuleName(name string) error
- func ValidateRulePrefix(prefix string) error
- type Client
- func (c Client) CreateRule(name, prefix string) (Rule, error)
- func (c Client) DeleteRule(name string) error
- func (c Client) Enroll(name, via string) (TokenIssue, error)
- func (c Client) FetchRules() ([]Rule, error)
- func (c Client) IssueToken(name string) (TokenIssue, error)
- func (c Client) ListTokens() ([]MemberToken, error)
- func (c Client) RevokeToken(name string) error
- func (c Client) Shorten(long string) (Result, error)
- func (c Client) UpdateRule(name, prefix string) (Rule, error)
- type MemberToken
- type Result
- type Rule
- type RuleStore
- type Server
- type Store
- type TokenIssue
- type TokenStore
Constants ¶
const DefaultBase = "https://luko.to"
DefaultBase is the public redirector origin.
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 ¶
var ErrRuleExists = errors.New("rule already exists")
ErrRuleExists reports a create colliding with an existing name.
var ErrRuleNotFound = errors.New("rule not found")
ErrRuleNotFound reports an update/delete of a missing name.
var ErrTokenExists = errors.New("token already exists")
ErrTokenExists reports an issue colliding with an existing name.
var ErrTokenNotFound = errors.New("token not found")
ErrTokenNotFound reports a revoke of a missing name.
Functions ¶
func ExpandDynamic ¶
ExpandDynamic resolves /<name>[/tail] back to the long URL.
func ExpandStatic ¶
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 ParseEnrollCIDRs ¶
ParseEnrollCIDRs parses the comma-separated LUKO_ENROLL_CIDRS value. An empty input returns nil — enrollment disabled, fail closed.
func RuleCachePath ¶
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 ¶
SaveRuleCache writes the origin's rule cache.
func ShortenDynamic ¶
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 ¶
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 ¶
StoreToken writes the mint token into the macOS Keychain.
func ValidTarget ¶
ValidTarget rejects mint inputs that are not absolute http(s) URLs.
func ValidateRuleName ¶
ValidateRuleName rejects names that could collide with fixed routes.
func ValidateRulePrefix ¶
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 ¶
CreateRule adds a dynamic rule server-side and refreshes the cache.
func (Client) DeleteRule ¶
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 ¶
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 ¶
RevokeToken deletes a named member token (admin only).
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 ¶
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 ¶
OpenRuleStore loads (or creates) the rule store at path.
func (*RuleStore) Delete ¶
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.
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.
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 (*Store) Mint ¶
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 ¶
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.