{"id":38179425,"url":"https://github.com/earentir/linknife","last_synced_at":"2026-01-16T23:45:17.420Z","repository":{"id":308966097,"uuid":"1034735439","full_name":"earentir/linknife","owner":"earentir","description":"cli URL shortener","archived":false,"fork":false,"pushed_at":"2025-08-08T23:00:09.000Z","size":25,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-09T00:27:29.890Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/earentir.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-08T22:29:08.000Z","updated_at":"2025-08-08T22:59:06.000Z","dependencies_parsed_at":"2025-08-09T00:27:32.999Z","dependency_job_id":"eb8cec91-5c4b-46b9-b08d-4261c8f2043d","html_url":"https://github.com/earentir/linknife","commit_stats":null,"previous_names":["earentir/linknife"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/earentir/linknife","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earentir%2Flinknife","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earentir%2Flinknife/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earentir%2Flinknife/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earentir%2Flinknife/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/earentir","download_url":"https://codeload.github.com/earentir/linknife/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earentir%2Flinknife/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28488072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T22:54:02.790Z","status":"ssl_error","status_checked_at":"2026-01-16T22:50:10.344Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-16T23:45:16.594Z","updated_at":"2026-01-16T23:45:17.398Z","avatar_url":"https://github.com/earentir.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"linknife\n\nA tiny self-hosted URL-shortener written in Go.\n\t•\tFlat layout — every package lives one directory deep (no cmd/, no internal/).\n\t•\tBoltDB storage (single file, zero external deps).\n\t•\tAdmin + user API keys with per-permission flags.\n\t•\t10-second landing/redirect page with cancel tracking.\n\t•\tCLI utilities to manage keys, run the server and dump stats.\n\n⸻\n\nQuick start\n\n# clone \u0026 fetch deps\n$ git clone https://github.com/yourname/linknife \u0026\u0026 cd linknife \u0026\u0026 go mod tidy\n\n# 1. create minimal config \u0026 empty users file\n$ echo '{ \"port\":8080, \"db_path\":\"./data/linknife.db\" }' \u003e config.json\n$ echo '{}' \u003e users.json\n\n# 2. generate an admin key and inject it into config.json\n$ go run . apikey admin --config config.json\n\n# 3. (optional) generate a user key with all perms\n$ go run . apikey user generate --users users.json --create --update --delete\n\n# 4. start the server\n$ go run . serve --config config.json\n\nServer is now listening at http://localhost:8080.\n\n⸻\n\nFile layout\n\n.\n```bash\n├── main.go         # CLI entry-point (Cobra)\n├── server/         # HTTP handlers + Bolt logic\n├── apikeygen/      # key generation \u0026 JSON mutators\n├── jsonutil/       # tiny generic JSON load/save\n├── go.mod\n├── go.sum\n└── README.md\n```\n\n⸻\n\nConfig files\n\nconfig.json\n```json\n{\n  \"admin_api_key\": \"\",\n  \"db_path\": \"linknife.db\",\n  \"log_path\": \"linknife.log\",\n  \"port\": 8080,\n  \"tls_cert\": \"cert.pem\",\n  \"tls_key\": \"key.pem\",\n  \"tls_port\": 0,\n  \"users_file\": \"users.json\"\n}\n```\n\nusers.json\n```json\n{\n  \"\u003c128-hex-key\u003e\": {\n    \"api_key\": \"\u003csame-key\u003e\",\n    \"perm\": {\n      \"create\": true,\n      \"change\": true,\n      \"delete\": false\n    }\n  }\n}\n```\n\n⸻\n\nCLI reference\n\n### Command\tPurpose\n\nlinknife serve --config config.json\tstart HTTP/HTTPS server\nlinknife apikey admin --config config.json\tgenerate one admin key\nlinknife apikey user generate --users users.json [--create] [--update] [--delete]\tnew user key\nlinknife apikey user edit \u003ckey\u003e --users users.json [--create] [--update] [--delete]\tmodify perms\nlinknife apikey user delete \u003ckey\u003e --users users.json\tremove a key\nlinknife apikey user list --users users.json\tlist all user keys\nlinknife stats --config config.json\tglobal link/visit stats\n\nFlags omitted on generate/edit default to false.\n\n⸻\n\nHTTP API\n\nAll JSON is UTF-8; auth via header X-API-Key: … or query ?api_key=.\n\n1  Shorten\n\ncurl -H \"X-API-Key: $USER_KEY\" -d '{\"url\":\"https://example.com\"}' http://localhost:8080/api/shorten\n\n→ { \"short_url\": \"http://…/1a2b3c4d\", \"code\": \"1a2b3c4d\" }\n\n2  Update\n\ncurl -H \"X-API-Key: $USER_KEY\" -d '{\"url\":\"https://new.example.com\"}' http://localhost:8080/api/update/1a2b3c4d\n\n3  Delete code\n\ncurl -X POST -H \"X-API-Key: $USER_KEY\" http://localhost:8080/api/remove/1a2b3c4d\n\n4  Per-link stats\n\ncurl \"http://localhost:8080/1a2b3c4d/stats?api_key=$ADMIN_KEY\"\n\n5  Cancel redirect (auto-triggered by landing page)\n\ncurl -X POST http://localhost:8080/api/cancel/1a2b3c4d\n\n6  Redirect page (browser visit)\n\nGET /1a2b3c4d → 10-second splash then 302.\n\n⸻\n\nBuild static binary\n\ngo build -trimpath -ldflags=\"-s -w\" -o linknife .\n\n\n⸻\n\nBackup\n\t•\tBoltDB (db_path) is a single file — stop server or copy atomically.\n\t•\tusers.json \u0026 config.json are plain text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fearentir%2Flinknife","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fearentir%2Flinknife","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fearentir%2Flinknife/lists"}