separate out command binaries

main
Bill Mill 2 years ago
parent 70bf57dba3
commit 61a2113d9d
  1. 10
      Makefile
  2. 165
      client/main.go
  3. 171
      cmd/springer/main.go
  4. 67
      cmd/springerd/main.go
  5. 2
      modd.conf
  6. 84
      server/main.go

@ -1,8 +1,8 @@
bin/springerd: server/main.go
go build -o bin/springerd ./server
bin/springerd: cmd/springerd/main.go server/main.go
go build -o bin/springerd ./cmd/springer
bin/springer: client/main.go
go build -o bin/springer ./client
go build -o bin/springer ./cmd/springerd
# Use zig as the cc to cross-compile mattn/sqlite for linux
#
@ -12,5 +12,5 @@ bin/springer: client/main.go
#
# to run locally:
# docker run -it -v $(pwd)/bin:/app ubuntu /app/springerd-linux
bin/springerd-linux: server/main.go
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux" CXX="zig cc -target x86_64-linux" go build -o bin/springerd-linux ./server
bin/springerd-linux: cmd/springerd/main.go server/main.go
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux" CXX="zig cc -target x86_64-linux" go build -o bin/springerd-linux ./cmd/springerd

@ -1,25 +1,16 @@
// TODO:
// * Board UI?
package main
package client
import (
"bytes"
"crypto/ed25519"
"encoding/hex"
"errors"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/user"
"path/filepath"
"runtime"
"sync"
"time"
)
func validKey() (ed25519.PublicKey, ed25519.PrivateKey) {
func ValidKey() (ed25519.PublicKey, ed25519.PrivateKey) {
// A conforming key's final seven hex characters must be 83e followed by
// four characters that, interpreted as MMYY, express a valid month and
// year in the range 01/00 .. 12/99. Formally, the key must match this
@ -75,155 +66,3 @@ func validKey() (ed25519.PublicKey, ed25519.PrivateKey) {
return publicKey, privateKey
}
func fileExists(name string) bool {
if _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) {
return false
}
return true
}
func getKeys(folder string) (ed25519.PublicKey, ed25519.PrivateKey) {
// get the expected public key file and private key file paths
var pubfile, privfile string
if len(folder) == 0 {
user, err := user.Current()
if err != nil {
panic(err)
}
configPath := os.Getenv("XDG_CONFIG_HOME")
if configPath == "" {
configPath = filepath.Join(user.HomeDir, ".config", "spring83")
}
if err = os.MkdirAll(configPath, os.ModePerm); err != nil {
panic(err)
}
pubfile = filepath.Join(configPath, "key.pub")
privfile = filepath.Join(configPath, "key.priv")
} else {
pubfile = filepath.Join(folder, "key.pub")
privfile = filepath.Join(folder, "key.priv")
}
// try to load the public and private key files as ed25519 keys
var pubkey ed25519.PublicKey
var privkey ed25519.PrivateKey
var err error
if fileExists(pubfile) && fileExists(privfile) {
pubkey, err = ioutil.ReadFile(pubfile)
if err != nil {
panic(err)
}
privkey, err = ioutil.ReadFile(privfile)
if err != nil {
panic(err)
}
} else {
fmt.Println("Generating valid key. This will take a minute")
pubkey, privkey = validKey()
os.WriteFile(pubfile, pubkey, 0666)
os.WriteFile(privfile, privkey, 0600)
}
return pubkey, privkey
}
func getBody(inputFile string) []byte {
var body []byte
var err error
if inputFile == "-" {
body, err = ioutil.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
} else {
body, err = ioutil.ReadFile(inputFile)
if err != nil {
panic(err)
}
}
// Prepend a time element. Maybe we should check to see if it's already
// been provided?
timeElt := []byte(fmt.Sprintf("<time datetime=\"%s\">", time.Now().UTC().Format(time.RFC3339)))
body = append(timeElt, body...)
if len(body) == 0 {
panic(fmt.Errorf("input required"))
}
if len(body) > 2217 {
panic(fmt.Errorf("input body too long"))
}
return body
}
func sendBody(server string, body []byte, pubkey ed25519.PublicKey, privkey ed25519.PrivateKey) {
client := &http.Client{}
url := fmt.Sprintf("%s/%x", server, pubkey)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(body))
if err != nil {
panic(err)
}
sig := ed25519.Sign(privkey, body)
req.Header.Set("Authorization", fmt.Sprintf("Spring-83 Signature=%x", sig))
dt := time.Now().Format(time.RFC1123)
req.Header.Set("If-Unmodified-Since", dt)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if resp.StatusCode != 200 {
fmt.Printf("%s: %s\n", resp.Status, responseBody)
}
}
func usage() {
fmt.Print(`springer [-file=filename] [-key=keyfolder] server
Send a spring83 board to a server
flags:
-file=filename
if present, a file to send to the server instead of accepting bytes on
stdin
-key=keyfolder
a folder the program should use for finding your public and private
keys. It will expect there to be two files, one called "key.pub" and
another called "key.priv" which are your public and private keys,
respectively
`)
os.Exit(1)
}
func main() {
inputFile := flag.String("file", "-", "The file to send to the server")
keyFolder := flag.String("key", "", "A folder to check for key.pub and key.priv")
flag.Usage = usage
flag.Parse()
if len(flag.Args()) < 1 {
usage()
}
server := flag.Args()[0]
pubkey, privkey := getKeys(*keyFolder)
sendBody(server, getBody(*inputFile), pubkey, privkey)
}

