コミット
c228426a58
77
README.md
77
README.md
|
@ -1,8 +1,6 @@
|
|||
htwtxt – hosted twtxt server
|
||||
============================
|
||||
# htwtxt – hosted twtxt server
|
||||
|
||||
Rationale
|
||||
---------
|
||||
## Rationale
|
||||
|
||||
[*twtxt*](https://github.com/buckket/twtxt) is a protocol and client for
|
||||
decentralized microblogging. Users are expected to provide their feeds as plain
|
||||
|
@ -10,37 +8,70 @@ text files with URLs accessible over the Internet. *htwtxt* is a web server to
|
|||
host and grow such text files for users without trivial access to their own web
|
||||
space.
|
||||
|
||||
Clone, build, run
|
||||
-----------------
|
||||
## Online demo
|
||||
|
||||
With htwtxt written in Go, the following instructions expect a Go development
|
||||
environment with [the go tool](https://golang.org/cmd/go/) installed, and the
|
||||
`$GOPATH` set:
|
||||
A demo instance with frequent downtimes can be tested at
|
||||
http://test.plomlompom.com:8000 – don't expect any of its feeds' URLs to be
|
||||
stable. It's just for testing, and data frequently gets deleted.
|
||||
|
||||
## Setup and run
|
||||
|
||||
### Setup Go build environment
|
||||
|
||||
With htwtxt written in Go, the setup instructions below expect a Go development
|
||||
environment – with a somewhat current [go tool](https://golang.org/cmd/go/)
|
||||
installed, and a `$GOPATH` set. If your system does not have such an
|
||||
environment, here's some hints on how to set it up:
|
||||
|
||||
wget https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.5.3.linux-amd64.tar.gz
|
||||
export GOPATH=~/go
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
|
||||
(You might want to add the last two lines to your `.bashrc` or whatever usually
|
||||
initializes your environment variables. And you might want to replace the
|
||||
package pulled by wget by whatever is the newest stable release of Go
|
||||
available.)
|
||||
|
||||
### Clone, build, run
|
||||
|
||||
Once your Go build environment is ready, do this:
|
||||
|
||||
git clone https://github.com/plomlompom/htwtxt $GOPATH/src/htwtxt
|
||||
go get htwtxt
|
||||
mkdir ~/htwtxt
|
||||
cp -R $GOPATH/src/htwtxt/templates ~/htwtxt
|
||||
cd ~/htwtxt
|
||||
$GOPATH/bin/htwtxt
|
||||
|
||||
This will build and start the server, and set up directories and files expected
|
||||
by it below `~/htwtxt` (or any other path you prefer instead of this).
|
||||
This will build and start the server, which will store login and feed data below
|
||||
`~/htwtxt`. An alternate directory may be specified with the `--dir` flag.
|
||||
|
||||
Configuring port number and TLS
|
||||
-------------------------------
|
||||
## Tweaking
|
||||
|
||||
### Configuring port number and TLS
|
||||
|
||||
By default, htwtxt serves unencrypted HTTP over port 8000. But the executable
|
||||
accepts up to three optional arguments to change this:
|
||||
accepts the flag `--port` to provide an alternate port number, and the flags
|
||||
`--cert` and `--key` to provide paths to an SSL certificate and key file to run
|
||||
htwtxt as an HTTPS server.
|
||||
|
||||
$GOPATH/bin/htwtxt [PORT] [CERTIFICATE] [KEY]
|
||||
You might encounter the following issue when trying to set a low port number
|
||||
(such as the HTTP standard 80, or the HTTPS standard 443):
|
||||
|
||||
`PORT` is the port number to serve. `CERTIFICATE` and `KEY` are paths to the
|
||||
certificate and key files needed to run *htwtxt* as a HTTPS server.
|
||||
ListenAndServe: listen tcp :80: bind: permission denied
|
||||
|
||||
Copyright, license
|
||||
------------------
|
||||
This is [a common privilege problem](http://stackoverflow.com/q/413807) and
|
||||
[might be solved](http://stackoverflow.com/a/414258) bis this:
|
||||
|
||||
htwtx (c) 2016 Christian Heller a.k.a. plomlompom
|
||||
sudo setcap 'cap_net_bind_service=+ep' $GOPATH/bin/htwtxt
|
||||
|
||||
License: Affero GPL version 3, see ./LICENSE
|
||||
### Changing HTML templates
|
||||
|
||||
By default, HTML templates are read out of `$GOPATH/src/htwtxt/templates/`. An
|
||||
alternate directory can be given with the flag `--templates` (it should contain
|
||||
template files of the same names as the default ones, however).
|
||||
|
||||
## Copyright, license
|
||||
|
||||
htwtxt (c) 2016 Christian Heller a.k.a. plomlompom
|
||||
|
||||
License: Affero GPL version 3, see `./LICENSE`
|
||||
|
|
92
main.go
92
main.go
|
@ -4,6 +4,7 @@ package main
|
|||
|
||||
import "bufio"
|
||||
import "errors"
|
||||
import "flag"
|
||||
import "github.com/gorilla/mux"
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
import "html/template"
|
||||
|
@ -16,10 +17,11 @@ import "strings"
|
|||
import "time"
|
||||
|
||||
const loginsFile = "logins.txt"
|
||||
const twtsDir = "twtxt"
|
||||
const portDefault = 8000
|
||||
const feedsDir = "feeds"
|
||||
|
||||
var useHttps bool
|
||||
var dataDir string
|
||||
var loginsPath string
|
||||
var feedsPath string
|
||||
var templ *template.Template
|
||||
|
||||
func createFileIfNotExists(path string) {
|
||||
|
@ -66,7 +68,7 @@ func login(w http.ResponseWriter, r *http.Request) (string, error) {
|
|||
name := r.FormValue("name")
|
||||
pw := r.FormValue("password")
|
||||
loginValid := false
|
||||
file, err := os.Open(loginsFile)
|
||||
file, err := os.Open(loginsPath)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
log.Fatal("Can't open file for reading", err)
|
||||
|
@ -107,7 +109,7 @@ func accountLine(w http.ResponseWriter, r *http.Request,
|
|||
return "", errors.New("")
|
||||
}
|
||||
if checkDupl {
|
||||
fileRead, err := os.Open(loginsFile)
|
||||
fileRead, err := os.Open(loginsPath)
|
||||
defer fileRead.Close()
|
||||
if err != nil {
|
||||
log.Fatal("Can't open file for reading", err)
|
||||
|
@ -145,7 +147,7 @@ func signUpHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
appendToFile(loginsFile, newLine+"\n")
|
||||
appendToFile(loginsPath, newLine+"\n")
|
||||
execTemplate(w, "feedset.html", "")
|
||||
}
|
||||
|
||||
|
@ -162,7 +164,7 @@ func accountPostHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
text, err := ioutil.ReadFile(loginsFile)
|
||||
text, err := ioutil.ReadFile(loginsPath)
|
||||
if err != nil {
|
||||
log.Fatal("Can't read file", err)
|
||||
}
|
||||
|
@ -175,24 +177,24 @@ func accountPostHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
text = []byte(strings.Join(lines, "\n"))
|
||||
tmpFile := "tmp_" + loginsFile
|
||||
tmpFile := "tmp_" + loginsPath
|
||||
if err := ioutil.WriteFile(tmpFile, []byte(text), 0600); err != nil {
|
||||
log.Fatal("Trouble writing file", err)
|
||||
}
|
||||
if err := os.Rename(loginsFile, "_"+loginsFile); err != nil {
|
||||
if err := os.Rename(loginsPath, "_"+loginsFile); err != nil {
|
||||
log.Fatal("Trouble moving file", err)
|
||||
}
|
||||
if err := os.Rename(tmpFile, loginsFile); err != nil {
|
||||
if err := os.Rename(tmpFile, loginsPath); err != nil {
|
||||
log.Fatal("Trouble moving file", err)
|
||||
}
|
||||
if err := os.Remove("_" + loginsFile); err != nil {
|
||||
if err := os.Remove("_" + loginsPath); err != nil {
|
||||
log.Fatal("Trouble removing file", err)
|
||||
}
|
||||
execTemplate(w, "feedset.html", "")
|
||||
}
|
||||
|
||||
func listHandler(w http.ResponseWriter, r *http.Request) {
|
||||
file, err := os.Open(loginsFile)
|
||||
file, err := os.Open(loginsPath)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
log.Fatal("Can't open file for reading", err)
|
||||
|
@ -222,11 +224,11 @@ func twtxtPostHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
text := r.FormValue("twt")
|
||||
twtsFile := twtsDir + "/" + name
|
||||
twtsFile := feedsPath + "/" + name
|
||||
createFileIfNotExists(twtsFile)
|
||||
text = strings.Replace(text, "\n", " ", -1)
|
||||
appendToFile(twtsFile, time.Now().Format(time.RFC3339)+"\t"+text+"\n")
|
||||
http.Redirect(w, r, "/"+twtsFile, 302)
|
||||
http.Redirect(w, r, "/"+feedsDir+"/"+name, 302)
|
||||
}
|
||||
|
||||
func twtxtHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -235,7 +237,7 @@ func twtxtHandler(w http.ResponseWriter, r *http.Request) {
|
|||
execTemplate(w, "error.html", "Bad path.")
|
||||
return
|
||||
}
|
||||
path := twtsDir + "/" + name
|
||||
path := feedsPath + "/" + name
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
execTemplate(w, "error.html", "Empty twtxt for user.")
|
||||
return
|
||||
|
@ -244,53 +246,57 @@ func twtxtHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func main() {
|
||||
useHttps = false
|
||||
port := portDefault
|
||||
var err error
|
||||
if len(os.Args) > 1 {
|
||||
port, err = strconv.Atoi(os.Args[1])
|
||||
if err != nil {
|
||||
log.Fatal("Invalid port argument:", err)
|
||||
}
|
||||
portPtr := flag.Int("port", 8000, "port to serve")
|
||||
keyPtr := flag.String("key", "", "SSL key file")
|
||||
certPtr := flag.String("cert", "", "SSL certificate file")
|
||||
templDirPtr := flag.String("templates",
|
||||
os.Getenv("GOPATH")+"/src/htwtxt/templates",
|
||||
"directory where to expect HTML templates")
|
||||
flag.StringVar(&dataDir, "dir", os.Getenv("HOME")+"/htwtxt",
|
||||
"directory to store feeds and login data")
|
||||
flag.Parse()
|
||||
log.Println("Using as templates dir:", *templDirPtr)
|
||||
log.Println("Using as data dir:", dataDir)
|
||||
loginsPath = dataDir + "/" + loginsFile
|
||||
feedsPath = dataDir + "/" + feedsDir
|
||||
if ("" == *keyPtr && "" != *certPtr) ||
|
||||
("" != *keyPtr && "" == *certPtr) {
|
||||
log.Fatal("Expect either both key and certificate or none.")
|
||||
}
|
||||
var certificateFile string
|
||||
var serverKeyFile string
|
||||
if len(os.Args) > 3 {
|
||||
useHttps = true
|
||||
log.Println("using TLS")
|
||||
certificateFile = os.Args[2]
|
||||
serverKeyFile = os.Args[3]
|
||||
if _, err := os.Stat(certificateFile); err != nil {
|
||||
if "" != *keyPtr {
|
||||
log.Println("Using TLS.")
|
||||
if _, err := os.Stat(*certPtr); err != nil {
|
||||
log.Fatal("No certificate file found.")
|
||||
}
|
||||
if _, err := os.Stat(serverKeyFile); err != nil {
|
||||
if _, err := os.Stat(*keyPtr); err != nil {
|
||||
log.Fatal("No server key file found.")
|
||||
}
|
||||
}
|
||||
createFileIfNotExists(loginsFile)
|
||||
createFileIfNotExists(loginsPath)
|
||||
// TODO: Handle err here.
|
||||
_ = os.Mkdir(twtsDir, 0700)
|
||||
templ, err = template.New("main").ParseGlob("./templates/*.html")
|
||||
_ = os.Mkdir(feedsPath, 0700)
|
||||
templ, err = template.New("main").ParseGlob(*templDirPtr + "/*.html")
|
||||
if err != nil {
|
||||
log.Fatal("Can't set up new template: ", err)
|
||||
}
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/", indexHandler)
|
||||
router.HandleFunc("/twtxt", listHandler).Methods("GET")
|
||||
router.HandleFunc("/twtxt/", listHandler)
|
||||
router.HandleFunc("/feeds", listHandler).Methods("GET")
|
||||
router.HandleFunc("/feeds/", listHandler)
|
||||
router.HandleFunc("/account", accountFormHandler).Methods("GET")
|
||||
router.HandleFunc("/account", accountPostHandler).Methods("POST")
|
||||
router.HandleFunc("/signup", signUpFormHandler).Methods("GET")
|
||||
router.HandleFunc("/signup", signUpHandler).Methods("POST")
|
||||
router.HandleFunc("/twtxt", twtxtPostHandler).Methods("POST")
|
||||
router.HandleFunc("/twtxt/{name}", twtxtHandler)
|
||||
router.HandleFunc("/feeds", twtxtPostHandler).Methods("POST")
|
||||
router.HandleFunc("/feeds/{name}", twtxtHandler)
|
||||
http.Handle("/", router)
|
||||
log.Println("serving at port", port)
|
||||
if useHttps {
|
||||
err = http.ListenAndServeTLS(":"+strconv.Itoa(port),
|
||||
certificateFile, serverKeyFile, nil)
|
||||
log.Println("serving at port", *portPtr)
|
||||
if "" != *keyPtr {
|
||||
err = http.ListenAndServeTLS(":"+strconv.Itoa(*portPtr),
|
||||
*certPtr, *keyPtr, nil)
|
||||
} else {
|
||||
err = http.ListenAndServe(":"+strconv.Itoa(port), nil)
|
||||
err = http.ListenAndServe(":"+strconv.Itoa(*portPtr), nil)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal("ListenAndServe: ", err)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<html>
|
||||
{{ template "header" }}
|
||||
<h1>edit account</h1>
|
||||
<form method="POST" action="/account">
|
||||
E-mail: <input type="text" name="mail" /><br />
|
||||
|
@ -9,6 +9,4 @@
|
|||
Old password: <input type="password" name="password" /><br />
|
||||
<input type="submit" value="update" />
|
||||
</form>
|
||||
<hr />
|
||||
<p>This project is licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3</a>, source code <a href="https://github.com/plomlompom/htwtxt">here</a>.</p>
|
||||
</html>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
<html>
|
||||
{{ template "header" }}
|
||||
<h1>error</h1>
|
||||
<p>Something went wrong: {{ .Msg }}</p>
|
||||
<p><a href="/">Try again?</a></p>
|
||||
<hr />
|
||||
<p>This project is licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3</a>, source code <a href="https://github.com/plomlompom/htwtxt">here</a>.</p>
|
||||
</html>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
<html>
|
||||
{{ template "header" }}
|
||||
<h1>feed setup</h1>
|
||||
<p>
|
||||
Feed successfully set.
|
||||
<a href="/">Return to twtxt input form.</a>
|
||||
</p>
|
||||
<hr />
|
||||
<p>This project is licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3</a>, source code <a href="https://github.com/plomlompom/htwtxt">here</a>.</p>
|
||||
</html>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
<html>
|
||||
{{ template "header" }}
|
||||
<h1>publish a twtxt</h1>
|
||||
<form method="POST" action="twtxt">
|
||||
<form method="POST" action="feeds">
|
||||
Message: <input type="text" name="twt" size="140" /><br />
|
||||
Name: <input type="text" name="name" />
|
||||
Password: <input type="password" name="password" /><br />
|
||||
<input type="submit" value="twt" />
|
||||
<input type="submit" value="twtxt" />
|
||||
</form>
|
||||
No feed yet? <a href="/signup">Create one!</a><br />
|
||||
Also, check out <a href="/twtxt">other people's feeds</a> …<br />
|
||||
Also, check out <a href="/feeds">other people's feeds</a> …<br />
|
||||
Or <a href="/account">edit your account settings</a>.
|
||||
<hr />
|
||||
<p>This project is licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3</a>, source code <a href="https://github.com/plomlompom/htwtxt">here</a>.</p>
|
||||
</html>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<html>
|
||||
{{ template "header" }}
|
||||
<h1>list of feeds</h1>
|
||||
<ul>
|
||||
{{ range .Dir }}
|
||||
<li><a href="/twtxt/{{ . }}">{{ . }}</li>
|
||||
<li><a href="/feeds/{{ . }}">{{ . }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
<hr />
|
||||
<p>This project is licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3</a>, source code <a href="https://github.com/plomlompom/htwtxt">here</a>.</p>
|
||||
</html>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{{ define "header" }}
|
||||
<html>
|
||||
<a href="/">hosted twtxt server</a>
|
||||
<hr />
|
||||
{{ end }}
|
||||
{{ define "footer" }}
|
||||
<hr />
|
||||
<p>This project is licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3</a>, source code <a href="https://github.com/plomlompom/htwtxt">here</a>.</p>
|
||||
</html>
|
||||
{{ end }}
|
|
@ -1,4 +1,4 @@
|
|||
<html>
|
||||
{{ template "header" }}
|
||||
<h1>sign up</h1>
|
||||
<form method="POST" action="/signup">
|
||||
Name: <input type="text" name="name" /> (only up to 140 legal chars: A-Z, a-z, 0-1, _)<br />
|
||||
|
@ -7,6 +7,4 @@
|
|||
E-mail: <input type="text" name="mail" /> (optional; stored internally to reach and/or identify you when there's trouble with your feed, you want to reset your password, etc.; in any such communication, communicating from an address provided here is a necessary condition for you to be treated as the owner of your feed)<br />
|
||||
<input type="submit" value="create" />
|
||||
</form>
|
||||
<hr />
|
||||
<p>This project is licensed under the <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3</a>, source code <a href="https://github.com/plomlompom/htwtxt">here</a>.</p>
|
||||
</html>
|
||||
{{ template "footer" }}
|
||||
|
|
読み込み中…
新しいイシューから参照