コミットを比較
10 コミット
86c4cb9889
...
ff5d14874f
作成者 | SHA1 | 日付 |
---|---|---|
Izuru Yakumo | ff5d14874f | |
Izuru Yakumo | 86816b9705 | |
Izuru Yakumo | e9e364611a | |
Christian Heller | 0c06d89af1 | |
Christian Heller | 05ff0d4e56 | |
Christian Heller | 67415705d9 | |
Christian Heller | 7f9ff126c2 | |
Christian Heller | 116ad4ca0f | |
Christian Heller | 759647e800 | |
buckket | 042a88e3f5 |
|
@ -0,0 +1,31 @@
|
|||
GO ?= go
|
||||
RM ?= rm
|
||||
SCDOC ?= scdoc
|
||||
GOFLAGS ?= -v -ldflags "-w -X `go list`.Version=$(VERSION) -X `go list`.Commit=$(COMMIT) -X `go list`.Build=$(BUILD)" -tags "static_build"
|
||||
PREFIX ?= /usr/local
|
||||
BINDIR ?= bin
|
||||
MANDIR ?= share/man
|
||||
MKDIR ?= mkdir
|
||||
CP ?= cp
|
||||
|
||||
VERSION = `git describe --abbrev=0 --tags 2>/dev/null || echo "$VERSION"`
|
||||
COMMIT = `git rev-parse --short HEAD || echo "$COMMIT"`
|
||||
BRANCH = `git rev-parse --abbrev-ref HEAD`
|
||||
BUILD = `git show -s --pretty=format:%cI`
|
||||
|
||||
GOARCH ?= amd64
|
||||
GOOS ?= linux
|
||||
|
||||
all: akyuu
|
||||
|
||||
akyuu:
|
||||
$(GO) build $(GOFLAGS)
|
||||
clean:
|
||||
$(RM) -f akyuu akyuuctl akyuu-znc-import doc/akyuu.1
|
||||
install:
|
||||
$(MKDIR) -p $(DESTDIR)$(PREFIX)/$(BINDIR)
|
||||
$(MKDIR) -p $(DESTDIR)/var/lib/akyuu
|
||||
$(CP) -f akyuu $(DESTDIR)$(PREFIX)/$(BINDIR)
|
||||
$(CP) -R templates $(DESTDIR)/var/lib/akyuu
|
||||
$(MKDIR) -p $(DESTDIR)/var/lib/akyuu/data
|
||||
.PHONY: akyuu clean install
|
34
README.md
34
README.md
|
@ -1,4 +1,4 @@
|
|||
# htwtxt – hosted twtxt server
|
||||
# akyuu – a twtxt server that never forgets
|
||||
|
||||
## Rationale
|
||||
|
||||
|
@ -24,14 +24,6 @@ space.
|
|||
- all HTML+CSS is read from a templates directory, which can be freely chosen at
|
||||
server start so as to ease customization of the interface
|
||||
|
||||
## Online demo
|
||||
|
||||
A demo instance with frequent downtimes and public sign-up 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). A somewhat
|
||||
more conservatively managed instance can be found at
|
||||
<http://htwtxt.plomlompom.com:80/>.
|
||||
|
||||
## Setup and run
|
||||
|
||||
### Setup Go build environment
|
||||
|
@ -56,17 +48,15 @@ available.)
|
|||
|
||||
Once your Go build environment is ready, do this:
|
||||
|
||||
git clone https://github.com/plomlompom/htwtxt $GOPATH/src/htwtxt
|
||||
go get htwtxt
|
||||
mkdir ~/htwtxt
|
||||
$GOPATH/bin/htwtxt
|
||||
go install marisa.chaotic.ninja/akyuu@latest
|
||||
$GOPATH/bin/akyuu [options]
|
||||
|
||||
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.
|
||||
`~/akyuu`. An alternate directory may be specified with the `--dir` flag.
|
||||
|
||||
### Writing twtxt messages via API
|
||||
|
||||
Using htwtxt from a web browser for purposes such as writing a twtxt message
|
||||
Using akyuu from a web browser for purposes such as writing a twtxt message
|
||||
should be self-explanatory (just use the HTML form on the start page). But it's
|
||||
also possible to write new messages directly to a twtxt feed via a `POST`
|
||||
request to `/feeds`. Just provide appropriate values for the data fields `name`
|
||||
|
@ -80,10 +70,10 @@ line example utilizing the curl tool:
|
|||
|
||||
### Configure port number and TLS
|
||||
|
||||
By default, htwtxt serves unencrypted HTTP over port 8000. But the executable
|
||||
By default, akyuu serves unencrypted HTTP over port 8000. But the executable
|
||||
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.
|
||||
akyuu as an HTTPS server.
|
||||
|
||||
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):
|
||||
|
@ -93,7 +83,7 @@ You might encounter the following issue when trying to set a low port number
|
|||
This is [a common privilege problem](http://stackoverflow.com/q/413807) and
|
||||
[might be solved](http://stackoverflow.com/a/414258) bis this:
|
||||
|
||||
sudo setcap 'cap_net_bind_service=+ep' $GOPATH/bin/htwtxt
|
||||
sudo setcap 'cap_net_bind_service=+ep' $GOPATH/bin/akyuu
|
||||
|
||||
### Public or closed sign-up
|
||||
|
||||
|
@ -105,7 +95,7 @@ followed by an argument of the form `NAME:PASSWORD`.
|
|||
### Set site owner contact info
|
||||
|
||||
The server serves a `/info` page (from the `info.html` template) that may
|
||||
include the site owner's contact info, as given with the `--info` flag.
|
||||
include the site owner's contact info, as given with the `--contact` flag.
|
||||
|
||||
### Activate password reset mails
|
||||
|
||||
|
@ -121,7 +111,7 @@ posed on the password reset links they enable by setting their mail address.
|
|||
|
||||
### Change HTML templates
|
||||
|
||||
By default, HTML templates are read out of `$GOPATH/src/htwtxt/templates/`. An
|
||||
By default, HTML templates are read out of `$GOPATH/src/akyuu/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).
|
||||
|
||||
|
@ -130,6 +120,8 @@ template files of the same names as the default ones, however).
|
|||
htwtxt (c) 2016 Christian Heller a.k.a. [plomlompom](http://www.plomlompom.de),
|
||||
with template design input by [Kai Kubasta](http://kaikubasta.de).
|
||||
|
||||
akyuu (c) 2023-present Izuru Yakumo.
|
||||
|
||||
License: Affero GPL version 3, see `./LICENSE`
|
||||
|
||||
Current version number: 1.0.5
|
||||
Current version number: 1.0.7
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
module marisa.chaotic.ninja/akyuu
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
golang.org/x/crypto v0.8.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
28
handlers.go
28
handlers.go
|
@ -1,17 +1,21 @@
|
|||
// $TheSupernovaDuo: akyuu,v master 2023/4/14 21:2:6 yakumo_izuru Exp $
|
||||
// See LICENSE for copyright details
|
||||
package main
|
||||
|
||||
import "bufio"
|
||||
import "crypto/rand"
|
||||
import "encoding/base64"
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
import "gopkg.in/gomail.v2"
|
||||
import "log"
|
||||
import "github.com/gorilla/mux"
|
||||
import "net/http"
|
||||
import "os"
|
||||
import "strconv"
|
||||
import "strings"
|
||||
import "time"
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/gomail.v2"
|
||||
"log"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func passwordResetRequestGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if "" == mailuser {
|
||||
|
|
58
io.go
58
io.go
|
@ -1,29 +1,32 @@
|
|||
// htwtxt – hosted twtxt server; see README for copyright and license info
|
||||
|
||||
// $TheSupernovaDuo: akyuu,v master 2023/4/14 21:2:6 yakumo_izuru Exp $
|
||||
// See LICENSE for copyright details
|
||||
package main
|
||||
|
||||
import "bufio"
|
||||
import "errors"
|
||||
import "log"
|
||||
import "os"
|
||||
import "strings"
|
||||
import "io/ioutil"
|
||||
|
||||
const loginsFile = "logins.txt"
|
||||
const feedsDir = "feeds"
|
||||
const ipDelaysFile = "ip_delays.txt"
|
||||
const pwResetFile = "password_reset.txt"
|
||||
const pwResetWaitFile = "password_reset_wait.txt"
|
||||
|
||||
var certPath string
|
||||
var dataDir string
|
||||
var feedsPath string
|
||||
var ipDelaysPath string
|
||||
var keyPath string
|
||||
var loginsPath string
|
||||
var pwResetPath string
|
||||
var pwResetWaitPath string
|
||||
var templPath string
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
const (
|
||||
loginsFile = "logins.txt"
|
||||
feedsDir = "feeds"
|
||||
ipDelaysFile = "ip_delays.txt"
|
||||
pwResetFile = "password_reset.txt"
|
||||
pwResetWaitFile = "password_reset_wait.txt"
|
||||
)
|
||||
var (
|
||||
certPath string
|
||||
dataDir string
|
||||
feedsPath string
|
||||
ipDelaysPath string
|
||||
keyPath string
|
||||
loginsPath string
|
||||
pwResetPath string
|
||||
pwResetWaitPath string
|
||||
templPath string
|
||||
)
|
||||
|
||||
func createFileIfNotExists(path string) {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
|
@ -45,7 +48,7 @@ func openFile(path string) *os.File {
|
|||
}
|
||||
|
||||
func linesFromFile(path string) []string {
|
||||
text, err := ioutil.ReadFile(path)
|
||||
text, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("Can't read file", err)
|
||||
}
|
||||
|
@ -57,7 +60,7 @@ func linesFromFile(path string) []string {
|
|||
|
||||
func writeAtomic(path, text string) {
|
||||
tmpFile := path + "_tmp"
|
||||
if err := ioutil.WriteFile(tmpFile, []byte(text), 0600); err != nil {
|
||||
if err := os.WriteFile(tmpFile, []byte(text), 0600); err != nil {
|
||||
log.Fatal("Trouble writing file", err)
|
||||
}
|
||||
if err := os.Rename(path, path+"_"); err != nil {
|
||||
|
@ -76,7 +79,7 @@ func writeLinesAtomic(path string, lines []string) {
|
|||
}
|
||||
|
||||
func appendToFile(path string, msg string) {
|
||||
text, err := ioutil.ReadFile(path)
|
||||
text, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("Can't read file", err)
|
||||
}
|
||||
|
@ -164,6 +167,5 @@ func initFilesAndDirs() {
|
|||
createFileIfNotExists(pwResetPath)
|
||||
createFileIfNotExists(pwResetWaitPath)
|
||||
createFileIfNotExists(ipDelaysPath)
|
||||
// TODO: Handle err here.
|
||||
_ = os.Mkdir(feedsPath, 0700)
|
||||
}
|
||||
|
|
68
main.go
68
main.go
|
@ -1,34 +1,38 @@
|
|||
// htwtxt – hosted twtxt server; see README for copyright and license info
|
||||
|
||||
// $TheSupernovaDuo: akyuu,v master 2023/4/14 21:2:6 yakumo_izuru Exp $
|
||||
// See LICENSE for copyright details
|
||||
package main
|
||||
|
||||
import "errors"
|
||||
import "flag"
|
||||
import "fmt"
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
import "golang.org/x/crypto/ssh/terminal"
|
||||
import "gopkg.in/gomail.v2"
|
||||
import "html/template"
|
||||
import "io/ioutil"
|
||||
import "log"
|
||||
import "net"
|
||||
import "net/http"
|
||||
import "os"
|
||||
import "strconv"
|
||||
import "strings"
|
||||
import "syscall"
|
||||
import "time"
|
||||
|
||||
const resetLinkExp = 1800
|
||||
const resetWaitTime = 3600 * 24
|
||||
const version = "1.0"
|
||||
|
||||
var contact string
|
||||
var dialer *gomail.Dialer
|
||||
var mailuser string
|
||||
var myself string
|
||||
var signupOpen bool
|
||||
var templ *template.Template
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"gopkg.in/gomail.v2"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
const (
|
||||
resetLinkExp = 1800
|
||||
resetWaitTime = 3600 * 24
|
||||
version = "1.0"
|
||||
)
|
||||
var (
|
||||
contact string
|
||||
dialer *gomail.Dialer
|
||||
mailuser string
|
||||
myself string
|
||||
signupOpen bool
|
||||
templ *template.Template
|
||||
)
|
||||
|
||||
func execTemplate(w http.ResponseWriter, file string, input string) {
|
||||
type data struct{ Msg string }
|
||||
|
@ -243,9 +247,9 @@ func readOptions() (string, int, string, int, string, bool) {
|
|||
flag.StringVar(&keyPath, "key", "", "SSL key file")
|
||||
flag.StringVar(&certPath, "cert", "", "SSL certificate file")
|
||||
flag.StringVar(&templPath, "templates",
|
||||
os.Getenv("GOPATH")+"/src/htwtxt/templates",
|
||||
os.Getenv("GOPATH")+"/src/akyuu/templates",
|
||||
"directory where to expect HTML templates")
|
||||
flag.StringVar(&dataDir, "dir", os.Getenv("HOME")+"/htwtxt",
|
||||
flag.StringVar(&dataDir, "dir", os.Getenv("HOME")+"/akyuu",
|
||||
"directory to store feeds and login data")
|
||||
flag.StringVar(&contact, "contact",
|
||||
"[operator passed no contact info to server]",
|
||||
|
@ -284,7 +288,7 @@ func main() {
|
|||
mailserver, mailport, mailpw, port, newLogin, showVersion :=
|
||||
readOptions()
|
||||
if showVersion {
|
||||
fmt.Println("htwtxt", version)
|
||||
fmt.Println("akyuu", FullVersion())
|
||||
return
|
||||
}
|
||||
initFilesAndDirs()
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
{{ template "header" }}
|
||||
<section>
|
||||
<h2>Account settings</h2>
|
||||
<ul>
|
||||
<li><a href="/accountsetpw">Change password</a></li>
|
||||
<li><a href="/accountsetmail">Set mail address</a></li>
|
||||
<li><a href="/accountsetquestion">Set security question</a></li>
|
||||
<li><a href="/passwordreset">Request password reset</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>Account settings</H2>
|
||||
</td>
|
||||
<TD aLIgn="CENTER">
|
||||
<UL>
|
||||
<LI><A HREF="/accountsetpw">Change password</A></LI>
|
||||
<LI><A HREF="/accountsetmail">Set mail address</A></LI>
|
||||
<LI><A HREF="/accountsetquestion">Set security question</A></LI>
|
||||
<LI><A HREF="/passwordreset">Request password reset</A></LI>
|
||||
</UL>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{{ template "header" }}
|
||||
<section class="error">
|
||||
<h2>Error</h2>
|
||||
<p>Something went wrong: {{ .Msg }}</p>
|
||||
</section>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>Error</H2>
|
||||
<P>Something went wrong: {{ .Msg }}</P>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
{{ template "header" }}
|
||||
<section class="error">
|
||||
<h2>Error</h2>
|
||||
<p>Something went wrong: Bad login. (Have you forgotten your password? If you added a mail address to your login data, you might be able to <a href="/passwordreset">reset</a> it.)</p>
|
||||
</section>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>Error</H2>
|
||||
<P>Something went wrong: Bad login. (Have you forgotten your password? If you added a mail address to your login data, you might be able to <A HREF="/passwordreset">reset</A> it.)</P>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{{ template "header" }}
|
||||
<section class="success">
|
||||
<h2>Success</h2>
|
||||
<p>Your account has been set up.</p>
|
||||
</section>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>Success</H2>
|
||||
<P>Your account has been set up.</P>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,27 +1,16 @@
|
|||
{{ template "header" }}
|
||||
<form method="post" action="feeds">
|
||||
<fieldset>
|
||||
<legend>Send twtxt</legend>
|
||||
<FORM METHOD="post" ACTION="feeds">
|
||||
<H1>Send twtxt</H1>
|
||||
<LABEL FOR="twt">Message</LABEL>
|
||||
<INPUT TYPE="text" ID="twt" NAME="twt" MAXLENGTH="140" PLACEHOLDER="What’s happening?" ARIA-DESCRIBEDBY="txt-desc" required />
|
||||
<P ID="txt-desc">Max. 140 characters</P>
|
||||
|
||||
<div>
|
||||
<label for="twt">Message</label>
|
||||
<input type="text" id="twt" name="twt" maxlength="140" placeholder="What’s happening?" aria-describedby="txt-desc" required />
|
||||
<p id="txt-desc"><abbr title="Maximum">Max.</abbr> 140 characters</p>
|
||||
</div>
|
||||
<LABEL FOR="name">Name</LABEL>
|
||||
<INPUT TYPE="text" ID="name" NAME="name" required />
|
||||
<LABEL FOR="password">Password</LABEL>
|
||||
<INPUT TYPE="password" NAME="password" required />
|
||||
<HR />
|
||||
|
||||
<div>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<button type="submit">Publish</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
<BUTTON TYPE="submit">Publish</BUTTON>
|
||||
</FORM>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
{{ template "header" }}
|
||||
<section>
|
||||
<h2>About this site</h2>
|
||||
<p>This site is a web server to host and grow <a href="https://github.com/buckket/twtxt">twtxt</a> feeds for users without trivial access to their own web space.</p>
|
||||
<p>Its operator gives no guarantees regarding long-term availability of feeds, stability of their paths, or quality and integrity of their contents. If that does not satisfy your needs, consider hosting your own instance: The underlying server software <a href="https://github.com/plomlompom/htwtxt">htwtxt</a> is freely available, and should not be too difficult to set up.</p>
|
||||
<p>Site operator's contact info: {{ .Msg }}</p>
|
||||
</section>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>About this site</H2>
|
||||
<P>This site is a web server to host and grow <a href="https://github.com/buckket/twtxt">twtxt</a> feeds for users without trivial access to their own web space.</P>
|
||||
<P>Its operator gives no guarantees regarding long-term availability of feeds, stability of their paths, or quality and integrity of their contents. If that does not satisfy your needs, consider hosting your own instance: The underlying server software <A href="https://git.chaotic.ninja/yakumo.izuru/akyuu">akyuu</A> is freely available, and should not be too difficult to set up.</P>
|
||||
<P>Site operator's contact info: {{ .Msg }}</P>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
{{ template "header" }}
|
||||
<section>
|
||||
<h2>Feeds</h2>
|
||||
<ul>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>Feeds</H2>
|
||||
<UL>
|
||||
{{ range .Dir }}
|
||||
<li><a href="/feeds/{{ . }}">{{ . }}</a></li>
|
||||
<LI><A HREF="/feeds/{{ . }}">{{ . }}</A></LI>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</section>
|
||||
</UL>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
{{ template "header" }}
|
||||
<section>
|
||||
<h2>Password reset inactive</h2>
|
||||
<p>The site operator has currently not activated automatic password resetting.</p>
|
||||
</section>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>Password reset inactive</H2>
|
||||
<P>The site operator has currently not activated automatic password resetting.</P>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{{ template "header" }}
|
||||
<section>
|
||||
<h2>Account creation closed</h2>
|
||||
<p>The site operator has not decided to currently open up account creation on this site to the public.</p>
|
||||
</section>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" WIDTH="90%" BGCOLOR="#CCCCCC" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<H2>Account creation closed</H2>
|
||||
<P>The site operator has not decided to currently open up account creation on this site to the public.</P>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,38 +1,30 @@
|
|||
{{ define "header" }}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>hosted twtxt server</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/style.css" media="all" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1><a href="/">hosted twtxt server</a></h1>
|
||||
</header>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/feeds">Feeds</a></li>
|
||||
<li><a href="/signup">Create account</a></li>
|
||||
<li><a href="/account">Edit account</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{ end }}
|
||||
{{ define "footer" }}
|
||||
<footer>
|
||||
<p>Read more <a href="/info">about this site</a>.<br /> Licensed under <a href="http://www.gnu.org/licenses/agpl-3.0.html" rel="license">AGPLv3</a>. Source code <a href="https://github.com/plomlompom/htwtxt">on GitHub</a>.</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!DOCTYPE HTML PUBLIC "//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META NAME="viewport" CONTENT="width=device-width, initial-scale=1.0" />
|
||||
<TITLE>akyuu</title>
|
||||
<LINK REL="stylesheet" TYPE="text/css" HREF="/style.css" MEDIA="all" />
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<CENTER>
|
||||
<H1><A HREF="/">akyuu</A></H1>
|
||||
</CENTER>
|
||||
<TABLE BORDER="2" CELLPADDING="12" CELLSPACING="0" ALIGN="CENTER">
|
||||
<TR>
|
||||
<TD ALIGN="CENTER">
|
||||
<UL>
|
||||
<LI><A HREF="/feeds">Feeds</A></LI>
|
||||
<LI><A HREF="/signup">Create account</A></LI>
|
||||
<LI><A HREF="/account">Edit account</A></LI>
|
||||
</UL>
|
||||
</TD>
|
||||
</TABLE>
|
||||
{{ end }}
|
||||
{{ define "footer" }}
|
||||
<CENTER>
|
||||
<P>Read more <A HREF="/info">about this site</A>.<BR /> Licensed under <A HREF="http://www.gnu.org/licenses/agpl-3.0.html" REL="license">AGPLv3</A>. Source code <A HREF="https://git.chaotic.ninja/yakumo.izuru/akyuu"></A>.</P>
|
||||
</CENTER>
|
||||
</BODY>
|
||||
</HTML>
|
||||
{{ end }}
|
||||
|
|
|
@ -1,27 +1,14 @@
|
|||
{{ template "header" }}
|
||||
<form method="post" action="/passwordreset/{{ .Msg }}">
|
||||
<fieldset>
|
||||
<legend>Reset account data</legend>
|
||||
|
||||
<div>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" required />
|
||||
<p id="name-desc">Please repeat for verification.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password">New password</label>
|
||||
<input type="password" id="password" name="new_password" required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password2">New password <span>(repeat)</span></label>
|
||||
<input type="password" id="password2" name="new_password2" required />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<button type="submit">Reset</button>
|
||||
</fieldset>
|
||||
<h1>Reset account data</h1>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" required />
|
||||
<p id="name-desc">Please repeat for verification.</p>
|
||||
<label for="password">New password</label>
|
||||
<input type="password" id="password" name="new_password" required />
|
||||
<label for="password2">New password <span>(repeat)</span></label>
|
||||
<input type="password" id="password2" name="new_password2" required />
|
||||
<hr />
|
||||
<button type="submit">Reset</button>
|
||||
</form>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
{{ template "header" }}
|
||||
<form method="post" action="passwordreset">
|
||||
<fieldset>
|
||||
<legend>Request password reset</legend>
|
||||
|
||||
<p>To reset the password for an account, an e-mail address <a href="/accountsetmail">must have been set</a> for it. The request here will (if not hindered by the wait time described below) then be answered with an e-mail to the address which contains nothing but a randomized URL pointing back to a page on this server.</p>
|
||||
<p>Said page will only be available for about 30 minutes. It will ask for the account name, a new password (must be entered twice), and, if a security question <a href="/accountsetquestion">has been set</a> for the account in question, an answer to that. The new password will only be set when the account name given there is the same given here, and, if a security question was set for that account, the answer is the one previously defined for it. Wrong answers will remove the temporary account reset page at once. A new one can be requested here only once every 24 hours.</p>
|
||||
<p>Note that clicking the button below will always redirect you to this site's start page – no matter whether the server actually sends out a password reset link for any account or not.</p>
|
||||
|
||||
<div>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<button type="submit">Request reset</button>
|
||||
</fieldset>
|
||||
<h1>Request password reset</h1>
|
||||
<p>To reset the password for an account, an e-mail address <a href="/accountsetmail">must have been set</a> for it. The request here will (if not hindered by the wait time described below) then be answered with an e-mail to the address which contains nothing but a randomized URL pointing back to a page on this server.</p>
|
||||
<p>Said page will only be available for about 30 minutes. It will ask for the account name, a new password (must be entered twice), and, if a security question <a href="/accountsetquestion">has been set</a> for the account in question, an answer to that. The new password will only be set when the account name given there is the same given here, and, if a security question was set for that account, the answer is the one previously defined for it. Wrong answers will remove the temporary account reset page at once. A new one can be requested here only once every 24 hours.</p>
|
||||
<p>Note that clicking the button below will always redirect you to this site's start page – no matter whether the server actually sends out a password reset link for any account or not.</p>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" />
|
||||
<hr />
|
||||
<button type="submit">Request reset</button>
|
||||
</form>
|
||||
{{ template "footer" }}
|
||||
|
||||
|
|
|
@ -1,44 +1,32 @@
|
|||
{{ template "header" }}
|
||||
<form method="post" action="signup">
|
||||
<fieldset>
|
||||
<legend>Create account</legend>
|
||||
<h1>Create account</h1>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" maxlength="140" aria-describedby="name-desc" pattern="[a-zA-Z0-9_]{1,140}" required />
|
||||
<p id="name-desc">Legal characters: A-Z, a-z, 0-9, _ (<abbr title="maximum">max.</abbr> 140)</p>
|
||||
|
||||
<div>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" maxlength="140" aria-describedby="name-desc" pattern="[a-zA-Z0-9_]{1,140}" required />
|
||||
<p id="name-desc">Legal characters: A-Z, a-z, 0-9, _ (<abbr title="maximum">max.</abbr> 140)</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="mail">E-mail address <span>(optional)</span></label>
|
||||
<input type="email" id="mail" name="mail" aria-describedby="mail-desc"/>
|
||||
<p id="mail-desc">Used for password reset and feed owner authentication.</p>
|
||||
</div>
|
||||
<label for="mail">E-mail address <span>(optional)</span></label>
|
||||
<input type="email" id="mail" name="mail" aria-describedby="mail-desc"/>
|
||||
<p id="mail-desc">Used for password reset and feed owner authentication.</p>
|
||||
|
||||
<div>
|
||||
<label for="secquestion">Security question <span>(optional)</span></label>
|
||||
<input type="text" id="secquestion" name="secquestion" aria-describedby="secquestion-desc" />
|
||||
<p id="secquestion-desc">To pose on password reset request.</p>
|
||||
</div>
|
||||
|
||||
<label for="secquestion">Security question <span>(optional)</span></label>
|
||||
<input type="text" id="secquestion" name="secquestion" aria-describedby="secquestion-desc" />
|
||||
<p id="secquestion-desc">To pose on password reset request.</p>
|
||||
|
||||
<div>
|
||||
<label for="secanswer">Security question answer <span>(optional)</span></label>
|
||||
<input type="text" id="secanswer" name="secanswer" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="new_password" required />
|
||||
</div>
|
||||
<label for="secanswer">Security question answer <span>(optional)</span></label>
|
||||
<input type="text" id="secanswer" name="secanswer" />
|
||||
|
||||
<div>
|
||||
<label for="password2">Password <span>(repeat)</span></label>
|
||||
<input type="password" id="password2" name="new_password2" required />
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<button type="submit">Sign up</button>
|
||||
</fieldset>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="new_password" required />
|
||||
|
||||
|
||||
<label for="password2">Password <span>(repeat)</span></label>
|
||||
<input type="password" id="password2" name="new_password2" required />
|
||||
<hr />
|
||||
<button type="submit">Sign up</button>
|
||||
</form>
|
||||
{{ template "footer" }}
|
||||
|
|
|
@ -1,89 +1,60 @@
|
|||
body {
|
||||
background-color: white;
|
||||
color: black;
|
||||
font: 1rem/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
word-wrap: break-word;
|
||||
max-width: 30rem;
|
||||
padding: 0 1rem;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
color: black;
|
||||
font: 1rem/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
word-wrap: break-word;
|
||||
max-width: 30rem;
|
||||
padding: 0 1rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00c;
|
||||
color: red;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #00c;
|
||||
color: white;
|
||||
color: gold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
outline: thin solid;
|
||||
outline: thin solid;
|
||||
}
|
||||
|
||||
header,
|
||||
nav,
|
||||
footer {
|
||||
text-align: center;
|
||||
h2:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
nav li {
|
||||
display: inline-block;
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
|
||||
section,
|
||||
fieldset {
|
||||
background-color: #eee;
|
||||
padding: 1rem;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
section.success {
|
||||
background-color: #cfc;
|
||||
}
|
||||
|
||||
section.error {
|
||||
background-color: #fcc;
|
||||
}
|
||||
|
||||
section h2:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form div {
|
||||
margin-bottom: 1rem;
|
||||
form {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
form p,
|
||||
label span {
|
||||
color: #444;
|
||||
font-size: .8rem;
|
||||
color: #444;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
form p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
legend,
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font-size: 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
input#twt {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
table {
|
||||
border-color: purple;
|
||||
}
|
||||
tr {
|
||||
border-color: lime;
|
||||
}
|
||||
td {
|
||||
border-color: lightblue;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// $TheSupernovaDuo: akyuu,v master 2023/4/14 21:2:6 yakumo_izuru Exp $
|
||||
// See LICENSE for copyright details
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultVersion = "0.0.0"
|
||||
defaultCommit = "HEAD"
|
||||
defaultBuild = "0000-01-01:00:00+00:00"
|
||||
)
|
||||
|
||||
var (
|
||||
// Version is the tagged release version in the form <major>.<minor>.<patch>
|
||||
// following semantic versioning and is overwritten by the build system.
|
||||
Version = defaultVersion
|
||||
|
||||
// Commit is the commit sha of the build (normally from Git) and is overwritten
|
||||
// by the build system.
|
||||
Commit = defaultCommit
|
||||
|
||||
// Build is the date and time of the build as an RFC3339 formatted string
|
||||
// and is overwritten by the build system.
|
||||
Build = defaultBuild
|
||||
)
|
||||
|
||||
// FullVersion display the full version and build
|
||||
func FullVersion() string {
|
||||
var sb strings.Builder
|
||||
|
||||
isDefault := Version == defaultVersion && Commit == defaultCommit && Build == defaultBuild
|
||||
|
||||
if !isDefault {
|
||||
sb.WriteString(fmt.Sprintf("%s@%s %s", Version, Commit, Build))
|
||||
}
|
||||
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
if isDefault {
|
||||
sb.WriteString(fmt.Sprintf(" %s", info.Main.Version))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" %s", info.GoVersion))
|
||||
if info.Main.Sum != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s", info.Main.Sum))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
読み込み中…
新しいイシューから参照