Initial Commit
This commit is contained in:
commit
3c42d7d878
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM golang:alpine
|
||||
|
||||
EXPOSE 8000/tcp
|
||||
|
||||
ENTRYPOINT ["pastebin"]
|
||||
|
||||
RUN \
|
||||
apk add --update git && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
RUN mkdir -p /go/src/pastebin
|
||||
WORKDIR /go/src/pastebin
|
||||
|
||||
COPY . /go/src/pastebin
|
||||
|
||||
RUN go get -v -d
|
||||
RUN go install -v
|
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (C) 2017 James Mills
|
||||
|
||||
httpfs is covered by the MIT license::
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
13
Makefile
Normal file
13
Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
.PHONY: dev build clean
|
||||
|
||||
all: dev
|
||||
|
||||
dev: build
|
||||
./pastebin -bind 127.0.0.1:8000
|
||||
|
||||
build: clean
|
||||
go get ./...
|
||||
go build -o ./pastebin .
|
||||
|
||||
clean:
|
||||
rm -rf bin pastebin
|
49
README.md
Normal file
49
README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# pastebin
|
||||
[![Build Status](https://travis-ci.org/prologic/pastebin.svg)](https://travis-ci.org/prologic/pastebin)
|
||||
[![GoDoc](https://godoc.org/github.com/prologic/pastebin?status.svg)](https://godoc.org/github.com/prologic/pastebin)
|
||||
[![Wiki](https://img.shields.io/badge/docs-wiki-blue.svg)](https://github.com/prologic/pastebin/wiki)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/prologic/pastebin)](https://goreportcard.com/report/github.com/prologic/pastebin)
|
||||
[![Coverage](https://coveralls.io/repos/prologic/pastebin/badge.svg)](https://coveralls.io/r/prologic/pastebin)
|
||||
|
||||
pastebin is a web app that allows you to create smart bookmarks, commands and aliases by pointing your web browser's default search engine at a running instance. Similar to bunny1 or yubnub.
|
||||
|
||||
## Installation
|
||||
|
||||
### Source
|
||||
|
||||
```#!bash
|
||||
$ go install github.com/prologic/pastebin/...
|
||||
```
|
||||
|
||||
### OS X Homebrew
|
||||
|
||||
There is a formula provided that you can tap and install from
|
||||
[prologic/homebrew-pastebin](https://github.com/prologic/homebrew-pastebin):
|
||||
|
||||
```#!bash
|
||||
$ brew tap prologic/pastebin
|
||||
$ brew install pastebin
|
||||
```
|
||||
|
||||
**NB:** This installs the latest released binary; so if you want a more
|
||||
recent unreleased version from master you'll have to clone the repository
|
||||
and build yourself.
|
||||
|
||||
pastebin is still early days so contributions, ideas and expertise are
|
||||
much appreciated and highly welcome!
|
||||
|
||||
## Usage
|
||||
|
||||
Run pastebin:
|
||||
|
||||
```#!bash
|
||||
$ pastebin -bind 127.0.0.1:8000
|
||||
```
|
||||
|
||||
Set your browser's default pastebin engine to http://localhost:8000/?q=%s
|
||||
|
||||
Then type `help` to view the main help page, `g foo bar` to perform a [Google](https://google.com) search for "foo bar" or `list` to list all available commands.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
7
config.go
Normal file
7
config.go
Normal file
@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
// Config ...
|
||||
type Config struct {
|
||||
expiry int
|
||||
fqdn string
|
||||
}
|
23
config_test.go
Normal file
23
config_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestZeroConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cfg := Config{}
|
||||
assert.Equal(cfg.expiry, 0)
|
||||
assert.Equal(cfg.fqdn, "")
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cfg := Config{expiry: 1800, fqdn: "https://localhost"}
|
||||
assert.Equal(cfg.expiry, 1800)
|
||||
assert.Equal(cfg.fqdn, "https://localhost")
|
||||
}
|
43
main.go
Normal file
43
main.go
Normal file
@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/namsral/flag"
|
||||
)
|
||||
|
||||
var (
|
||||
db *bolt.DB
|
||||
cfg Config
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
config string
|
||||
dbpath string
|
||||
bind string
|
||||
fqdn string
|
||||
expiry int
|
||||
)
|
||||
|
||||
flag.StringVar(&config, "config", "", "config file")
|
||||
flag.StringVar(&dbpath, "dbpth", "urls.db", "Database path")
|
||||
flag.StringVar(&bind, "bind", "0.0.0.0:8000", "[int]:<port> to bind to")
|
||||
flag.StringVar(&fqdn, "fqdn", "localhost", "FQDN for public access")
|
||||
flag.IntVar(&expiry, "expiery", 5, "expiry time (mins) for passte")
|
||||
flag.Parse()
|
||||
|
||||
// TODO: Abstract the Config and Handlers better
|
||||
cfg.fqdn = fqdn
|
||||
cfg.expiry = expiry
|
||||
|
||||
var err error
|
||||
db, err = bolt.Open(dbpath, 0600, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
NewServer(bind, cfg).ListenAndServe()
|
||||
}
|
43
scripts/release.sh
Executable file
43
scripts/release.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo -n "Version to tag: "
|
||||
read TAG
|
||||
|
||||
echo -n "Name of release: "
|
||||
read NAME
|
||||
|
||||
echo -n "Desc of release: "
|
||||
read DESC
|
||||
|
||||
git tag ${TAG}
|
||||
git push --tags
|
||||
|
||||
if [ ! -d ./bin ]; then
|
||||
mkdir bin
|
||||
else
|
||||
rm -rf ./bin/*
|
||||
fi
|
||||
|
||||
echo -n "Building binaries ... "
|
||||
|
||||
rice embed-go
|
||||
|
||||
GOOS=linux GOARCH=amd64 go build -o ./bin/pastebin-Linux-x86_64 .
|
||||
GOOS=linux GOARCH=arm64 go build -o ./bin/pastebin-Linux-x86_64 .
|
||||
GOOS=darwin GOARCH=amd64 go build -o ./bin/pastebin-Darwin-x86_64 .
|
||||
GOOS=windows GOARCH=amd64 go build -o ./bin/pastebin-Windows-x86_64.exe .
|
||||
|
||||
echo "DONE"
|
||||
|
||||
echo -n "Uploading binaries ... "
|
||||
|
||||
github-release release \
|
||||
-u prologic -p -r pastebin \
|
||||
-t ${TAG} -n "${NAME}" -d "${DESC}"
|
||||
|
||||
for file in bin/*; do
|
||||
name="$(echo $file | sed -e 's|bin/||g')"
|
||||
github-release upload -u prologic -r pastebin -t ${TAG} -n $name -f $file
|
||||
done
|
||||
|
||||
echo "DONE"
|
109
server.go
Normal file
109
server.go
Normal file
@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/GeertJohan/go.rice"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/renstrom/shortuuid"
|
||||
"go.iondynamics.net/templice"
|
||||
)
|
||||
|
||||
// Server ...
|
||||
type Server struct {
|
||||
bind string
|
||||
config Config
|
||||
store *cache.Cache
|
||||
templates *templice.Template
|
||||
router *httprouter.Router
|
||||
}
|
||||
|
||||
func (s *Server) render(w http.ResponseWriter, tmpl string, data interface{}) {
|
||||
err := s.templates.ExecuteTemplate(w, tmpl+".html", data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// IndexHandler ...
|
||||
func (s *Server) IndexHandler() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
s.render(w, "index", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// PasteHandler ...
|
||||
func (s *Server) PasteHandler() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
uuid := shortuuid.NewWithNamespace(s.config.fqdn)
|
||||
blob := r.Form.Get("blob")
|
||||
s.store.Set(uuid, blob, cache.DefaultExpiration)
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("./%s", uuid))
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
}
|
||||
http.Redirect(w, r, r.URL.ResolveReference(u).String(), http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
// ViewHandler ...
|
||||
func (s *Server) ViewHandler() httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
uuid := p.ByName("uuid")
|
||||
if uuid == "" {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
blob, ok := s.store.Get(uuid)
|
||||
if !ok {
|
||||
http.Error(w, "Not Found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
s.render(w, "view", struct{ Blob string }{Blob: blob.(string)})
|
||||
}
|
||||
}
|
||||
|
||||
// ListenAndServe ...
|
||||
func (s *Server) ListenAndServe() {
|
||||
log.Fatal(http.ListenAndServe(s.bind, s.router))
|
||||
}
|
||||
|
||||
func (s *Server) initRoutes() {
|
||||
s.router.GET("/", s.IndexHandler())
|
||||
s.router.POST("/", s.PasteHandler())
|
||||
s.router.GET("/:uuid", s.ViewHandler())
|
||||
}
|
||||
|
||||
// NewServer ...
|
||||
func NewServer(bind string, config Config) *Server {
|
||||
server := &Server{
|
||||
bind: bind,
|
||||
config: config,
|
||||
router: httprouter.New(),
|
||||
store: cache.New(5*time.Minute, 10*time.Minute),
|
||||
templates: templice.New(rice.MustFindBox("templates")),
|
||||
}
|
||||
|
||||
err := server.templates.Load()
|
||||
if err != nil {
|
||||
log.Panicf("error loading templates: %s", err)
|
||||
}
|
||||
|
||||
server.initRoutes()
|
||||
|
||||
return server
|
||||
}
|
14
templates/index.html
Normal file
14
templates/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>pastebin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Enter your text here:</p>
|
||||
<form method="POST" action="">
|
||||
<textarea name="blob" cols=80 rows=24></textarea>
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
10
templates/view.html
Normal file
10
templates/view.html
Normal file
@ -0,0 +1,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>pastebin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre>{{.Blob}}</pre>
|
||||
</body>
|
||||
|
||||
</html>
|
19
utils.go
Normal file
19
utils.go
Normal file
@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
)
|
||||
|
||||
// RandomString ...
|
||||
func RandomString(length int) string {
|
||||
bytes := make([]byte, length)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphabet[b%byte(len(alphabet))]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
Loading…
Reference in New Issue
Block a user