cmd/web: make error messages cleaner, add TLS, fix the posting form
Signed-off-by: Xe Iaso <xe@tailscale.com>
This commit is contained in:
parent
34f5253535
commit
d8cdd736a7
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/tls"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"embed"
|
"embed"
|
||||||
|
@ -26,8 +27,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hostname = flag.String("hostname", envOr("TSNET_HOSTNAME", "paste"), "hostname to use on your tailnet, TSNET_HOSTNAME in the environment")
|
hostname = flag.String("hostname", envOr("TSNET_HOSTNAME", "paste"), "hostname to use on your tailnet, TSNET_HOSTNAME in the environment")
|
||||||
dataDir = flag.String("data-location", dataLocation(), "where data is stored, defaults to DATA_DIR or ~/.config/tailscale/paste")
|
dataDir = flag.String("data-location", dataLocation(), "where data is stored, defaults to DATA_DIR or ~/.config/tailscale/paste")
|
||||||
|
tsnetLogVerbose = flag.Bool("tsnet-verbose", false, "if set, have tsnet log verbosely to standard error")
|
||||||
|
|
||||||
//go:embed schema.sql
|
//go:embed schema.sql
|
||||||
sqlSchema string
|
sqlSchema string
|
||||||
|
@ -57,9 +59,10 @@ func envOr(key, defaultVal string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
lc *tailscale.LocalClient
|
lc *tailscale.LocalClient // localclient to tsnet server
|
||||||
db *sql.DB
|
db *sql.DB // SQLite datastore
|
||||||
tmpls *template.Template
|
tmpls *template.Template // HTML templates
|
||||||
|
httpsURL string // the tailnet/public base URL of this service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) TailnetIndex(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) TailnetIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -70,7 +73,7 @@ func (s *Server) TailnetIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ui, err := upsertUserInfo(r.Context(), s.db, s.lc, r.RemoteAddr)
|
ui, err := upsertUserInfo(r.Context(), s.db, s.lc, r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ShowError(w, r, err, http.StatusInternalServerError)
|
s.ShowError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +174,7 @@ VALUES
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ShowError(w, r, err, http.StatusInternalServerError)
|
s.ShowError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,9 +183,9 @@ VALUES
|
||||||
switch r.Header.Get("Accept") {
|
switch r.Header.Get("Accept") {
|
||||||
case "text/plain":
|
case "text/plain":
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprintf(w, "http://%s/paste/%s", r.Host, id)
|
fmt.Fprintf(w, "https://%s/paste/%s", s.httpsURL, id)
|
||||||
default:
|
default:
|
||||||
http.Redirect(w, r, fmt.Sprintf("http://%s/%s", r.Host, id), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, fmt.Sprintf("https://%s/paste/%s", s.httpsURL, id), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -190,7 +193,7 @@ VALUES
|
||||||
func (s *Server) TailnetPasteIndex(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) TailnetPasteIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
userInfo, err := upsertUserInfo(r.Context(), s.db, s.lc, r.RemoteAddr)
|
userInfo, err := upsertUserInfo(r.Context(), s.db, s.lc, r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ShowError(w, r, err, http.StatusInternalServerError)
|
s.ShowError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +214,7 @@ SELECT p.id
|
||||||
FROM pastes p
|
FROM pastes p
|
||||||
INNER JOIN users u
|
INNER JOIN users u
|
||||||
ON p.user_id = u.id
|
ON p.user_id = u.id
|
||||||
|
ORDER BY p.rowid DESC
|
||||||
LIMIT 25
|
LIMIT 25
|
||||||
OFFSET ?1
|
OFFSET ?1
|
||||||
`
|
`
|
||||||
|
@ -229,7 +233,7 @@ OFFSET ?1
|
||||||
|
|
||||||
rows, err := s.db.Query(q, clampToZero(pageNum))
|
rows, err := s.db.Query(q, clampToZero(pageNum))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ShowError(w, r, err, http.StatusInternalServerError)
|
s.ShowError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +253,17 @@ OFFSET ?1
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(jpis) == 0 {
|
if len(jpis) == 0 {
|
||||||
|
err = s.tmpls.ExecuteTemplate(w, "nopastes.tmpl", struct {
|
||||||
|
UserInfo *tailcfg.UserProfile
|
||||||
|
Title string
|
||||||
|
}{
|
||||||
|
UserInfo: userInfo.UserProfile,
|
||||||
|
Title: "Pastes",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s: %v", r.RemoteAddr, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var prev, next *int
|
var prev, next *int
|
||||||
|
@ -276,7 +290,7 @@ OFFSET ?1
|
||||||
Pastes: jpis,
|
Pastes: jpis,
|
||||||
Prev: prev,
|
Prev: prev,
|
||||||
Next: next,
|
Next: next,
|
||||||
Page: pageNum,
|
Page: pageNum + 1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("%s: %v", r.RemoteAddr, err)
|
log.Printf("%s: %v", r.RemoteAddr, err)
|
||||||
|
@ -407,6 +421,10 @@ func main() {
|
||||||
Logf: func(string, ...any) {},
|
Logf: func(string, ...any) {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *tsnetLogVerbose {
|
||||||
|
s.Logf = log.Printf
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Start(); err != nil {
|
if err := s.Start(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -421,7 +439,26 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
_ = lc
|
|
||||||
|
// wait for tailscale to start before trying to fetch cert names
|
||||||
|
for i := 0; i < 60; i++ {
|
||||||
|
st, err := lc.Status(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error retrieving tailscale status; retrying: %v", err)
|
||||||
|
} else {
|
||||||
|
if st.BackendState == "Running" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
httpsURL, ok := lc.ExpandSNIName(ctx, *hostname)
|
||||||
|
if !ok {
|
||||||
|
log.Printf(httpsURL)
|
||||||
|
log.Fatal("HTTPS is not enabled in the admin panel")
|
||||||
|
}
|
||||||
|
|
||||||
ln, err := s.Listen("tcp", ":80")
|
ln, err := s.Listen("tcp", ":80")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -430,7 +467,7 @@ func main() {
|
||||||
|
|
||||||
tmpls := template.Must(template.ParseFS(templateFiles, "tmpl/*.tmpl"))
|
tmpls := template.Must(template.ParseFS(templateFiles, "tmpl/*.tmpl"))
|
||||||
|
|
||||||
srv := &Server{lc, db, tmpls}
|
srv := &Server{lc, db, tmpls, httpsURL}
|
||||||
|
|
||||||
tailnetMux := http.NewServeMux()
|
tailnetMux := http.NewServeMux()
|
||||||
tailnetMux.Handle("/static/", http.FileServer(http.FS(staticFiles)))
|
tailnetMux.Handle("/static/", http.FileServer(http.FS(staticFiles)))
|
||||||
|
@ -445,7 +482,18 @@ func main() {
|
||||||
funnelMux.HandleFunc("/paste/", srv.ShowPost)
|
funnelMux.HandleFunc("/paste/", srv.ShowPost)
|
||||||
|
|
||||||
log.Printf("listening on http://%s", *hostname)
|
log.Printf("listening on http://%s", *hostname)
|
||||||
log.Fatal(http.Serve(ln, tailnetMux))
|
go func() { log.Fatal(http.Serve(ln, tailnetMux)) }()
|
||||||
|
|
||||||
|
l443, err := s.Listen("tcp", ":443")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l443.Close()
|
||||||
|
l443 = tls.NewListener(l443, &tls.Config{
|
||||||
|
GetCertificate: lc.GetCertificate,
|
||||||
|
})
|
||||||
|
log.Printf("listening on https://%s", httpsURL)
|
||||||
|
log.Fatal(http.Serve(l443, tailnetMux))
|
||||||
}
|
}
|
||||||
|
|
||||||
func openDB(dir string) (*sql.DB, error) {
|
func openDB(dir string) (*sql.DB, error) {
|
||||||
|
|
7
cmd/web/tmpl/nopastes.tmpl
Normal file
7
cmd/web/tmpl/nopastes.tmpl
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{{template "header" .}}
|
||||||
|
|
||||||
|
<div class="right"><img style="width:32px;height:32px" src="{{.PasterProfilePicURL}}" /></div>
|
||||||
|
|
||||||
|
There are no pastes in the list. Try <a href="/">creating some</a>
|
||||||
|
|
||||||
|
{{template "footer" .}}
|
8
cmd/web/tmpl/pastecreated.tmpl
Normal file
8
cmd/web/tmpl/pastecreated.tmpl
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{{template "header" .}}
|
||||||
|
|
||||||
|
<div class="right"><img style="width:32px;height:32px" src="{{.PasterProfilePicURL}}" /></div>
|
||||||
|
|
||||||
|
Your <a href="/paste/{{.ID}}">new paste</a> has been created!
|
||||||
|
|
||||||
|
<a href="/paste/{{.ID}}">Permalink</a>
|
||||||
|
{{template "footer" .}}
|
Loading…
Reference in a new issue