chore: Restructure
This commit is contained in:
246
cmd/rio/main.go
Normal file
246
cmd/rio/main.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.polynom.me/rio/internal/acme"
|
||||
"git.polynom.me/rio/internal/certificates"
|
||||
"git.polynom.me/rio/internal/dns"
|
||||
"git.polynom.me/rio/internal/pages"
|
||||
"git.polynom.me/rio/internal/repo"
|
||||
"git.polynom.me/rio/internal/server"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func handleSubdomain(domain string, cname string, path, giteaUrl string, giteaClient *gitea.Client, w http.ResponseWriter) {
|
||||
hostParts := strings.Split(domain, ".")
|
||||
username := hostParts[0]
|
||||
|
||||
// Strip the leading /
|
||||
if path[:1] == "/" {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
repo, path, err := repo.RepoFromPath(
|
||||
username,
|
||||
domain,
|
||||
cname,
|
||||
path,
|
||||
giteaClient,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get repo: %s", err)
|
||||
w.WriteHeader(404)
|
||||
return
|
||||
}
|
||||
|
||||
pages.ServeFile(username, repo.Name, path, giteaUrl, w)
|
||||
}
|
||||
|
||||
func Handler(pagesDomain, giteaUrl string, giteaClient *gitea.Client) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Server", "rio")
|
||||
|
||||
if strings.HasSuffix(req.Host, pagesDomain) {
|
||||
log.Debug("Domain can be directly handled")
|
||||
handleSubdomain(req.Host, "", req.URL.Path, giteaUrl, giteaClient, w)
|
||||
return
|
||||
}
|
||||
|
||||
cname, err := dns.LookupCNAME(req.Host)
|
||||
if err != nil {
|
||||
log.Warningf("CNAME request failed, we don't handle %s", req.Host)
|
||||
w.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
log.Debugf("Got CNAME %s", cname)
|
||||
|
||||
if strings.HasSuffix(cname, pagesDomain) {
|
||||
log.Debugf("%s is alias of %s and can be handled after a CNAME query", req.Host, cname)
|
||||
handleSubdomain(cname, cname, req.URL.Path, giteaUrl, giteaClient, w)
|
||||
return
|
||||
}
|
||||
|
||||
log.Errorf("Not handling %s", req.Host)
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
|
||||
func runServer(ctx *cli.Context) error {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
giteaUrl := ctx.String("gitea-url")
|
||||
domain := ctx.String("pages-domain")
|
||||
certsFile := ctx.String("certs-file")
|
||||
acmeEmail := ctx.String("acme-email")
|
||||
acmeServer := ctx.String("acme-server")
|
||||
acmeFile := ctx.String("acme-file")
|
||||
acmeHost := ctx.String("acme-host")
|
||||
acmePort := ctx.String("acme-port")
|
||||
acmeDisable := ctx.Bool("acme-disable")
|
||||
|
||||
// Setup the Gitea stuff
|
||||
httpClient := http.Client{Timeout: 10 * time.Second}
|
||||
client, err := gitea.NewClient(
|
||||
giteaUrl,
|
||||
gitea.SetHTTPClient(&httpClient),
|
||||
gitea.SetToken(""),
|
||||
gitea.SetUserAgent("rio"),
|
||||
)
|
||||
|
||||
// Listen on the port
|
||||
addr := ctx.String("listen-host") + ":" + ctx.String("listen-port")
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
fmt.Errorf("Failed to create listener: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !acmeDisable {
|
||||
if acmeEmail == "" || acmeFile == "" || certsFile == "" {
|
||||
return errors.New("The options acme-file, acme-email, and certs-file are required")
|
||||
}
|
||||
|
||||
cache, err := certificates.CertificateCacheFromFile(certsFile)
|
||||
if err != nil {
|
||||
log.Debugf("Generating cert")
|
||||
fallback, err := certificates.MakeFallbackCertificate(domain)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate fallback certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
cache.FallbackCertificate = fallback
|
||||
cache.Certificates = make(map[string]certificates.CertificateWrapper)
|
||||
cache.FlushToDisk(certsFile)
|
||||
log.Debug("Certificate wrote to disk")
|
||||
} else {
|
||||
log.Debug("Certificate store read from disk")
|
||||
}
|
||||
|
||||
// Create an ACME client, if we failed to load one
|
||||
acmeClient, err := acme.ClientFromFile(acmeFile, acmeServer)
|
||||
if err != nil {
|
||||
log.Warn("Failed to load ACME client data from disk. Generating new account")
|
||||
acmeClient, err = acme.GenerateNewAccount(acmeEmail, acmeFile, acmeServer)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate new ACME client: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("ACME account registered")
|
||||
} else {
|
||||
log.Info("ACME client data read from disk")
|
||||
}
|
||||
|
||||
// Set up the HTTP01 listener
|
||||
err = acmeClient.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer(acmeHost, acmePort),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to setup HTTP01 challenge listener: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig := server.MakeTlsConfig(
|
||||
domain,
|
||||
certsFile,
|
||||
&cache,
|
||||
acmeClient,
|
||||
)
|
||||
listener = tls.NewListener(listener, tlsConfig)
|
||||
}
|
||||
|
||||
if err := http.Serve(listener, Handler(domain, giteaUrl, client)); err != nil {
|
||||
fmt.Printf("Listening failed")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Action: runServer,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "gitea-url",
|
||||
Usage: "The (HTTPS) URL to the serving Gitea instance",
|
||||
EnvVars: []string{"GITEA_URL"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "listen-host",
|
||||
Usage: "The host to listen on",
|
||||
EnvVars: []string{"HOST"},
|
||||
Value: "127.0.0.1",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "listen-port",
|
||||
Usage: "The port to listen on",
|
||||
EnvVars: []string{"PORT"},
|
||||
Value: "8888",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-host",
|
||||
Usage: "The host to bind to for ACME challenges",
|
||||
EnvVars: []string{"ACME_HOST"},
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-port",
|
||||
Usage: "The port to listen on for ACME challenges",
|
||||
EnvVars: []string{"ACME_PORT"},
|
||||
Value: "8889",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pages-domain",
|
||||
Usage: "The domain on which the server is reachable",
|
||||
EnvVars: []string{"PAGES_DOMAIN"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "certs-file",
|
||||
Usage: "File to store certificates in",
|
||||
EnvVars: []string{"CERTS_FILE"},
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-file",
|
||||
Usage: "File to store ACME configuration in",
|
||||
EnvVars: []string{"ACME_FILE"},
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-email",
|
||||
Usage: "Email to use for an ACME account",
|
||||
EnvVars: []string{"ACME_EMAIL"},
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-server",
|
||||
Usage: "CA Directory to use",
|
||||
EnvVars: []string{"ACME_SERVER"},
|
||||
Value: "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "acme-disable",
|
||||
Usage: "Whether to disable automatic ACME certificates",
|
||||
EnvVars: []string{"ACME_DISABLE"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatalf("Failed to run app: %s", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user