README ¶
gobin
gobin is a simple lightweight haste-server alternative written in Go, HTML, JS and CSS. It is aimed to be easy to use and deploy. You can find an instance running at xgob.in.
Table of Contents
Features
- Easy to deploy and use
- Built-in rate-limiting
- Create, update and delete documents
- Document update/delete webhooks
- Syntax highlighting
- Social Media PNG previews
- Document expiration
- Supports PostgreSQL or SQLite
- One binary and config file
- Docker image available
Metrics (to be implemented)- base16 & chroma custom themes
Installation
Docker
The easiest way to deploy gobin is using docker with Docker Compose. You can find the docker image on Packages.
Docker Compose
Create a new docker-compose.yml
file with the following content:
[!Note] You should change the password in the
docker-compose.yml
andgobin.json
file.
version: "3.8"
services:
gobin:
image: ghcr.io/topi314/gobin:latest
container_name: gobin
restart: unless-stopped
volumes:
- ./gobin.json:/var/lib/gobin/gobin.json
# use this for sqlite
- ./gobin.db:/var/lib/gobin/gobin.db
ports:
- 80:80
# or use this for postgres
postgres:
image: postgres:latest
container_name: postgres
restart: unless-stopped
volumes:
- ./data:/var/lib/postgresql/data
environment:
POSTGRES_DB: gobin
POSTGRES_USER: gobin
POSTGRES_PASSWORD: password
For gobin.json
/environment variables and database schema see Configuration.
docker-compose up -d
Manual
Requirements
- Go 1.20 or higher
- PostgreSQL 13 or higher
Build
git clone https://github.com/topi314/gobin.git
cd gobin
go build -o gobin
or
go install github.com/topi314/gobin@latest
Run
gobin --config=gobin.json
Configuration
The database schema is automatically created when you start gobin and there is no documents
table in the database.
Create a new gobin.json
file with the following content:
[!Note] Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
{
"log": {
// level can be -4 (debug), 0 (info), 4 (warn), 8 (error)
"level": 0,
// log format, either "json" or "text"
"format": "text",
// whether to add the source file and line to the log output
"add_source": false,
// whether to add color to the log output (only for text format)
"no_color": false
},
// enable or disable debug profiler endpoint
"debug": false,
// enable or disable hot reload of templates and assets
"dev_mode": false,
"listen_addr": "0.0.0.0:80",
// secret for jwt tokens, replace with a long random string
"jwt_secret": "...",
"database": {
// either "postgres" or "sqlite"
"type": "postgres",
"debug": false,
"expire_after": "168h",
"cleanup_interval": "10m",
// path to sqlite database
// if you run gobin with docker make sure to set it to "/var/lib/gobin/gobin.db"
"path": "gobin.db",
// postgres connection settings
"host": "localhost",
"port": 5432,
"username": "gobin",
"password": "password",
"database": "gobin",
"ssl_mode": "disable"
},
// max document size in characters
"max_document_size": 0,
// omit or set values to 0 or "0" to disable rate limit
"rate_limit": {
// number of requests which can be done in the duration
"requests": 10,
// the duration of the requests
"duration": "1m",
// a list of ip addresses which are exempt from rate limiting
"whitelist": ["127.0.0.1"],
// a list of ip addresses which are blocked from rate limited endpoints
"blacklist": ["123.456.789.0"]
},
// settings for social media previews, omit to disable
"preview": {
// path to inkscape binary https://inkscape.org/
"inkscape_path": "/usr/bin/inkscape",
// how many lines should be shown in the preview
"max_lines": 10,
// how high the resolution of the preview should be, 96 is the default
"dpi": 96,
// how many previews should be maximally cached
"cache_size": 1024,
// how long should previews be cached
"cache_duration": "1h"
},
// open telemetry settings, omit to disable
"otel": {
// the instance id of the server
"instance_id": "1",
// otel trace settings, omit to disable
"trace": {
// the address of the tempo instance
"endpoint": "tempo:4318",
// whether to use an insecure connection
"insecure": true
},
// otel metrics settings, omit to disable
"metrics": {
// the address where the metrics should be exposed
"listen_addr": ":9100"
}
},
// settings for webhooks, omit to disable
"webhook": {
// webhook reqauest timeout
"timeout": "10s",
// max number of tries to send a webhook
"max_tries": 3,
// how long to wait before retrying a webhook
"backoff": "1s",
// how much the backoff should be increased after each retry
"backoff_factor": 2,
// max backoff time
"max_backoff": "5m"
},
// load custom chroma xml or base16 yaml themes from this directory, omit to disable
"custom_styles": "custom_styles",
"default_style": "snazzy"
}
Alternatively you can use environment variables to configure gobin. The environment variables are prefixed with GOBIN_
and are in uppercase. For example GOBIN_DATABASE_TYPE
or GOBIN_RATE_LIMIT_REQUESTS
.
Here is a list of all environment variables
GOBIN_LOG_LEVEL=info
GOBIN_LOG_FORMAT=text
GOBIN_LOG_ADD_SOURCE=false
GOBIN_DEBUG=false
GOBIN_DEV_MODE=false
GOBIN_LISTEN_ADDR=0.0.0.0:80
GOBIN_JWT_SECRET=...
GOBIN_DATABASE_TYPE=postgres
GOBIN_DATABASE_DEBUG=false
GOBIN_DATABASE_EXPIRE_AFTER=168h
GOBIN_DATABASE_CLEANUP_INTERVAL=10m
GOBIN_DATABASE_PATH=gobin.db
GOBIN_DATABASE_HOST=localhost
GOBIN_DATABASE_PORT=5432
GOBIN_DATABASE_USERNAME=gobin
GOBIN_DATABASE_PASSWORD=password
GOBIN_DATABASE_DATABASE=gobin
GOBIN_DATABASE_SSL_MODE=disable
GOBIN_MAX_DOCUMENT_SIZE=0
GOBIN_RATE_LIMIT_REQUESTS=10
GOBIN_RATE_LIMIT_DURATION=1m
GOBIN_PREVIEW_INKSCAPE_PATH=/usr/bin/inkscape
GOBIN_PREVIEW_MAX_LINES=10
GOBIN_PREVIEW_DPI=96
GOBIN_PREVIEW_CACHE_SIZE=1024
GOBIN_PREVIEW_CACHE_TTL=1h
GOBIN_WEBHOOK_TIMEOUT=10s
GOBIN_WEBHOOK_MAX_TRIES=3
GOBIN_WEBHOOK_BACKOFF=1s
GOBIN_WEBHOOK_BACKOFF_FACTOR=2
GOBIN_WEBHOOK_MAX_BACKOFF=5m
GOBIN_CUSTOM_STYLES=custom_styles
GOBIN_DEFAULT_STYLE=snazzy
Custom Themes
You can add your own themes to gobin by adding the custom_styles
directory to the config file and adding your themes to it.
The themes have to be in the following format:
scheme: "name"
author: "author"
theme: "dark" # or "light"
base00: "282a36"
base01: "34353e"
base02: "43454f"
base03: "78787e"
base04: "a5a5a9"
base05: "e2e4e5"
base06: "eff0eb"
base07: "f1f1f0"
base08: "ff5c57"
base09: "ff9f43"
base0A: "f3f99d"
base0B: "5af78e"
base0C: "9aedfe"
base0D: "57c7ff"
base0E: "ff6ac1"
base0F: "b2643c"
See base16 for more information.
Or you can use the chroma XML themes.
Rate Limits
Following endpoints are rate-limited:
POST
/documents
PATCH
/documents/{key}
DELETE
/documents/{key}
PATCH
and DELETE
share the same bucket while POST
has its own bucket
API
Fields marked with ?
are optional and types marked with ?
are nullable.
Formatter Enum
Document formatting is done using chroma. The following formatters are available:
Value | Description |
---|---|
terminal8 | 8-bit terminal colors |
terminal16 | 16-bit terminal colors |
terminal256 | 256-bit terminal colors |
terminal16m | true terminal colors |
html | HTML |
html-standalone | Standalone HTML |
svg | SVG |
Language Enum
The following languages are available:
Click to expand
Prefix | Language |
---|---|
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk |
B | Ballerina, Bash, Batchfile, BibTeX, Bicep, BlitzBasic, BNF, Brainfuck, BQN |
C | C, C#, C++, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Chapel, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython |
D | D, Dart, Diff, Django/Jinja, Docker, DTD, Dylan |
E | EBNF, Elixir, Elm, EmacsLisp, Erlang |
F | Factor, Fish, Forth, Fortran, FSharp |
G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, Gherkin, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groff, Groovy |
H | Handlebars, Haskell, Haxe, HCL, Hexdump, HLB, HLSL, HTML, HTTP, Hy |
I | Idris, Igor, INI, Io |
J | J, Java, JavaScript, JSON, Julia, Jungle |
K | Kotlin |
L | Lighttpd configuration file, LLVM, Lua |
M | Makefile, Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL |
N | NASM, Newspeak, Nginx configuration file, Nim, Nix |
O | Objective-C, OCaml, Octave, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode |
P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, PromQL, Properties, Protocol Buffer, PSL, Puppet, Python 2, Python |
Q | QBasic |
R | R, Racket, Ragel, Raku, react, ReasonML, reg, reStructuredText, Rexx, Ruby, Rust |
S | SAS, Sass, Scala, Scheme, Scilab, SCSS, Sed, Smalltalk, Smarty, Snobol, Solidity, SPARQL, SQL, SquidConf, Standard ML, stas, Stylus, Svelte, Swift, SYSTEMD, systemverilog |
T | TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData |
V | VB.net, verilog, VHDL, VHS, VimL, vue |
W | WDTE |
X | XML, Xorg |
Y | YAML, YANG |
Z | Zig |
Create a document
To create a paste you have to send a POST
request to /documents
with the content
as plain/text
body.
Query Parameter | Type | Description |
---|---|---|
language? | language | The language of the document. |
formatter? | formatter | With which formatter to render the document. |
package main
func main() {
println("Hello World!")
}
A successful request will return a 200 OK
response with a JSON body containing the document key and token to update the document.
{
"key": "hocwr6i6",
"version": 1,
"data": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
"formatted": "...", // only if formatter is set
"css": "...", // only if formatter=html
"language": "go",
"token": "kiczgez33j7qkvqdg9f7ksrd8jk88wba"
}
Get a document
To get a document you have to send a GET
request to /documents/{key}
.
Query Parameter | Type | Description |
---|---|---|
language? | language | In which language the document should be rendered. |
formatter? | formatter | With which formatter to render the document |
The response will be a 200 OK
with the document content as application/json
body.
{
"key": "hocwr6i6",
"version": 1,
"data": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
"formatted": "...", // only if formatter is set
"css": "...", // only if formatter=html
"language": "go"
}
Get a documents versions
To get a documents versions you have to send a GET
request to /documents/{key}/versions
.
Query Parameter | Type | Description |
---|---|---|
withData? | bool | If the data should be included in the response. |
The response will be a 200 OK
with the document content as application/json
body.
[
{
"version": 1,
"data": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
"language": "go"
},
{
"version": 2,
"data": "package main\n\nfunc main() {\n println(\"Hello World2!\")\n}",
"language": "go"
}
]
Get a document version
To get a document version you have to send a GET
request to /documents/{key}/versions/{version}
.
Query Parameter | Type | Description |
---|---|---|
language? | language | In which language the document should be rendered. |
formatter? | formatter | With which formatter to render the document. |
The response will be a 200 OK
with the document content as application/json
body.
{
"key": "hocwr6i6",
"version": 1,
"data": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
"formatted": "...", // only if formatter is set
"css": "...", // only if formatter=html
"language": "go"
}
Update a document
To update a paste you have to send a PATCH
request to /documents/{key}
with the content
as plain/text
body and the token
as Authorization
header.
Query Parameter | Type | Description |
---|---|---|
language? | language | The language of the document. |
formatter? | formatter | With which formatter to render the document. |
Authorization: kiczgez33j7qkvqdg9f7ksrd8jk88wba
package main
func main() {
println("Hello World Updated!")
}
A successful request will return a 200 OK
response with a JSON body containing the document key and token to update the document.
[!Note] The update token will not change after updating the document. You can use the same token to update the document again.
{
"key": "hocwr6i6",
"version": 2,
"data": "package main\n\nfunc main() {\n println(\"Hello World Updated!\")\n}", // only if formatter is set
"formatted": "...", // only if formatter is set
"css": "...", // only if formatter=html
"language": "go"
}
Share a document
To share a document you have to send a POST
request to /documents/{key}/share
with the token
as Authorization
header and the following JSON body:
{
"permissions": [
"write",
"delete",
"share"
]
}
A successful request will return a 200 OK
response with a JSON body containing the share token.
You can append the token to URLs like this: https://xgob.in/{key}?token={token}
to make the frontend auto import the token for editing/deleting/sharing the document.
{
"token": "kiczgez33j7qkvqdg9f7ksrd8jk88wba"
}
Delete a document
To delete a document you have to send a DELETE
request to /documents/{key}
with the token
as Authorization
header.
A successful request will return a 204 No Content
response with an empty body.
Delete a document version
To delete a document version you have to send a DELETE
request to /documents/{key}/versions/{version}
with the token
as Authorization
header.
A successful request will return a 204 No Content
response with an empty body.
Document webhooks
You can listen for document changes using webhooks. The webhook will send a POST
request to the specified url with the following JSON body:
{
// the id of the webhook
"webhook_id": "hocwr6i6",
// the event which triggered the webhook (update or delete)
"event": "update",
// when the event was created
"created_at": "2021-08-01T12:00:00Z",
// the updated or deleted document
"document": {
// the key of the document
"key": "hocwr6i6",
// the version of the document
"version": 2,
// the language of the document
"language": "go",
// the content of the document
"data": "package main\n\nfunc main() {\n println(\"Hello World Updated!\")\n}"
}
}
Gobin will include the webhook secret in the Authorization
header in the following format: Secret {secret}
.
When sending an event to a webhook fails gobin will retry it up to x times with an exponential backoff. The retry settings can be configured in the config file. When an event fails to be sent after x retries, the webhook will be dropped.
[!Important] Authorizing for the following webhook endpoints is done using the
Authorization
header in the following format:Secret {secret}
.
Create a document webhook
To create a webhook you have to send a POST
request to /documents/{key}/webhooks
with the following JSON body:
{
// the url to send a request to
"url": "https://example.com/webhook",
// the secret to include in the request
"secret": "secret",
// the events you want to receive
"events": [
// update event is sent when a document is updated. This includes content and language changes
"update",
// delete event is sent when a document is deleted
"delete"
]
}
A successful request will return a 200 OK
response with a JSON body containing the webhook.
{
// the id of the webhook
"id": 1,
// the url to send a request to
"url": "https://example.com/webhook",
// the secret to include in the request
"secret": "secret",
// the events you want to receive
"events": [
// update event is sent when a document is updated. This includes content and language changes
"update",
// delete event is sent when a document is deleted
"delete"
]
}
Get a document webhook
To get a webhook you have to send a GET
request to /documents/{key}/webhooks/{id}
with the Authorization
header.
A successful request will return a 200 OK
response with a JSON body containing the webhook.
{
// the id of the webhook
"id": 1,
// the url to send a request to
"url": "https://example.com/webhook",
// the secret to include in the request
"secret": "secret",
// the events you want to receive
"events": [
// update event is sent when a document is updated. This includes content and language changes
"update",
// delete event is sent when a document is deleted
"delete"
]
}
Update a document webhook
To update a webhook you have to send a PATCH
request to /documents/{key}/webhooks/{id}
with the Authorization
header and the following JSON body:
[!Note] All fields are optional, but at least one field is required.
{
// the url to send a request to
"url": "https://example.com/webhook",
// the secret to include in the request
"secret": "secret",
// the events you want to receive
"events": [
// update event is sent when a document is updated. This includes content and language changes
"update",
// delete event is sent when a document is deleted
"delete"
]
}
A successful request will return a 200 OK
response with a JSON body containing the webhook.
{
// the id of the webhook
"id": 1,
// the url to send a request to
"url": "https://example.com/webhook",
// the secret to include in the request
"secret": "secret",
// the events you want to receive
"events": [
// update event is sent when a document is updated. This includes content and language changes
"update",
// delete event is sent when a document is deleted
"delete"
]
}
Delete a document webhook
To delete a webhook you have to send a DELETE
request to /documents/{key}/webhooks/{id}
with the Authorization
header.
A successful request will return a 204 No Content
response with an empty body.
Other endpoints
GET
/HEAD
/{key}/preview
- Get the preview of a document, query parameters are the same as forGET /documents/{key}
GET
/HEAD
/{key}/{version}/preview
- Get the preview of a document version, query parameters are the same as forGET /documents/{key}/versions/{version}
GET
/HEAD
/documents/{key}/preview
- Get the preview of a document, query parameters are the same as forGET /documents/{key}
GET
/HEAD
/documents/{key}/versions/{version}/preview
- Get the preview of a document version, query parameters are the same as forGET /documents/{key}/versions/{version}
GET
/HEAD
/raw/{key}
- Get the raw content of a document, query parameters are the same as forGET /documents/{key}
GET
/HEAD
/raw/{key}/{version}
- Get the raw content of a document version, query parameters are the same as forGET /documents/{key}/versions/{version}
GET
/ping
- Get the status of the serverGET
/debug
- Proof debug endpoint (only available in debug mode)GET
/version
- Get the version of the server
Errors
In case of an error gobin will return the following JSON body with the corresponding HTTP status code:
{
"message": "document not found", // error message
"status": 404, // HTTP status code
"path": "/documents/7df3vw", // request path
"request_id": "fbe0a365387f/gVAMGuraLW-003490" // request id
}
License
gobin is licensed under the Apache License 2.0.
Contributing
Contributions are always welcome! Just open a pull request or discussion and I will take a look at it.
Credits
- @Damon for helping me.
Contact
Documentation ¶
There is no documentation for this package.