Merge pull request #1 from plomlompom/master

Merge from plom
このコミットが含まれているのは:
Kai Kubasta 2016-02-10 21:55:05 +01:00
コミット c228426a58
9個のファイルの変更129行の追加96行の削除

ファイルの表示

@ -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
ファイルの表示

@ -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" }}

10
templates/partials.html ノーマルファイル
ファイルの表示

@ -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" }}