@ -0,0 +1,171 @@
// TODO:
// * Board UI?
package main
import (
"bytes"
"crypto/ed25519"
"errors"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/user"
"path/filepath"
"time"
"github.com/llimllib/springer/client"
)
func fileExists(name string) bool {
if _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) {
return false
}
return true
}
func getKeys(folder string) (ed25519.PublicKey, ed25519.PrivateKey) {
// get the expected public key file and private key file paths
var pubfile, privfile string
if len(folder) == 0 {
user, err := user.Current()
if err != nil {
panic(err)
}
configPath := os.Getenv("XDG_CONFIG_HOME")
if configPath == "" {
configPath = filepath.Join(user.HomeDir, ".config", "spring83")
}
if err = os.MkdirAll(configPath, os.ModePerm); err != nil {
panic(err)
}
pubfile = filepath.Join(configPath, "key.pub")
privfile = filepath.Join(configPath, "key.priv")
} else {
pubfile = filepath.Join(folder, "key.pub")
privfile = filepath.Join(folder, "key.priv")
}
// try to load the public and private key files as ed25519 keys
var pubkey ed25519.PublicKey
var privkey ed25519.PrivateKey
var err error
if fileExists(pubfile) && fileExists(privfile) {
pubkey, err = ioutil.ReadFile(pubfile)
if err != nil {
panic(err)
}
privkey, err = ioutil.ReadFile(privfile)
if err != nil {
panic(err)
}
} else {
fmt.Println("Generating valid key. This will take a minute")
pubkey, privkey = client.ValidKey()
os.WriteFile(pubfile, pubkey, 0666)
os.WriteFile(privfile, privkey, 0600)
}
return pubkey, privkey
}
func getBody(inputFile string) []byte {
var body []byte
var err error
if inputFile == "-" {
body, err = ioutil.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
} else {
body, err = ioutil.ReadFile(inputFile)
if err != nil {
panic(err)
}
}
// Prepend a time element. Maybe we should check to see if it's already
// been provided?
timeElt := []byte(fmt.Sprintf("<time datetime=\"%s\">", time.Now().UTC().Format(time.RFC3339)))
body = append(timeElt, body...)
if len(body) == 0 {
panic(fmt.Errorf("input required"))
}
if len(body) > 2217 {
panic(fmt.Errorf("input body too long"))
}
return body
}
func sendBody(server string, body []byte, pubkey ed25519.PublicKey, privkey ed25519.PrivateKey) {
client := &http.Client{}
url := fmt.Sprintf("%s/%x", server, pubkey)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(body))
if err != nil {
panic(err)
}
sig := ed25519.Sign(privkey, body)
req.Header.Set("Authorization", fmt.Sprintf("Spring-83 Signature=%x", sig))
dt := time.Now().Format(time.RFC1123)
req.Header.Set("If-Unmodified-Since", dt)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if resp.StatusCode != 200 {
fmt.Printf("%s: %s\n", resp.Status, responseBody)
}
}
func usage() {
fmt.Print(`springer [-file=filename] [-key=keyfolder] server
Send a spring83 board to a server
flags:
-file=filename
if present, a file to send to the server instead of accepting bytes on
stdin
-key=keyfolder
a folder the program should use for finding your public and private
keys. It will expect there to be two files, one called "key.pub" and
another called "key.priv" which are your public and private keys,
respectively
`)
os.Exit(1)
}
func main() {
inputFile := flag.String("file", "-", "The file to send to the server")
keyFolder := flag.String("key", "", "A folder to check for key.pub and key.priv")
flag.Usage = usage
flag.Parse()
if len(flag.Args()) < 1 {
usage()
}
server := flag.Args()[0]
pubkey, privkey := getKeys(*keyFolder)
sendBody(server, getBody(*inputFile), pubkey, privkey)
}

@ -0,0 +1,67 @@
package main
import (
"fmt"
"net/http"
"os"
"time"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/llimllib/springer/server"
)
// getenv returns the environment variable given by the key if present,
// otherwise it returns adefault
func getenv(key string, adefault string) string {
val, ok := os.LookupEnv(key)
if !ok {
return adefault
}
return val
}
func initLog() zerolog.Logger {
logLevel := getenv("LOG_LEVEL", "info")
level, err := zerolog.ParseLevel(logLevel)
if err != nil {
log.Panic().Err(err).Msg("")
}
log := zerolog.New(os.Stderr).With().Timestamp().Logger().Level(level)
if getenv("PRETTY_LOGGING", "") != "" {
log = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
}
return log
}
func main() {
log := initLog()
db := server.InitDB(log)
server.InitRuntimeValues(log)
server.InitCleaner(db, log)
spring83 := server.NewSpring83Server(db, log)
host := getenv("HOST", "")
port := getenv("PORT", "8000")
addr := fmt.Sprintf("%s:%s", host, port)
timeoutMsg := "Request timed out"
srv := &http.Server{
Addr: addr,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
IdleTimeout: 30 * time.Second,
ReadHeaderTimeout: 2 * time.Second,
Handler: server.RequestLogger(log, http.TimeoutHandler(spring83, 2*time.Second, timeoutMsg)),
}
log.Info().Str("addr", addr).Msg("starting helloserver on")
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Err(err).Msg("Error received from server")
}
}

