
Signed-off-by: Aoi K <koizumi.aoi@chaotic.ninja> git-svn-id: file:///srv/svn/repo/toyohime/trunk@111 922d331f-388e-da47-97a9-ad700dc0b8b9
284 lines
7.4 KiB
Go
284 lines
7.4 KiB
Go
/*
|
|
Runs a barebones vanity server over HTTP.
|
|
|
|
Usage
|
|
|
|
./yorihime [-index] [-nohealthz] fqdn [repo file]
|
|
|
|
The "-index" flag enables an index page at "/" that lists all repos hosted on
|
|
this server.
|
|
|
|
The "-nohealthz" flag disables the "/healthz" endpoint that returns a 200 OK
|
|
when everything is OK.
|
|
|
|
The "-watch" flag watches the repo file for changes. When it is updated, the
|
|
updated version will be used for serving.
|
|
|
|
If repo file is not given, "./repos" is used. The file has the following format:
|
|
|
|
pkgroot vcsScheme://vcsHost/user/repo
|
|
pkgroot2 vcsScheme://vcsHost/user/repo2
|
|
|
|
vcsHost is either a Gogs server (that's what I use) or github.com. I'm open to
|
|
supporting other VCSs but I'm not sure what that would look like.
|
|
*/
|
|
package main // go.jonnrb.io/vanity
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"marisa.chaotic.ninja/toyohime"
|
|
)
|
|
|
|
var (
|
|
showIndex = flag.Bool("index", false, "Show a list of repos at /")
|
|
noHealthz = flag.Bool("nohealthz", false, "Disable healthcheck endpoint at /healthz")
|
|
watch = flag.Bool("watch", false, "Watch repos file for changes and reload")
|
|
listenPort = flag.String("listen", ":8080", "Port to bind on")
|
|
)
|
|
|
|
var (
|
|
host string // param 1
|
|
reposPath string = "repos" // param 2
|
|
)
|
|
|
|
func serveRepo(mux *http.ServeMux, root string, u *url.URL) {
|
|
vcsScheme, vcsHost := u.Scheme, u.Host
|
|
|
|
// Get ["", "user", "repo"].
|
|
pathParts := strings.Split(u.Path, "/")
|
|
if len(pathParts) != 3 {
|
|
log.Fatalf("Repo URL must be of the form vcsScheme://vcsHost/user/repo but got %q", u.String())
|
|
}
|
|
user, repo := pathParts[1], pathParts[2]
|
|
|
|
importPath := host + "/" + root
|
|
var h http.Handler
|
|
if vcsHost == "github.com" {
|
|
h = toyohime.GitHubHandler(importPath, user, repo, vcsScheme)
|
|
} else {
|
|
h = toyohime.GogsHandler(importPath, vcsHost, user, repo, vcsScheme)
|
|
}
|
|
mux.Handle("/"+root, h)
|
|
mux.Handle("/"+root+"/", h)
|
|
}
|
|
|
|
func addRepoHandlers(mux *http.ServeMux, r io.Reader) error {
|
|
indexMap := map[string]string{}
|
|
|
|
sc := bufio.NewScanner(r)
|
|
for sc.Scan() {
|
|
fields := strings.Fields(sc.Text())
|
|
switch len(fields) {
|
|
case 0:
|
|
continue
|
|
case 2:
|
|
// Pass
|
|
default:
|
|
return fmt.Errorf("expected line of form \"path vcsScheme://vcsHost/user/repo\" but got %q", sc.Text())
|
|
}
|
|
|
|
if *showIndex {
|
|
indexMap[fields[0]] = fields[1]
|
|
}
|
|
|
|
path := fields[0]
|
|
u, err := url.Parse(fields[1])
|
|
if err != nil {
|
|
return fmt.Errorf("repo was not a valid URL: %q", fields[1])
|
|
}
|
|
|
|
serveRepo(mux, path, u)
|
|
}
|
|
|
|
if !*showIndex {
|
|
return nil
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
err := template.Must(template.New("").Parse(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Import paths hosted at {{ .Host }}</title>
|
|
<style type="text/css">
|
|
body,td{font-size:11pt; color:#4d4d4d; font-family:ms pgothic, ms gothic, osaka; background-color:#f8dfdf}
|
|
a:link{color:#cc9999; text-decoration:none}
|
|
a:visited{color:#cc9999; text-decoration:none}
|
|
a:active{color:#cc9999; text-decoration:none; position:relative; top:3px; left:3px}
|
|
a:hover{color:#cc9999; text-decoration:none; position:relative; top:3px; left:3px}
|
|
.yohaku{margin-top:30px}
|
|
.dia{line-height:130%}
|
|
.star1{font-size:22px; color:#cc9999}
|
|
.position1{position:relative; top:7px}
|
|
.line{border-top:2px dotted #cc9999}
|
|
.waku{border:1px solid #cc9999}
|
|
.bg{background-color:#ffffff}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
{{ $host := .Host }}
|
|
<div align="center">
|
|
<p>でホストされているインポート パス {{ html $host }}</p>
|
|
</div>
|
|
<div align="center">
|
|
<table width="470" class="bg waku yohaku" cellspacing="0" style="margin-bottom: 20px">
|
|
{{ range $root, $repo := .IndexMap }}
|
|
<tr><td class="bg" style="padding-top: 5px; padding-bottom: 5px">
|
|
<div class="star1" align="center">
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
<span style="position:relative; top:5px">*</span>
|
|
<span style="position:relative; top:-5px">*</span>
|
|
</div>
|
|
</td></tr>
|
|
<tr><td class="bg line dia" style="padding: 15px 10px 10px 10px">
|
|
<center>
|
|
<h2>{{ html $root }}</h2>
|
|
<a href="https://{{ html $host }}/{{ html $root }}">Package</a> <br/>
|
|
<a href="{{ html $repo }}">Repository</a></li>
|
|
{{ else }}
|
|
Nothing here.
|
|
{{ end }}
|
|
</center>
|
|
</table>
|
|
<p>~から明らかに盗まれた <a href="http://azukifont.com">azukifont.com</a></p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`)).Execute(&b, struct {
|
|
IndexMap map[string]string
|
|
Host string
|
|
}{
|
|
IndexMap: indexMap,
|
|
Host: host,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't create index page: %v", err)
|
|
}
|
|
buf := b.Bytes()
|
|
|
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
io.Copy(w, bytes.NewReader(buf))
|
|
}))
|
|
return nil
|
|
}
|
|
|
|
func registerHealthz(mux *http.ServeMux, isHealthy func() bool) {
|
|
mux.Handle("/healthz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if isHealthy() {
|
|
io.WriteString(w, "OK\r\n")
|
|
} else {
|
|
http.Error(w, "internal error\r\n", http.StatusInternalServerError)
|
|
}
|
|
}))
|
|
}
|
|
|
|
var healthcheck = func() bool {
|
|
return true
|
|
}
|
|
|
|
func generateHandler() (http.Handler, error) {
|
|
mux := http.NewServeMux()
|
|
|
|
f, err := os.Open(reposPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error opening %q: %v", reposPath, err)
|
|
}
|
|
if err := addRepoHandlers(mux, f); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !*noHealthz {
|
|
registerHealthz(mux, healthcheck)
|
|
}
|
|
return mux, nil
|
|
}
|
|
|
|
func buildServer(h http.Handler) *http.Server {
|
|
return &http.Server{
|
|
// This should be sufficient.
|
|
ReadTimeout: 5 * time.Second,
|
|
WriteTimeout: 5 * time.Second,
|
|
IdleTimeout: 5 * time.Second,
|
|
|
|
Addr: *listenPort,
|
|
Handler: h,
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(os.Stderr, "usage: %s fqdn [repos file]\n", os.Args[0])
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.Parse()
|
|
|
|
host = flag.Arg(0)
|
|
if host == "" {
|
|
flag.Usage()
|
|
os.Exit(-1)
|
|
}
|
|
|
|
if override := flag.Arg(1); override != "" {
|
|
reposPath = override
|
|
}
|
|
|
|
var h http.Handler
|
|
if *watch {
|
|
dh := newDynamicHandler(reposPath, generateHandler)
|
|
healthcheck = dh.IsHealthy
|
|
defer dh.Close()
|
|
h = dh
|
|
} else {
|
|
var err error
|
|
h, err = generateHandler()
|
|
if err != nil {
|
|
log.Printf("Error generating handler: %v", err)
|
|
}
|
|
}
|
|
|
|
srv := buildServer(h)
|
|
|
|
log.Printf("starting yorihime %v on port %v\n", toyohime.FullVersion(), *listenPort)
|
|
log.Println("gotta take care of those moon rabbits!")
|
|
log.Println(srv.ListenAndServe())
|
|
}
|