@ -1,3 +1,3 @@
server/main.go server/templates/** {
daemon: go run ./server
daemon: go run ./cmd/springerd
}

@ -9,7 +9,7 @@
// * scan for <link rel="next"...> links as specified in the spec
// * add unique request ID
package main
package server
import (
"bytes"
@ -37,7 +37,6 @@ import (
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
const MAX_KEY_64 = (1 << 64) - 1
@ -62,23 +61,13 @@ var (
vcsTime string
)
// getenv returns the environment variable given by the key if present,
// otherwise it returns adefault
func getenv(key string, adefault string) string {
val, ok := os.LookupEnv(key)
if !ok {
return adefault
}
return val
}
func must(err error, log zerolog.Logger) {
if err != nil {
log.Fatal().Err(err).Msg("")
}
}
func initDB(log zerolog.Logger) *sql.DB {
func InitDB(log zerolog.Logger) *sql.DB {
dbName := "./spring83.db"
// if the db doesn't exist, create it
@ -108,19 +97,16 @@ func initDB(log zerolog.Logger) *sql.DB {
return db
}
func initLog() zerolog.Logger {
logLevel := getenv("LOG_LEVEL", "info")
level, err := zerolog.ParseLevel(logLevel)
if err != nil {
log.Panic().Err(err).Msg("")
}
log := zerolog.New(os.Stderr).With().Timestamp().Logger().Level(level)
if getenv("PRETTY_LOGGING", "") != "" {
log = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
}
return log
// initCleaner starts a goroutine that will delete any expired entries every 5 minutes
func InitCleaner(db *sql.DB, log zerolog.Logger) {
removeExpiredBoards(db, log)
ticker := time.NewTicker(5 * time.Minute)
go func() {
for {
<-ticker.C
removeExpiredBoards(db, log)
}
}()
}
type responseRecorder struct {
@ -133,7 +119,7 @@ func (rec *responseRecorder) WriteHeader(code int) {
rec.ResponseWriter.WriteHeader(code)
}
func requestLogger(log zerolog.Logger, next http.Handler) http.Handler {
func RequestLogger(log zerolog.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rec := &responseRecorder{w, http.StatusOK}
t := time.Now()
@ -154,7 +140,7 @@ func requestLogger(log zerolog.Logger, next http.Handler) http.Handler {
})
}
func initRuntimeValues(log zerolog.Logger) {
func InitRuntimeValues(log zerolog.Logger) {
if bi, ok := debug.ReadBuildInfo(); !ok {
log.Fatal().Msg("Unable to get build info")
} else {
@ -190,46 +176,6 @@ func removeExpiredBoards(db *sql.DB, log zerolog.Logger) {
log.Info().Int64("deleted", rows).Dur("duration", time.Since(t)).Msg("entries cleaned")
}
// initCleaner starts a goroutine that will delete any expired entries every 5 minutes
func initCleaner(db *sql.DB, log zerolog.Logger) {
removeExpiredBoards(db, log)
ticker := time.NewTicker(5 * time.Minute)
go func() {
for {
<-ticker.C
removeExpiredBoards(db, log)
}
}()
}
func main() {
log := initLog()
db := initDB(log)
initRuntimeValues(log)
initCleaner(db, log)
server := newSpring83Server(db, log)
host := getenv("HOST", "")
port := getenv("PORT", "8000")
addr := fmt.Sprintf("%s:%s", host, port)
timeoutMsg := "Request timed out"
srv := &http.Server{
Addr: addr,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
IdleTimeout: 30 * time.Second,
ReadHeaderTimeout: 2 * time.Second,
Handler: requestLogger(log, http.TimeoutHandler(server, 2*time.Second, timeoutMsg)),
}
log.Info().Str("addr", addr).Msg("starting helloserver on")
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Err(err).Msg("Error received from server")
}
}
func readTemplate(name string, fsys fs.FS) (string, error) {
h, err := fs.ReadFile(fsys, name)
if err != nil {
@ -265,7 +211,7 @@ type Spring83Server struct {
homeTemplate *template.Template
}
func newSpring83Server(db *sql.DB, log zerolog.Logger) *Spring83Server {
func NewSpring83Server(db *sql.DB, log zerolog.Logger) *Spring83Server {
return &Spring83Server{
db: db,
log: log,

Loading…
Cancel
Save