From 9c8488de85a321b9c5a8d816034fdde0e0359291 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Mon, 6 Feb 2023 23:31:49 -0500 Subject: [PATCH 01/25] checkpoint --- .gitignore | 24 +- Makefile | 12 + cmd/webapp/handlers.go | 171 ++++++++ cmd/webapp/main.go | 169 ++++++++ {pubs => cmd/webapp/pubs}/argos-sensys10.pdf | Bin .../webapp/pubs}/citysense-ieeehst08.pdf | Bin {pubs => cmd/webapp/pubs}/cobra-nsdi07.pdf | Bin {pubs => cmd/webapp/pubs}/desync-ipsn07.pdf | Bin .../webapp/pubs}/ianrose-dissertation.pdf | Bin {static => cmd/webapp/static}/css/albums.css | 0 {static => cmd/webapp/static}/css/kids.css | 0 {static => cmd/webapp/static}/css/main.css | 0 {static => cmd/webapp/static}/css/strava.css | 0 favicon.ico => cmd/webapp/static/favicon.ico | Bin .../webapp/static}/images/benfranklin.jpg | Bin .../webapp/static}/images/bg/brickwall.png | Bin .../webapp/static}/images/bg/brickwall2.png | Bin .../webapp/static}/images/bg/brushed.png | Bin .../webapp/static}/images/bg/creampaper.png | Bin .../webapp/static}/images/bg/notebook.png | Bin .../webapp/static}/images/bg/notebook2.png | Bin .../webapp/static}/images/bg/old_moon.png | Bin .../webapp/static}/images/bg/sos.png | Bin .../static}/images/bg/squared_metal.png | Bin .../static}/images/bg/textured_paper.png | Bin .../webapp/static}/images/dropbox-icon.png | Bin .../webapp/static}/images/gplus-icon.svg | 0 .../webapp/static}/images/gplus.png | Bin .../webapp/static}/images/harv_grad.jpg | Bin .../webapp/static}/images/linkedin.png | Bin .../webapp/static}/images/me_camping.jpg | Bin .../webapp/static}/images/me_in_wyoming.jpg | Bin .../webapp/static}/images/my_email.jpg | Bin .../static}/images/placeholder-cards.png | Bin .../webapp/static}/images/twitter.png | Bin index.html => cmd/webapp/static/index.html | 0 {static => cmd/webapp/static}/js/pubs.js | 0 .../webapp/talks}/argos-sensys10.ppt | Bin {talks => cmd/webapp/talks}/cobra-nsdi07.ppt | Bin .../talks}/network-monitoring-apr16.ppt | Bin .../webapp/talks}/ssr-update-feb08.odp | Bin go.mod | 27 +- go.sum | 145 +------ internal/secrets.go | 33 ++ internal/storage/db.go | 31 ++ internal/storage/models.go | 17 + internal/storage/query.sql | 7 + internal/storage/query.sql.go | 59 +++ internal/storage/schema.sql | 7 + internal/storage/sqlc.yaml | 7 + internal/storage/storage.go | 34 ++ internal/strava/handlers.go | 154 +++++++ strava.go => internal/strava/strava.go | 192 ++++----- util.go => internal/util.go | 2 +- main.go | 387 ------------------ templates/kidlinks.html | 32 -- 56 files changed, 829 insertions(+), 681 deletions(-) create mode 100644 Makefile create mode 100644 cmd/webapp/handlers.go create mode 100644 cmd/webapp/main.go rename {pubs => cmd/webapp/pubs}/argos-sensys10.pdf (100%) rename {pubs => cmd/webapp/pubs}/citysense-ieeehst08.pdf (100%) rename {pubs => cmd/webapp/pubs}/cobra-nsdi07.pdf (100%) rename {pubs => cmd/webapp/pubs}/desync-ipsn07.pdf (100%) rename {pubs => cmd/webapp/pubs}/ianrose-dissertation.pdf (100%) rename {static => cmd/webapp/static}/css/albums.css (100%) rename {static => cmd/webapp/static}/css/kids.css (100%) rename {static => cmd/webapp/static}/css/main.css (100%) rename {static => cmd/webapp/static}/css/strava.css (100%) rename favicon.ico => cmd/webapp/static/favicon.ico (100%) rename {static => cmd/webapp/static}/images/benfranklin.jpg (100%) rename {static => cmd/webapp/static}/images/bg/brickwall.png (100%) rename {static => cmd/webapp/static}/images/bg/brickwall2.png (100%) rename {static => cmd/webapp/static}/images/bg/brushed.png (100%) rename {static => cmd/webapp/static}/images/bg/creampaper.png (100%) rename {static => cmd/webapp/static}/images/bg/notebook.png (100%) rename {static => cmd/webapp/static}/images/bg/notebook2.png (100%) rename {static => cmd/webapp/static}/images/bg/old_moon.png (100%) rename {static => cmd/webapp/static}/images/bg/sos.png (100%) rename {static => cmd/webapp/static}/images/bg/squared_metal.png (100%) rename {static => cmd/webapp/static}/images/bg/textured_paper.png (100%) rename {static => cmd/webapp/static}/images/dropbox-icon.png (100%) rename {static => cmd/webapp/static}/images/gplus-icon.svg (100%) rename {static => cmd/webapp/static}/images/gplus.png (100%) rename {static => cmd/webapp/static}/images/harv_grad.jpg (100%) rename {static => cmd/webapp/static}/images/linkedin.png (100%) rename {static => cmd/webapp/static}/images/me_camping.jpg (100%) rename {static => cmd/webapp/static}/images/me_in_wyoming.jpg (100%) rename {static => cmd/webapp/static}/images/my_email.jpg (100%) rename {static => cmd/webapp/static}/images/placeholder-cards.png (100%) rename {static => cmd/webapp/static}/images/twitter.png (100%) rename index.html => cmd/webapp/static/index.html (100%) rename {static => cmd/webapp/static}/js/pubs.js (100%) rename {talks => cmd/webapp/talks}/argos-sensys10.ppt (100%) rename {talks => cmd/webapp/talks}/cobra-nsdi07.ppt (100%) rename {talks => cmd/webapp/talks}/network-monitoring-apr16.ppt (100%) rename {talks => cmd/webapp/talks}/ssr-update-feb08.odp (100%) create mode 100644 internal/secrets.go create mode 100644 internal/storage/db.go create mode 100644 internal/storage/models.go create mode 100644 internal/storage/query.sql create mode 100644 internal/storage/query.sql.go create mode 100644 internal/storage/schema.sql create mode 100644 internal/storage/sqlc.yaml create mode 100644 internal/storage/storage.go create mode 100644 internal/strava/handlers.go rename strava.go => internal/strava/strava.go (65%) rename util.go => internal/util.go (99%) delete mode 100644 main.go delete mode 100644 templates/kidlinks.html diff --git a/.gitignore b/.gitignore index 123fd60..fc5ebd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,22 @@ -*.pyc -secrets.go +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Custom stuff from here down: +bin +*.sqlite +.idea +secrets.yaml +var diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b18ade --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +.PHONY: bin/webapp + +bin/webapp: + @mkdir -p bin + go build -o bin/ ./cmd/webapp + +sql: + ./bin/sqlc -f internal/storage/sqlc.yaml generate + +# to set things up initially +init: + GOBIN=`pwd`/bin go install github.com/kyleconroy/sqlc/cmd/sqlc@latest diff --git a/cmd/webapp/handlers.go b/cmd/webapp/handlers.go new file mode 100644 index 0000000..3f263b8 --- /dev/null +++ b/cmd/webapp/handlers.go @@ -0,0 +1,171 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "html/template" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + + "github.com/ianrose14/website/internal" +) + +const ( + AlbumsConfigUrl = "https://www.dropbox.com/s/kr8ewc68husts57/albums.json?dl=1" +) + +var ( + albumsTemplate = template.Must(template.ParseFiles("templates/albums.html")) +) + +type album struct { + Name string `json:"name"` + Url string `json:"url"` + CoverPath string `json:"cover_path"` + CoverUrl string +} + +// albumsHandler serves a page that lists all available photo albums. +func (svr *server) albumsHandler(w http.ResponseWriter, _ *http.Request) { + rsp, err := http.Get(AlbumsConfigUrl) + if err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to fetch albums config from dropbox: %s", err) + return + } + defer internal.DrainAndClose(rsp.Body) + + if err := internal.CheckResponse(rsp); err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to fetch albums config from dropbox: %s", err) + return + } + + var results struct { + Albums []*album `json:"albums"` + } + + if err := json.NewDecoder(rsp.Body).Decode(&results); err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to json-decode response: %s", err) + return + } + + for _, album := range results.Albums { + album.CoverUrl = fmt.Sprintf("/albums/thumbnail?path=%s", url.QueryEscape(album.CoverPath)) + } + + if err := albumsTemplate.Execute(w, results.Albums); err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to render template: %s", err) + return + } +} + +func (svr *server) dumpHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + + writeIt := func(s string, args ...interface{}) { + log.Printf(s, args...) + fmt.Fprintf(w, s+"\n", args...) + } + + writeIt("URL: %s", r.URL) + writeIt("Method: %s", r.Method) + writeIt("Proto: %s", r.Proto) + writeIt("RemoteAddr: %s", r.RemoteAddr) + + var buf bytes.Buffer + buf.WriteString("Headers:\n") + for k, v := range r.Header { + fmt.Fprintf(&buf, "%s: %v\n", k, v) + } + writeIt("%s", buf.String()) + fmt.Fprintln(w, "") // write blank line to response body + + buf.Reset() + buf.WriteString("Cookies:\n") + for _, c := range r.Cookies() { + fmt.Fprintf(&buf, "%s\n", c) + } + writeIt("%s", buf.String()) + fmt.Fprintln(w, "") // write blank line to response body + + b, err := ioutil.ReadAll(r.Body) + if err != nil { + writeIt("error: failed to read body: %s", err) + } else { + buf.Reset() + if _, err := base64.NewEncoder(base64.StdEncoding, &buf).Write(b); err != nil { + writeIt("error: failed to base64-encode body: %s", err) + } else { + writeIt("Body (base64):\n%s\n", buf.String()) + } + fmt.Fprintln(w, "") // write blank line to response body + + writeIt("Body (raw):\n%s", string(b)) + fmt.Fprintln(w, "") // write blank line to response body + } +} + +func (svr *server) thumbnailHandler(w http.ResponseWriter, r *http.Request) { + path := r.URL.Query().Get("path") + if path == "" { + internal.HttpError(w, http.StatusBadRequest, "no path query") + return + } + + if !strings.HasPrefix(path, "/photos/") { + internal.HttpError(w, http.StatusBadRequest, "rejecting forbidden path %s", path) + return + } + + params := struct { + Path string `json:"path"` + Format string `json:"format"` + Size string `json:"size"` + }{ + Path: path, + Format: "jpeg", + Size: "w640h480", + } + + jstr, err := json.Marshal(¶ms) + if err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to json-encode params: %s", err) + return + } + + qs := make(url.Values) + qs.Add("arg", string(jstr)) + + urls := "https://api-content.dropbox.com/2/files/get_thumbnail?" + qs.Encode() + log.Printf("fetching %s", urls) + + req, err := http.NewRequest("GET", urls, nil) + if err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to make new http request") + return + } + + req.Header.Set("Authorization", "Bearer "+svr.secrets.Dropbox.AccessToken) + + rsp, err := http.DefaultClient.Do(req) + if err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to fetch thumbnail from dropbox: %s", err) + return + } + defer internal.DrainAndClose(rsp.Body) + + if err := internal.CheckResponse(rsp); err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to fetch thumbnail from dropbox: %s", err) + return + } + + w.Header().Set("Content-Type", rsp.Header.Get("Content-Type")) + if _, err := io.Copy(w, rsp.Body); err != nil { + log.Printf("failed to copy thumbnail body to response stream: %s", err) + } +} diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go new file mode 100644 index 0000000..4a970d5 --- /dev/null +++ b/cmd/webapp/main.go @@ -0,0 +1,169 @@ +package main + +import ( + "context" + "database/sql" + "embed" + _ "embed" + "flag" + "html/template" + "io/fs" + "log" + "net/http" + "os" + "runtime" + "time" + + "github.com/ianrose14/website/internal" + "github.com/ianrose14/website/internal/storage" + "github.com/ianrose14/website/internal/strava" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/crypto/acme/autocert" +) + +var ( + //go:embed static/* + staticFS embed.FS + + //go:embed pubs/* + publicationsFS embed.FS + + //go:embed talks/* + talksFS embed.FS + + //stravaVars = &internal.MemoryDatabase{vals: make(map[string]*internal.StravaTokens)} + stravaTemplate = template.Must(template.ParseFiles("templates/strava.html")) +) + +func init() { + log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile) +} + +func main() { + certsDir := flag.String("certs", "certs", "directory to store letsencrypt certs") + dbfile := flag.String("db", "solarsnoop.sqlite", "sqlite database file") + host := flag.String("host", "", "optional hostname for webserver") + secretsFile := flag.String("secrets", "config/secrets.yaml", "Path to local secrets file") + flag.Parse() + + ctx := context.Background() + + secrets, err := internal.ParseSecrets(*secretsFile) + if err != nil { + log.Fatalf("failed to parse secrets: %s", err) + } + + db, err := sql.Open("sqlite3", "file:"+*dbfile+"?cache=shared") + if err != nil { + log.Fatalf("failed to open sqlite connection: %s", err) + } + defer func() { + if err := db.Close(); err != nil { + log.Printf("warning: failed to cleanly close database: %s", err) + } + }() + + if err := storage.UpsertDatabaseTables(ctx, db); err != nil { + log.Fatalf("failed to upsert database tables: %s", err) + } + + svr := &server{ + db: db, + secrets: secrets, + host: *host, + } + + stravaAccount := &strava.ApiParams{ + ClientId: secrets.Strava.ClientID, + ClientSecret: secrets.Strava.ClientSecret, + } + + httpFS := func(files embed.FS, subdir string) http.Handler { + d, err := fs.Sub(files, subdir) + if err != nil { + log.Fatalf("static file config error: %+v", err) + } + return http.FileServer(http.FS(d)) + } + + mux := http.NewServeMux() + mux.Handle("/", httpFS(staticFS, "static")) + mux.Handle("/pubs/", http.FileServer(http.FS(publicationsFS))) + mux.Handle("/talks/", http.FileServer(http.FS(talksFS))) + + mux.HandleFunc("/albums/", svr.albumsHandler) + mux.HandleFunc("/albums/thumbnail/", svr.thumbnailHandler) + mux.HandleFunc("/dump/", svr.dumpHandler) + + stravaDb := strava.NewSqliteDb(db) + h := func(w http.ResponseWriter, r *http.Request) { + strava.Handler(w, r, stravaTemplate, stravaDb, stravaAccount) + } + mux.HandleFunc("/running/", h) + mux.HandleFunc("/strava/", h) + http.HandleFunc("/strava/exchange_token/", func(w http.ResponseWriter, r *http.Request) { + strava.TokenHandler(w, r, stravaDb, stravaAccount) + }) + + mux.Handle("/favicon.ico", httpFS(staticFS, "static")) + + var httpHandler http.Handler = mux + + // TODO: in a handler wrapper, redirect http to https (in production only) + + const inDev = runtime.GOOS == "darwin" + + // when testing locally it doesn't make sense to start + // HTTPS server, so only do it in production. + // In real code, I control this with -production cmd-line flag + if !inDev { + if err := os.MkdirAll(*certsDir, 0777); err != nil { + log.Fatalf("failed to create certs dir: %s", err) + } + + httpsSrv := makeHTTPServer(mux) + certManager := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + Email: "ianrose14+autocert@gmail.com", // NOSUBMIT - replace with alias? + HostPolicy: func(ctx context.Context, host string) error { + log.Printf("autocert query for host %q", host) + return autocert.HostWhitelist("solarsnoop.com", "www.solarsnoop.com")(ctx, host) + }, + Cache: autocert.DirCache(*certsDir), + } + httpsSrv.Addr = ":https" + httpsSrv.TLSConfig = certManager.TLSConfig() + + httpHandler = certManager.HTTPHandler(mux) + + go func() { + log.Printf("listening on port 443") + + err := httpsSrv.ListenAndServeTLS("", "") + if err != nil { + log.Fatalf("httpsSrv.ListendAndServeTLS() failed: %s", err) + } + }() + } + + log.Printf("listening on port 80") + if err := http.ListenAndServe(":http", httpHandler); err != nil { + log.Fatalf("http.ListenAndServe failed: %s", err) + } +} + +func makeHTTPServer(mux *http.ServeMux) *http.Server { + // set timeouts so that a slow or malicious client can't hold resources forever + return &http.Server{ + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + IdleTimeout: 120 * time.Second, + Handler: mux, + } +} + +type server struct { + db *sql.DB + secrets *internal.SecretsFile + host string +} diff --git a/pubs/argos-sensys10.pdf b/cmd/webapp/pubs/argos-sensys10.pdf similarity index 100% rename from pubs/argos-sensys10.pdf rename to cmd/webapp/pubs/argos-sensys10.pdf diff --git a/pubs/citysense-ieeehst08.pdf b/cmd/webapp/pubs/citysense-ieeehst08.pdf similarity index 100% rename from pubs/citysense-ieeehst08.pdf rename to cmd/webapp/pubs/citysense-ieeehst08.pdf diff --git a/pubs/cobra-nsdi07.pdf b/cmd/webapp/pubs/cobra-nsdi07.pdf similarity index 100% rename from pubs/cobra-nsdi07.pdf rename to cmd/webapp/pubs/cobra-nsdi07.pdf diff --git a/pubs/desync-ipsn07.pdf b/cmd/webapp/pubs/desync-ipsn07.pdf similarity index 100% rename from pubs/desync-ipsn07.pdf rename to cmd/webapp/pubs/desync-ipsn07.pdf diff --git a/pubs/ianrose-dissertation.pdf b/cmd/webapp/pubs/ianrose-dissertation.pdf similarity index 100% rename from pubs/ianrose-dissertation.pdf rename to cmd/webapp/pubs/ianrose-dissertation.pdf diff --git a/static/css/albums.css b/cmd/webapp/static/css/albums.css similarity index 100% rename from static/css/albums.css rename to cmd/webapp/static/css/albums.css diff --git a/static/css/kids.css b/cmd/webapp/static/css/kids.css similarity index 100% rename from static/css/kids.css rename to cmd/webapp/static/css/kids.css diff --git a/static/css/main.css b/cmd/webapp/static/css/main.css similarity index 100% rename from static/css/main.css rename to cmd/webapp/static/css/main.css diff --git a/static/css/strava.css b/cmd/webapp/static/css/strava.css similarity index 100% rename from static/css/strava.css rename to cmd/webapp/static/css/strava.css diff --git a/favicon.ico b/cmd/webapp/static/favicon.ico similarity index 100% rename from favicon.ico rename to cmd/webapp/static/favicon.ico diff --git a/static/images/benfranklin.jpg b/cmd/webapp/static/images/benfranklin.jpg similarity index 100% rename from static/images/benfranklin.jpg rename to cmd/webapp/static/images/benfranklin.jpg diff --git a/static/images/bg/brickwall.png b/cmd/webapp/static/images/bg/brickwall.png similarity index 100% rename from static/images/bg/brickwall.png rename to cmd/webapp/static/images/bg/brickwall.png diff --git a/static/images/bg/brickwall2.png b/cmd/webapp/static/images/bg/brickwall2.png similarity index 100% rename from static/images/bg/brickwall2.png rename to cmd/webapp/static/images/bg/brickwall2.png diff --git a/static/images/bg/brushed.png b/cmd/webapp/static/images/bg/brushed.png similarity index 100% rename from static/images/bg/brushed.png rename to cmd/webapp/static/images/bg/brushed.png diff --git a/static/images/bg/creampaper.png b/cmd/webapp/static/images/bg/creampaper.png similarity index 100% rename from static/images/bg/creampaper.png rename to cmd/webapp/static/images/bg/creampaper.png diff --git a/static/images/bg/notebook.png b/cmd/webapp/static/images/bg/notebook.png similarity index 100% rename from static/images/bg/notebook.png rename to cmd/webapp/static/images/bg/notebook.png diff --git a/static/images/bg/notebook2.png b/cmd/webapp/static/images/bg/notebook2.png similarity index 100% rename from static/images/bg/notebook2.png rename to cmd/webapp/static/images/bg/notebook2.png diff --git a/static/images/bg/old_moon.png b/cmd/webapp/static/images/bg/old_moon.png similarity index 100% rename from static/images/bg/old_moon.png rename to cmd/webapp/static/images/bg/old_moon.png diff --git a/static/images/bg/sos.png b/cmd/webapp/static/images/bg/sos.png similarity index 100% rename from static/images/bg/sos.png rename to cmd/webapp/static/images/bg/sos.png diff --git a/static/images/bg/squared_metal.png b/cmd/webapp/static/images/bg/squared_metal.png similarity index 100% rename from static/images/bg/squared_metal.png rename to cmd/webapp/static/images/bg/squared_metal.png diff --git a/static/images/bg/textured_paper.png b/cmd/webapp/static/images/bg/textured_paper.png similarity index 100% rename from static/images/bg/textured_paper.png rename to cmd/webapp/static/images/bg/textured_paper.png diff --git a/static/images/dropbox-icon.png b/cmd/webapp/static/images/dropbox-icon.png similarity index 100% rename from static/images/dropbox-icon.png rename to cmd/webapp/static/images/dropbox-icon.png diff --git a/static/images/gplus-icon.svg b/cmd/webapp/static/images/gplus-icon.svg similarity index 100% rename from static/images/gplus-icon.svg rename to cmd/webapp/static/images/gplus-icon.svg diff --git a/static/images/gplus.png b/cmd/webapp/static/images/gplus.png similarity index 100% rename from static/images/gplus.png rename to cmd/webapp/static/images/gplus.png diff --git a/static/images/harv_grad.jpg b/cmd/webapp/static/images/harv_grad.jpg similarity index 100% rename from static/images/harv_grad.jpg rename to cmd/webapp/static/images/harv_grad.jpg diff --git a/static/images/linkedin.png b/cmd/webapp/static/images/linkedin.png similarity index 100% rename from static/images/linkedin.png rename to cmd/webapp/static/images/linkedin.png diff --git a/static/images/me_camping.jpg b/cmd/webapp/static/images/me_camping.jpg similarity index 100% rename from static/images/me_camping.jpg rename to cmd/webapp/static/images/me_camping.jpg diff --git a/static/images/me_in_wyoming.jpg b/cmd/webapp/static/images/me_in_wyoming.jpg similarity index 100% rename from static/images/me_in_wyoming.jpg rename to cmd/webapp/static/images/me_in_wyoming.jpg diff --git a/static/images/my_email.jpg b/cmd/webapp/static/images/my_email.jpg similarity index 100% rename from static/images/my_email.jpg rename to cmd/webapp/static/images/my_email.jpg diff --git a/static/images/placeholder-cards.png b/cmd/webapp/static/images/placeholder-cards.png similarity index 100% rename from static/images/placeholder-cards.png rename to cmd/webapp/static/images/placeholder-cards.png diff --git a/static/images/twitter.png b/cmd/webapp/static/images/twitter.png similarity index 100% rename from static/images/twitter.png rename to cmd/webapp/static/images/twitter.png diff --git a/index.html b/cmd/webapp/static/index.html similarity index 100% rename from index.html rename to cmd/webapp/static/index.html diff --git a/static/js/pubs.js b/cmd/webapp/static/js/pubs.js similarity index 100% rename from static/js/pubs.js rename to cmd/webapp/static/js/pubs.js diff --git a/talks/argos-sensys10.ppt b/cmd/webapp/talks/argos-sensys10.ppt similarity index 100% rename from talks/argos-sensys10.ppt rename to cmd/webapp/talks/argos-sensys10.ppt diff --git a/talks/cobra-nsdi07.ppt b/cmd/webapp/talks/cobra-nsdi07.ppt similarity index 100% rename from talks/cobra-nsdi07.ppt rename to cmd/webapp/talks/cobra-nsdi07.ppt diff --git a/talks/network-monitoring-apr16.ppt b/cmd/webapp/talks/network-monitoring-apr16.ppt similarity index 100% rename from talks/network-monitoring-apr16.ppt rename to cmd/webapp/talks/network-monitoring-apr16.ppt diff --git a/talks/ssr-update-feb08.odp b/cmd/webapp/talks/ssr-update-feb08.odp similarity index 100% rename from talks/ssr-update-feb08.odp rename to cmd/webapp/talks/ssr-update-feb08.odp diff --git a/go.mod b/go.mod index 4f9387d..98453f2 100644 --- a/go.mod +++ b/go.mod @@ -2,26 +2,13 @@ module github.com/ianrose14/website go 1.18 -require cloud.google.com/go/datastore v1.10.0 +require ( + github.com/mattn/go-sqlite3 v1.14.16 + golang.org/x/crypto v0.5.0 + gopkg.in/yaml.v3 v3.0.1 +) require ( - cloud.google.com/go v0.108.0 // indirect - cloud.google.com/go/compute v1.12.1 // indirect - cloud.google.com/go/compute/metadata v0.2.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect - go.opencensus.io v0.24.0 // indirect - golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect - golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/text v0.4.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.103.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3 // indirect - google.golang.org/grpc v1.51.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + golang.org/x/net v0.5.0 // indirect + golang.org/x/text v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index 3fb901f..5e93f84 100644 --- a/go.sum +++ b/go.sum @@ -1,137 +1,12 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.108.0 h1:xntQwnfn8oHGX0crLVinvHM+AhXvi3QHQIEcX/2hiWk= -cloud.google.com/go v0.108.0/go.mod h1:lNUfQqusBJp0bgAg6qrHgYFYbTB+dOiob1itwnlD33Q= -cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute/metadata v0.2.2 h1:aWKAjYaBaOSrpKl57+jnS/3fJRQnxL7TvR/u1VVbt6k= -cloud.google.com/go/compute/metadata v0.2.2/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/datastore v1.10.0 h1:4siQRf4zTiAVt/oeH4GureGkApgb2vtPQAtOmhpqQwE= -cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3 h1:BCcW+lhENGqZ2R2MsM9oty220E8vY9E4QC1Tq05hN1E= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/secrets.go b/internal/secrets.go new file mode 100644 index 0000000..0d10c55 --- /dev/null +++ b/internal/secrets.go @@ -0,0 +1,33 @@ +package internal + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +type SecretsFile struct { + Dropbox struct { + AccessToken string `yaml:"accessToken"` + } + Strava struct { + ClientID string `yaml:"clientId"` + ClientSecret string `yaml:"clientSecret"` + } +} + +func ParseSecrets(filename string) (*SecretsFile, error) { + fp, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + defer fp.Close() + + var dst SecretsFile + if err := yaml.NewDecoder(fp).Decode(&dst); err != nil { + return nil, fmt.Errorf("failed to parse file: %w", err) + } + + return &dst, nil +} diff --git a/internal/storage/db.go b/internal/storage/db.go new file mode 100644 index 0000000..e0565ca --- /dev/null +++ b/internal/storage/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package storage + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/storage/models.go b/internal/storage/models.go new file mode 100644 index 0000000..8b6d28b --- /dev/null +++ b/internal/storage/models.go @@ -0,0 +1,17 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package storage + +import ( + "time" +) + +type StravaToken struct { + Username string + AccessToken string + RefreshToken string + CreatedTime time.Time + ExpiresAt time.Time +} diff --git a/internal/storage/query.sql b/internal/storage/query.sql new file mode 100644 index 0000000..758cea9 --- /dev/null +++ b/internal/storage/query.sql @@ -0,0 +1,7 @@ +-- name: InsertStravaTokens :exec +INSERT OR REPLACE INTO strava_tokens(username, access_token, refresh_token, created_time, expires_at) VALUES (?,?,?,?,?); + +-- name: FetchStravaTokens :one +SELECT access_token, refresh_token, created_time, expires_at + FROM strava_tokens + WHERE username=?; diff --git a/internal/storage/query.sql.go b/internal/storage/query.sql.go new file mode 100644 index 0000000..cb225d2 --- /dev/null +++ b/internal/storage/query.sql.go @@ -0,0 +1,59 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 +// source: query.sql + +package storage + +import ( + "context" + "time" +) + +const fetchStravaTokens = `-- name: FetchStravaTokens :one +SELECT access_token, refresh_token, created_time, expires_at + FROM strava_tokens + WHERE username=? +` + +type FetchStravaTokensRow struct { + AccessToken string + RefreshToken string + CreatedTime time.Time + ExpiresAt time.Time +} + +func (q *Queries) FetchStravaTokens(ctx context.Context, username string) (FetchStravaTokensRow, error) { + row := q.db.QueryRowContext(ctx, fetchStravaTokens, username) + var i FetchStravaTokensRow + err := row.Scan( + &i.AccessToken, + &i.RefreshToken, + &i.CreatedTime, + &i.ExpiresAt, + ) + return i, err +} + +const insertStravaTokens = `-- name: InsertStravaTokens :exec +INSERT OR REPLACE INTO strava_tokens(username, access_token, refresh_token, created_time, expires_at) VALUES (?,?,?,?,?) +` + +type InsertStravaTokensParams struct { + Username string + AccessToken string + RefreshToken string + CreatedTime time.Time + ExpiresAt time.Time +} + +func (q *Queries) InsertStravaTokens(ctx context.Context, arg InsertStravaTokensParams) error { + _, err := q.db.ExecContext(ctx, insertStravaTokens, + arg.Username, + arg.AccessToken, + arg.RefreshToken, + arg.CreatedTime, + arg.ExpiresAt, + ) + return err +} diff --git a/internal/storage/schema.sql b/internal/storage/schema.sql new file mode 100644 index 0000000..840a669 --- /dev/null +++ b/internal/storage/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS strava_tokens ( + username TEXT NOT NULL PRIMARY KEY, + access_token TEXT NOT NULL, + refresh_token TEXT NOT NULL, + created_time DATE NOT NULL, + expires_at DATE NOT NULL +); diff --git a/internal/storage/sqlc.yaml b/internal/storage/sqlc.yaml new file mode 100644 index 0000000..6028ce0 --- /dev/null +++ b/internal/storage/sqlc.yaml @@ -0,0 +1,7 @@ +version: 1 +packages: + - path: "." + name: "storage" + engine: "sqlite" + schema: "schema.sql" + queries: "query.sql" diff --git a/internal/storage/storage.go b/internal/storage/storage.go new file mode 100644 index 0000000..e1ba81c --- /dev/null +++ b/internal/storage/storage.go @@ -0,0 +1,34 @@ +package storage + +import ( + "context" + "database/sql" + _ "embed" + "fmt" +) + +var ( + //go:embed schema.sql + schema string +) + +func Str(s string) sql.NullString { + if s != "" { + return sql.NullString{String: s, Valid: true} + } + return sql.NullString{} +} + +func UpsertDatabaseTables(ctx context.Context, db *sql.DB) error { + conn, err := db.Conn(ctx) + if err != nil { + return fmt.Errorf("failed to get database connection: %w", err) + } + defer conn.Close() + + _, err = conn.ExecContext(ctx, schema) + if err != nil { + return fmt.Errorf("failed to create schema: %w", err) + } + return nil +} diff --git a/internal/strava/handlers.go b/internal/strava/handlers.go new file mode 100644 index 0000000..ecd856a --- /dev/null +++ b/internal/strava/handlers.go @@ -0,0 +1,154 @@ +package strava + +import ( + "fmt" + "html/template" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/ianrose14/website/internal" + "github.com/ianrose14/website/internal/storage" +) + +func Handler(w http.ResponseWriter, r *http.Request, tmpl *template.Template, db KVDB, account *ApiParams) { + year := time.Now().Year() + if s := r.URL.Query().Get("year"); s != "" { + if i, err := strconv.Atoi(s); err == nil { + year = i + } + } + + username := r.URL.Query().Get("username") + if username == "" { + c, err := r.Cookie("username") + if err == nil { + username = c.Value + } + } + + if username == "" { + http.Redirect(w, r, getAuthUrl(account), http.StatusTemporaryRedirect) + return + } + + accessToken, err := readAccessToken(r.Context(), username, db, account) + if err != nil { + if err == ErrNeedsAuth { + http.Redirect(w, r, getAuthUrl(account), http.StatusTemporaryRedirect) + return + } + + http.Error(w, fmt.Sprintf("failed to read access token: %s", err), http.StatusInternalServerError) + return + } + + profile, err := getProfile(accessToken) + if err != nil { + http.Error(w, fmt.Sprintf("failed to get profile info: %s", err), http.StatusInternalServerError) + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "username", + Value: username, + Expires: time.Now().Add(7 * 24 * time.Hour), + }) + + now := time.Now() + goalMiles := defaultGoalMiles[year] + queryStart := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC) + + var scaledGoalMiles float64 + var queryEnd time.Time + + if now.Year() == year { + scaledGoalMiles = float64(goalMiles) * float64(now.YearDay()) / 365 + queryEnd = queryStart.AddDate(0, 0, now.YearDay()) // finish is intentionally midnight at the END of the day + } else { + scaledGoalMiles = float64(goalMiles) + queryEnd = time.Date(year+1, time.January, 1, 0, 0, 0, 0, now.Location()) // Midnight, start of new years day + } + + activities, err := doStravaQuery(r.Context(), username, queryStart, queryEnd, db, account) + if err != nil { + http.Error(w, fmt.Sprintf("failed to query strava: %s", err), http.StatusInternalServerError) + return + } + + args := struct { + Username string + Activities []string + MilesTotal string + MilesYearGoal int + MilesScaledGoal string + Progress string + GaugeRotate int + }{ + Username: profile.Username, + MilesYearGoal: goalMiles, + MilesScaledGoal: fmt.Sprintf("%.1f", scaledGoalMiles), + } + + var sumMiles float64 + for _, activity := range activities { + if activity.Type != "Run" { + continue + } + secondsPerMile := int64(activity.MovingTime/activity.Miles() + 0.5000001) + args.Activities = append(args.Activities, + fmt.Sprintf("%s: %.1fK (%.1f miles) in %s (%d:%02d pace) on %s", activity.Name, + activity.DistanceMeters/1000., activity.Miles(), + formatSeconds(activity.MovingTime), secondsPerMile/60, secondsPerMile%60, + activity.StartDate)) + + sumMiles += activity.Miles() + } + + progress := 100 * sumMiles / scaledGoalMiles + + args.MilesTotal = fmt.Sprintf("%.1f", sumMiles) + args.Progress = fmt.Sprintf("%.0f", progress) + args.GaugeRotate = int(90.0 * progress / 100) + + if err := tmpl.Execute(w, &args); err != nil { + internal.HttpError(w, http.StatusInternalServerError, "failed to render template: %s", err) + return + } +} + +func TokenHandler(w http.ResponseWriter, r *http.Request, db KVDB, account *ApiParams) { + code := r.URL.Query().Get("code") + if code == "" { + return + } + + rsp, err := exchangeToken(code, account) + if err != nil { + http.Error(w, fmt.Sprintf("failure in token exchange: %s", err), http.StatusInternalServerError) + return + } + + arg := storage.InsertStravaTokensParams{ + AccessToken: rsp.AccessToken, + RefreshToken: rsp.RefreshToken, + CreatedTime: time.Now(), + ExpiresAt: time.Unix(rsp.ExpiresAt, 0), + } + + profile, err := getProfile(rsp.AccessToken) + if err != nil { + http.Error(w, fmt.Sprintf("failed to get profile info: %s", err), http.StatusInternalServerError) + return + } + + arg.Username = profile.Username + + if err := db.Write(r.Context(), &arg); err != nil { + http.Error(w, fmt.Sprintf("failure in write tokens to db: %s", err), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/running/?username="+url.QueryEscape(profile.Username)+"&year="+strconv.Itoa(time.Now().Year()), http.StatusTemporaryRedirect) +} diff --git a/strava.go b/internal/strava/strava.go similarity index 65% rename from strava.go rename to internal/strava/strava.go index 8ea9966..d08c2cb 100644 --- a/strava.go +++ b/internal/strava/strava.go @@ -1,8 +1,8 @@ -package main +package strava import ( - "bufio" "context" + "database/sql" "encoding/json" "errors" "fmt" @@ -10,16 +10,11 @@ import ( "log" "net/http" "net/url" - "os" - "strings" "sync" "time" - "cloud.google.com/go/datastore" -) - -const ( - StravaClientId = "59096" + "github.com/ianrose14/website/internal" + "github.com/ianrose14/website/internal/storage" ) var ( @@ -34,23 +29,23 @@ var ( } ) -type StravaTokens struct { - AccessToken string - ExpiresAt time.Time - RefreshToken string +type ApiParams struct { + ClientId string + ClientSecret string + Hostname string } type KVDB interface { - Read(ctx context.Context, key string) (*StravaTokens, error) - Write(ctx context.Context, key string, tokens *StravaTokens) error + Read(ctx context.Context, username string) (*storage.FetchStravaTokensRow, error) + Write(ctx context.Context, tokens *storage.InsertStravaTokensParams) error } type FileDatabase struct { filepath string } -func (db *FileDatabase) Read(ctx context.Context, key string) (*StravaTokens, error) { - if !FileExists(db.filepath) { +func (db *FileDatabase) Read(_ context.Context, key string) (*storage.FetchStravaTokensRow, error) { + if !internal.FileExists(db.filepath) { return nil, nil } @@ -59,7 +54,7 @@ func (db *FileDatabase) Read(ctx context.Context, key string) (*StravaTokens, er return nil, err } - m := make(map[string]*StravaTokens) + m := make(map[string]*storage.FetchStravaTokensRow) if err := json.Unmarshal(contents, &m); err != nil { return nil, err } @@ -67,10 +62,10 @@ func (db *FileDatabase) Read(ctx context.Context, key string) (*StravaTokens, er return m[key], nil } -func (db *FileDatabase) Write(ctx context.Context, key string, tokens *StravaTokens) error { - m := make(map[string]*StravaTokens) +func (db *FileDatabase) Write(_ context.Context, key string, tokens *storage.FetchStravaTokensRow) error { + m := make(map[string]*storage.FetchStravaTokensRow) - if FileExists(db.filepath) { + if internal.FileExists(db.filepath) { contents, err := ioutil.ReadFile(db.filepath) if err != nil { return err @@ -91,43 +86,50 @@ func (db *FileDatabase) Write(ctx context.Context, key string, tokens *StravaTok } type MemoryDatabase struct { - vals map[string]*StravaTokens + vals map[string]*storage.FetchStravaTokensRow mu sync.Mutex } -func (db *MemoryDatabase) Read(ctx context.Context, key string) (*StravaTokens, error) { +func (db *MemoryDatabase) Read(_ context.Context, username string) (*storage.FetchStravaTokensRow, error) { db.mu.Lock() defer db.mu.Unlock() - return db.vals[key], nil + return db.vals[username], nil } -func (db *MemoryDatabase) Write(ctx context.Context, key string, tokens *StravaTokens) error { +func (db *MemoryDatabase) Write(_ context.Context, tokens *storage.InsertStravaTokensParams) error { db.mu.Lock() defer db.mu.Unlock() - db.vals[key] = tokens + db.vals[tokens.Username] = &storage.FetchStravaTokensRow{ + AccessToken: tokens.AccessToken, + RefreshToken: tokens.RefreshToken, + CreatedTime: time.Now(), + ExpiresAt: tokens.ExpiresAt, + } return nil } -type DatastoreDb struct { - client *datastore.Client +type SqliteDb struct { + query *storage.Queries +} + +func NewSqliteDb(db *sql.DB) KVDB { + return &SqliteDb{query: storage.New(db)} } -func (db *DatastoreDb) Read(ctx context.Context, key string) (*StravaTokens, error) { - k := datastore.NameKey("StravaTokens", key, nil) - var tokens StravaTokens - if err := db.client.Get(ctx, k, &tokens); err != nil { - if err == datastore.ErrNoSuchEntity { +func (db *SqliteDb) Read(ctx context.Context, username string) (*storage.FetchStravaTokensRow, error) { + row, err := db.query.FetchStravaTokens(ctx, username) + if err != nil { + if err == sql.ErrNoRows { return nil, nil } return nil, err } - return &tokens, nil + + return &row, nil } -func (db *DatastoreDb) Write(ctx context.Context, key string, tokens *StravaTokens) error { - k := datastore.NameKey("StravaTokens", key, nil) - _, err := db.client.Put(ctx, k, tokens) - return err +func (db *SqliteDb) Write(ctx context.Context, tokens *storage.InsertStravaTokensParams) error { + return db.query.InsertStravaTokens(ctx, *tokens) } type Activity struct { @@ -154,8 +156,8 @@ type AuthResponse struct { AccessToken string `json:"access_token"` } -func doStravaQuery(ctx context.Context, sessionId string, start, finish time.Time, db KVDB) ([]Activity, error) { - accessToken, err := readAccessToken(ctx, sessionId, db) +func doStravaQuery(ctx context.Context, sessionId string, start, finish time.Time, db KVDB, account *ApiParams) ([]Activity, error) { + accessToken, err := readAccessToken(ctx, sessionId, db, account) if err != nil { return nil, fmt.Errorf("failed to read access token: %s", err) } @@ -163,7 +165,7 @@ func doStravaQuery(ctx context.Context, sessionId string, start, finish time.Tim return getActivities(accessToken, start, finish) } -func readAccessToken(ctx context.Context, username string, db KVDB) (string, error) { +func readAccessToken(ctx context.Context, username string, db KVDB, account *ApiParams) (string, error) { // read most recent refresh token tokens, err := db.Read(ctx, username) if err != nil { @@ -174,8 +176,8 @@ func readAccessToken(ctx context.Context, username string, db KVDB) (string, err } vals := make(url.Values) - vals.Set("client_id", StravaClientId) - vals.Set("client_secret", StravaClientSecret) + vals.Set("client_id", account.ClientId) + vals.Set("client_secret", account.ClientSecret) vals.Set("grant_type", "refresh_token") vals.Set("refresh_token", tokens.RefreshToken) @@ -183,7 +185,7 @@ func readAccessToken(ctx context.Context, username string, db KVDB) (string, err if err != nil { return "", fmt.Errorf("failed to post: %s", err) } - if err := CheckResponse(rsp); err != nil { + if err := internal.CheckResponse(rsp); err != nil { return "", fmt.Errorf("failed to post: %s", err) } @@ -198,25 +200,59 @@ func readAccessToken(ctx context.Context, username string, db KVDB) (string, err if err := json.NewDecoder(rsp.Body).Decode(&update); err != nil { return "", fmt.Errorf("failed to parse response: %s", err) } - DrainAndClose(rsp.Body) + internal.DrainAndClose(rsp.Body) if update.TokenType != "Bearer" { return "", fmt.Errorf("unexpected returned TokenType: %q", update.TokenType) } - tokens = &StravaTokens{ + arg := storage.InsertStravaTokensParams{ + Username: username, AccessToken: update.AccessToken, - ExpiresAt: time.Unix(update.ExpiresAt, 0), RefreshToken: update.RefreshToken, + CreatedTime: time.Now(), + ExpiresAt: time.Unix(update.ExpiresAt, 0), } - if err := db.Write(ctx, username, tokens); err != nil { + if err := db.Write(ctx, &arg); err != nil { return "", fmt.Errorf("failed to write tokens to db: %s", err) } return update.AccessToken, nil } +func exchangeToken(code string, account *ApiParams) (*AuthResponse, error) { + vals := make(url.Values) + vals.Set("client_id", account.ClientId) + vals.Set("client_secret", account.ClientSecret) + vals.Set("code", code) + vals.Set("grant_type", "authorization_code") + + rsp, err := http.DefaultClient.PostForm("https://www.strava.com/oauth/token", vals) + if err != nil { + return nil, fmt.Errorf("failed to post: %s", err) + } + if err := internal.CheckResponse(rsp); err != nil { + return nil, fmt.Errorf("failed to post: %s", err) + } + + var authResp AuthResponse + if err := json.NewDecoder(rsp.Body).Decode(&authResp); err != nil { + return nil, err + } + + return &authResp, nil +} + +func formatSeconds(s float64) string { + minutes := int(s / 60.) + return fmt.Sprintf("%d:%02.0f", minutes, s-float64(60*minutes)) +} + +func getAuthUrl(account *ApiParams) string { + return fmt.Sprintf("https://www.strava.com/oauth/authorize?client_id=" + account.ClientId + "&response_type=code&redirect_uri=https://" + account.Hostname + "/strava/exchange_token/&approval_prompt=force&scope=activity:read_all") +} + func getActivities(accessToken string, start, finish time.Time) ([]Activity, error) { urls := fmt.Sprintf("https://www.strava.com/api/v3/athlete/activities?per_page=200&before=%d&after=%d", finish.Unix(), start.Unix()) req, err := http.NewRequest("GET", urls, nil) @@ -229,9 +265,9 @@ func getActivities(accessToken string, start, finish time.Time) ([]Activity, err if err != nil { return nil, fmt.Errorf("failed to get: %s", err) } - defer DrainAndClose(rsp.Body) + defer internal.DrainAndClose(rsp.Body) - if err := CheckResponse(rsp); err != nil { + if err := internal.CheckResponse(rsp); err != nil { return nil, fmt.Errorf("failed to get: %s", err) } @@ -255,9 +291,9 @@ func getProfile(accessToken string) (*ProfileInfo, error) { if err != nil { return nil, fmt.Errorf("failed to get: %s", err) } - defer DrainAndClose(rsp.Body) + defer internal.DrainAndClose(rsp.Body) - if err := CheckResponse(rsp); err != nil { + if err := internal.CheckResponse(rsp); err != nil { return nil, fmt.Errorf("failed to get: %s", err) } @@ -268,55 +304,3 @@ func getProfile(accessToken string) (*ProfileInfo, error) { return &profile, nil } - -func formatSeconds(s float64) string { - minutes := int(s / 60.) - return fmt.Sprintf("%d:%02.0f", minutes, s-float64(60*minutes)) -} - -func StravaAuthUrl(hostname string) string { - return fmt.Sprintf("https://www.strava.com/oauth/authorize?client_id=" + StravaClientId + "&response_type=code&redirect_uri=https://" + hostname + "/strava/exchange_token/&approval_prompt=force&scope=activity:read_all") -} - -func doAuthFlow() error { - fmt.Println("Visit: " + StravaAuthUrl("localhost")) - - fmt.Println("") - fmt.Printf("Enter code from redirect URL: ") - scanner := bufio.NewScanner(os.Stdin) - if !scanner.Scan() { - return errors.New("cancelled") - } - code := strings.TrimSpace(scanner.Text()) - - rsp, err := StravaExchangeToken(code) - if err != nil { - return err - } - - fmt.Printf("%+v\n", rsp) - return nil -} - -func StravaExchangeToken(code string) (*AuthResponse, error) { - vals := make(url.Values) - vals.Set("client_id", StravaClientId) - vals.Set("client_secret", StravaClientSecret) - vals.Set("code", code) - vals.Set("grant_type", "authorization_code") - - rsp, err := http.DefaultClient.PostForm("https://www.strava.com/oauth/token", vals) - if err != nil { - return nil, fmt.Errorf("failed to post: %s", err) - } - if err := CheckResponse(rsp); err != nil { - return nil, fmt.Errorf("failed to post: %s", err) - } - - var authResp AuthResponse - if err := json.NewDecoder(rsp.Body).Decode(&authResp); err != nil { - return nil, err - } - - return &authResp, nil -} diff --git a/util.go b/internal/util.go similarity index 99% rename from util.go rename to internal/util.go index 27304be..b0a18ba 100644 --- a/util.go +++ b/internal/util.go @@ -1,4 +1,4 @@ -package main +package internal import ( "fmt" diff --git a/main.go b/main.go deleted file mode 100644 index db45a0d..0000000 --- a/main.go +++ /dev/null @@ -1,387 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/base64" - "encoding/json" - "fmt" - "html/template" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - "strconv" - "strings" - "time" - - "cloud.google.com/go/datastore" -) - -const ( - AlbumsConfigUrl = "https://www.dropbox.com/s/kr8ewc68husts57/albums.json?dl=1" - KidLinksConfigUrl = "https://www.dropbox.com/s/5vdvc3l1pkly94f/weblinks.json?dl=1" -) - -var ( - albumsTemplate = template.Must(template.ParseFiles("templates/albums.html")) - kidLinksTemplate = template.Must(template.ParseFiles("templates/kidlinks.html")) - stravaTemplate = template.Must(template.ParseFiles("templates/strava.html")) - - stravaVars = &MemoryDatabase{vals: make(map[string]*StravaTokens)} -) - -type album struct { - Name string `json:"name"` - Url string `json:"url"` - CoverPath string `json:"cover_path"` - CoverUrl string -} - -// Serves a page that lists all available photo albums. -func AlbumsHandler(w http.ResponseWriter, _ *http.Request) { - rsp, err := http.Get(AlbumsConfigUrl) - if err != nil { - HttpError(w, http.StatusInternalServerError, "failed to fetch albums config from dropbox: %s", err) - return - } - defer DrainAndClose(rsp.Body) - - if err := CheckResponse(rsp); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to fetch albums config from dropbox: %s", err) - return - } - - var results struct { - Albums []*album `json:"albums"` - } - - if err := json.NewDecoder(rsp.Body).Decode(&results); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to json-decode response: %s", err) - return - } - - for _, album := range results.Albums { - album.CoverUrl = fmt.Sprintf("/albums/thumbnail?path=%s", url.QueryEscape(album.CoverPath)) - } - - if err := albumsTemplate.Execute(w, results.Albums); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to render template: %s", err) - return - } -} - -func DumpHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - - writeIt := func(s string, args ...interface{}) { - log.Printf(s, args...) - fmt.Fprintf(w, s+"\n", args...) - } - - writeIt("URL: %s", r.URL) - writeIt("Method: %s", r.Method) - writeIt("Proto: %s", r.Proto) - writeIt("RemoteAddr: %s", r.RemoteAddr) - - var buf bytes.Buffer - buf.WriteString("Headers:\n") - for k, v := range r.Header { - fmt.Fprintf(&buf, "%s: %v\n", k, v) - } - writeIt("%s", buf.String()) - fmt.Fprintln(w, "") // write blank line to response body - - buf.Reset() - buf.WriteString("Cookies:\n") - for _, c := range r.Cookies() { - fmt.Fprintf(&buf, "%s\n", c) - } - writeIt("%s", buf.String()) - fmt.Fprintln(w, "") // write blank line to response body - - b, err := ioutil.ReadAll(r.Body) - if err != nil { - writeIt("error: failed to read body: %s", err) - } else { - buf.Reset() - if _, err := base64.NewEncoder(base64.StdEncoding, &buf).Write(b); err != nil { - writeIt("error: failed to base64-encode body: %s", err) - } else { - writeIt("Body (base64):\n%s\n", buf.String()) - } - fmt.Fprintln(w, "") // write blank line to response body - - writeIt("Body (raw):\n%s", string(b)) - fmt.Fprintln(w, "") // write blank line to response body - } -} - -type links struct { - Sections []struct { - Title string `json:"title"` - Links []struct { - Href string `json:"href"` - Text string `json:"text"` - Notes string `json:"notes"` - } `json:"links"` - } `json:"sections"` -} - -func KidsLinksHandler(w http.ResponseWriter, _ *http.Request) { - rsp, err := http.Get(KidLinksConfigUrl) - if err != nil { - HttpError(w, http.StatusInternalServerError, "failed to fetch kid links config from dropbox: %s", err) - return - } - defer DrainAndClose(rsp.Body) - - if err := CheckResponse(rsp); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to fetch kid links config from dropbox: %s", err) - return - } - - var results links - if err := json.NewDecoder(rsp.Body).Decode(&results); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to json-decode response: %s", err) - return - } - - if err := kidLinksTemplate.Execute(w, &results); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to render template: %s", err) - return - } -} - -func StravaHandler(w http.ResponseWriter, r *http.Request, db KVDB) { - year := time.Now().Year() - if s := r.URL.Query().Get("year"); s != "" { - if i, err := strconv.Atoi(s); err == nil { - year = i - } - } - - username := r.URL.Query().Get("username") - if username == "" { - c, err := r.Cookie("username") - if err == nil { - username = c.Value - } - } - - if username == "" { - http.Redirect(w, r, StravaAuthUrl("www.ianthomasrose.com"), http.StatusTemporaryRedirect) - return - } - - accessToken, err := readAccessToken(r.Context(), username, db) - if err != nil { - if err == ErrNeedsAuth { - http.Redirect(w, r, StravaAuthUrl("www.ianthomasrose.com"), http.StatusTemporaryRedirect) - return - } - - http.Error(w, fmt.Sprintf("failed to read access token: %s", err), http.StatusInternalServerError) - return - } - - profile, err := getProfile(accessToken) - if err != nil { - http.Error(w, fmt.Sprintf("failed to get profile info: %s", err), http.StatusInternalServerError) - return - } - - http.SetCookie(w, &http.Cookie{ - Name: "username", - Value: username, - Expires: time.Now().Add(7 * 24 * time.Hour), - }) - - now := time.Now() - goalMiles := defaultGoalMiles[year] - queryStart := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC) - - var scaledGoalMiles float64 - var queryEnd time.Time - - if now.Year() == year { - scaledGoalMiles = float64(goalMiles) * float64(now.YearDay()) / 365 - queryEnd = queryStart.AddDate(0, 0, now.YearDay()) // finish is intentionally midnight at the END of the day - } else { - scaledGoalMiles = float64(goalMiles) - queryEnd = time.Date(year+1, time.January, 1, 0, 0, 0, 0, now.Location()) // Midnight, start of new years day - } - - activities, err := doStravaQuery(r.Context(), username, queryStart, queryEnd, db) - if err != nil { - http.Error(w, fmt.Sprintf("failed to query strava: %s", err), http.StatusInternalServerError) - return - } - - args := struct { - Username string - Activities []string - MilesTotal string - MilesYearGoal int - MilesScaledGoal string - Progress string - GaugeRotate int - }{ - Username: profile.Username, - MilesYearGoal: goalMiles, - MilesScaledGoal: fmt.Sprintf("%.1f", scaledGoalMiles), - } - - var sumMiles float64 - for _, activity := range activities { - if activity.Type != "Run" { - continue - } - secondsPerMile := int64(activity.MovingTime/activity.Miles() + 0.5000001) - args.Activities = append(args.Activities, - fmt.Sprintf("%s: %.1fK (%.1f miles) in %s (%d:%02d pace) on %s", activity.Name, - activity.DistanceMeters/1000., activity.Miles(), - formatSeconds(activity.MovingTime), secondsPerMile/60, secondsPerMile%60, - activity.StartDate)) - - sumMiles += activity.Miles() - } - - progress := 100 * sumMiles / scaledGoalMiles - - args.MilesTotal = fmt.Sprintf("%.1f", sumMiles) - args.Progress = fmt.Sprintf("%.0f", progress) - args.GaugeRotate = int(90.0 * progress / 100) - - if err := stravaTemplate.Execute(w, &args); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to render template: %s", err) - return - } -} - -func StravaTokenHandler(w http.ResponseWriter, r *http.Request, db KVDB) { - code := r.URL.Query().Get("code") - if code == "" { - return - } - - rsp, err := StravaExchangeToken(code) - if err != nil { - http.Error(w, fmt.Sprintf("failure in token exchange: %s", err), http.StatusInternalServerError) - return - } - - tokens := StravaTokens{ - AccessToken: rsp.AccessToken, - ExpiresAt: time.Unix(rsp.ExpiresAt, 0), - RefreshToken: rsp.RefreshToken, - } - - profile, err := getProfile(rsp.AccessToken) - if err != nil { - http.Error(w, fmt.Sprintf("failed to get profile info: %s", err), http.StatusInternalServerError) - return - } - - if err := db.Write(r.Context(), profile.Username, &tokens); err != nil { - http.Error(w, fmt.Sprintf("failure in write tokens to db: %s", err), http.StatusInternalServerError) - return - } - - http.Redirect(w, r, "/running/?username="+url.QueryEscape(profile.Username)+"&year="+strconv.Itoa(time.Now().Year()), http.StatusTemporaryRedirect) -} - -func ThumbnailHandler(w http.ResponseWriter, r *http.Request) { - path := r.URL.Query().Get("path") - if path == "" { - HttpError(w, http.StatusBadRequest, "no path query") - return - } - - if !strings.HasPrefix(path, "/photos/") { - HttpError(w, http.StatusBadRequest, "rejecting forbidden path %s", path) - return - } - - params := struct { - Path string `json:"path"` - Format string `json:"format"` - Size string `json:"size"` - }{ - Path: path, - Format: "jpeg", - Size: "w640h480", - } - - jstr, err := json.Marshal(¶ms) - if err != nil { - HttpError(w, http.StatusInternalServerError, "failed to json-encode params: %s", err) - return - } - - qs := make(url.Values) - qs.Add("arg", string(jstr)) - - urls := "https://api-content.dropbox.com/2/files/get_thumbnail?" + qs.Encode() - log.Printf("fetching %s", urls) - - req, err := http.NewRequest("GET", urls, nil) - if err != nil { - HttpError(w, http.StatusInternalServerError, "failed to make new http request") - return - } - - req.Header.Set("Authorization", "Bearer "+DropboxAccessToken) - - rsp, err := http.DefaultClient.Do(req) - if err != nil { - HttpError(w, http.StatusInternalServerError, "failed to fetch thumbnail from dropbox: %s", err) - return - } - defer DrainAndClose(rsp.Body) - - if err := CheckResponse(rsp); err != nil { - HttpError(w, http.StatusInternalServerError, "failed to fetch thumbnail from dropbox: %s", err) - return - } - - w.Header().Set("Content-Type", rsp.Header.Get("Content-Type")) - if _, err := io.Copy(w, rsp.Body); err != nil { - log.Printf("failed to copy thumbnail body to response stream: %s", err) - } -} - -func main() { - client, err := datastore.NewClient(context.Background(), os.Getenv("GOOGLE_CLOUD_PROJECT")) - if err != nil { - log.Fatalf("failed to connect to datastore: %s", err) - } - - ddb := &DatastoreDb{ - client: client, - } - - http.HandleFunc("/albums/", AlbumsHandler) - http.HandleFunc("/albums/thumbnail", ThumbnailHandler) - http.HandleFunc("/dump", DumpHandler) - http.HandleFunc("/kids/", KidsLinksHandler) - http.HandleFunc("/running/", func(w http.ResponseWriter, r *http.Request) { - StravaHandler(w, r, ddb) - }) - http.HandleFunc("/strava/exchange_token/", func(w http.ResponseWriter, r *http.Request) { - StravaTokenHandler(w, r, ddb) - }) - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - log.Printf("Defaulting to port %s", port) - } - - log.Printf("Listening on port %s", port) - if err := http.ListenAndServe(":"+port, nil); err != nil { - log.Fatal(err) - } -} diff --git a/templates/kidlinks.html b/templates/kidlinks.html deleted file mode 100644 index 8987091..0000000 --- a/templates/kidlinks.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - -
-
Kids' Links
-
- {{ range .Sections }} -

{{ .Title }}

-
    - {{ range .Links }} -
  • {{ .Text }}{{ .Notes }}
  • - {{ end }} -
- {{ end }} -
-
- - From e75ce6fcb244f871d5528a129c8077f2e6b2d870 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Tue, 7 Feb 2023 00:03:31 -0500 Subject: [PATCH 02/25] pause --- Makefile | 8 ++++++-- deploy.sh | 7 +++++-- scripts/startup.sh | 4 ++++ 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 scripts/startup.sh diff --git a/Makefile b/Makefile index 3b18ade..080fbe4 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,13 @@ -.PHONY: bin/webapp +.PHONY: webapp init -bin/webapp: +webapp: @mkdir -p bin go build -o bin/ ./cmd/webapp +webapp-linux: + @mkdir -p bin/linux_amd64/ + GOOS=linux GOARCH=amd64 go build -o bin/linux_amd64/ ./cmd/webapp + sql: ./bin/sqlc -f internal/storage/sqlc.yaml generate diff --git a/deploy.sh b/deploy.sh index eaf417c..97676b9 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,5 +2,8 @@ set -e -gcloud auth login ianrose14@gmail.com -gcloud --project ian-rose app deploy +make webapp-linux +ssh ianrose14@34.66.56.67 mkdir -p config/ +scp config/* ianrose14@34.66.56.67:config/ +scp scripts/startup.sh ianrose14@34.66.56.67: +ssh ianrose14@34.66.56.67 bash ./startup.sh diff --git a/scripts/startup.sh b/scripts/startup.sh new file mode 100644 index 0000000..94ef7de --- /dev/null +++ b/scripts/startup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +set -e + From 6dfe6efa6cee711db532ca3ff9915e35ed1e5681 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Tue, 3 Oct 2023 13:17:49 -0400 Subject: [PATCH 03/25] pause --- Dockerfile | 8 ++++ cmd/webapp/handlers.go | 2 +- cmd/webapp/main.go | 38 +++++++++++++++++-- .../webapp/templates}/albums.html | 0 .../webapp/templates}/strava.html | 0 deploy.sh | 9 ----- go.mod | 8 ++-- go.sum | 8 ++++ robots.txt | 2 - scripts/deploy.sh | 19 ++++++++++ scripts/startup.sh | 8 ++++ 11 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 Dockerfile rename {templates => cmd/webapp/templates}/albums.html (100%) rename {templates => cmd/webapp/templates}/strava.html (100%) delete mode 100755 deploy.sh delete mode 100644 robots.txt create mode 100755 scripts/deploy.sh mode change 100644 => 100755 scripts/startup.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d2ebe4e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM debian:bullseye-slim +WORKDIR /root/ +RUN apt-get update +RUN apt-get install -y ca-certificates + +# make cgo happy, see https://github.com/mattn/go-sqlite3/issues/855#issuecomment-1496136603 +RUN apt-get install -y build-essential +COPY bin/linux_amd64/webapp /root diff --git a/cmd/webapp/handlers.go b/cmd/webapp/handlers.go index 3f263b8..68672a2 100644 --- a/cmd/webapp/handlers.go +++ b/cmd/webapp/handlers.go @@ -21,7 +21,7 @@ const ( ) var ( - albumsTemplate = template.Must(template.ParseFiles("templates/albums.html")) + albumsTemplate = template.Must(template.ParseFS(templatesFS, "templates/albums.html")) ) type album struct { diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 4a970d5..5a000b5 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -8,10 +8,13 @@ import ( "flag" "html/template" "io/fs" + "io/ioutil" "log" "net/http" "os" + "path/filepath" "runtime" + "strconv" "time" "github.com/ianrose14/website/internal" @@ -31,8 +34,11 @@ var ( //go:embed talks/* talksFS embed.FS + //go:embed templates/* + templatesFS embed.FS + //stravaVars = &internal.MemoryDatabase{vals: make(map[string]*internal.StravaTokens)} - stravaTemplate = template.Must(template.ParseFiles("templates/strava.html")) + stravaTemplate = template.Must(template.ParseFS(templatesFS, "templates/strava.html")) ) func init() { @@ -40,14 +46,30 @@ func init() { } func main() { - certsDir := flag.String("certs", "certs", "directory to store letsencrypt certs") - dbfile := flag.String("db", "solarsnoop.sqlite", "sqlite database file") + certsDir := flag.String("certs", "certs", "Directory to store letsencrypt certs") + daemonize := flag.Bool("d", false, "Whether to daemonize on start") + dbfile := flag.String("db", "store.sqlite", "sqlite database file") host := flag.String("host", "", "optional hostname for webserver") + pidfile := flag.String("pidfile", "", "Optional file to write process ID to") secretsFile := flag.String("secrets", "config/secrets.yaml", "Path to local secrets file") flag.Parse() ctx := context.Background() + s, err := filepath.Abs(*certsDir) + if err != nil { + log.Fatalf("failed to get absolute path of %q: %+v", *certsDir, err) + } + certsDir = &s + + if *pidfile != "" { + s, err := filepath.Abs(*pidfile) + if err != nil { + log.Fatalf("failed to get absolute path of %q: %+v", *pidfile, err) + } + pidfile = &s + } + secrets, err := internal.ParseSecrets(*secretsFile) if err != nil { log.Fatalf("failed to parse secrets: %s", err) @@ -67,6 +89,16 @@ func main() { log.Fatalf("failed to upsert database tables: %s", err) } + if *daemonize { + + } + + if *pidfile != "" { + if err := ioutil.WriteFile(*pidfile, []byte(strconv.Itoa(os.Getpid())), 0666); err != nil { + log.Fatalf("failed to write pidfile: %+v", err) + } + } + svr := &server{ db: db, secrets: secrets, diff --git a/templates/albums.html b/cmd/webapp/templates/albums.html similarity index 100% rename from templates/albums.html rename to cmd/webapp/templates/albums.html diff --git a/templates/strava.html b/cmd/webapp/templates/strava.html similarity index 100% rename from templates/strava.html rename to cmd/webapp/templates/strava.html diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 97676b9..0000000 --- a/deploy.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -e - -make webapp-linux -ssh ianrose14@34.66.56.67 mkdir -p config/ -scp config/* ianrose14@34.66.56.67:config/ -scp scripts/startup.sh ianrose14@34.66.56.67: -ssh ianrose14@34.66.56.67 bash ./startup.sh diff --git a/go.mod b/go.mod index 98453f2..5285618 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/ianrose14/website go 1.18 require ( - github.com/mattn/go-sqlite3 v1.14.16 - golang.org/x/crypto v0.5.0 + github.com/mattn/go-sqlite3 v1.14.17 + golang.org/x/crypto v0.12.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - golang.org/x/net v0.5.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/text v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 5e93f84..3385c91 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,19 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/robots.txt b/robots.txt deleted file mode 100644 index eb05362..0000000 --- a/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..c1c28f3 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +# Getting permissions errors on local when trying to push? Run this: +# gcloud auth configure-docker us-central1-docker.pkg.dev + +make webapp-linux + +docker buildx build --platform linux/amd64 -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . +#docker build -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . +docker push us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest + +ssh ianrose14@34.66.56.67 mkdir -p config/ +scp config/* ianrose14@34.66.56.67:config/ +scp scripts/startup.sh ianrose14@34.66.56.67: + +#scp bin/linux_amd64/webapp ianrose14@34.66.56.67: +ssh ianrose14@34.66.56.67 bash ./startup.sh diff --git a/scripts/startup.sh b/scripts/startup.sh old mode 100644 new mode 100755 index 94ef7de..e11cdde --- a/scripts/startup.sh +++ b/scripts/startup.sh @@ -2,3 +2,11 @@ set -e +(podman stop webapp) || true +(podman rm webapp) || true + +gcloud auth --quiet print-access-token | podman login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev +podman pull us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest +podman run -d -p 8080:80 -p 8443:443 --restart=no \ + -v "$(pwd)/config":/root/config/ -v "$(pwd)/data":/root/data/ --name webapp \ + us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com From 07e4832e5aa3717102ddf2d68a6b5d089ef2d5d5 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 11:40:57 -0500 Subject: [PATCH 04/25] pause before sqlite conversion --- cmd/webapp/main.go | 16 +++++++++------- go.mod | 18 ++++++++++++++++++ go.sum | 37 +++++++++++++++++++++++++++++++++++++ internal/strava/strava.go | 5 +++++ 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 5a000b5..1201745 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -24,6 +24,8 @@ import ( "golang.org/x/crypto/acme/autocert" ) +const inDev = runtime.GOOS == "darwin" + var ( //go:embed static/* staticFS embed.FS @@ -54,6 +56,10 @@ func main() { secretsFile := flag.String("secrets", "config/secrets.yaml", "Path to local secrets file") flag.Parse() + if *host == "" { + *host = "localhost" + } + ctx := context.Background() s, err := filepath.Abs(*certsDir) @@ -90,7 +96,7 @@ func main() { } if *daemonize { - + } if *pidfile != "" { @@ -108,6 +114,7 @@ func main() { stravaAccount := &strava.ApiParams{ ClientId: secrets.Strava.ClientID, ClientSecret: secrets.Strava.ClientSecret, + Hostname: *host, } httpFS := func(files embed.FS, subdir string) http.Handler { @@ -143,11 +150,6 @@ func main() { // TODO: in a handler wrapper, redirect http to https (in production only) - const inDev = runtime.GOOS == "darwin" - - // when testing locally it doesn't make sense to start - // HTTPS server, so only do it in production. - // In real code, I control this with -production cmd-line flag if !inDev { if err := os.MkdirAll(*certsDir, 0777); err != nil { log.Fatalf("failed to create certs dir: %s", err) @@ -156,7 +158,7 @@ func main() { httpsSrv := makeHTTPServer(mux) certManager := &autocert.Manager{ Prompt: autocert.AcceptTOS, - Email: "ianrose14+autocert@gmail.com", // NOSUBMIT - replace with alias? + Email: "ianrose14+autocert@gmail.com", HostPolicy: func(ctx context.Context, host string) error { log.Printf("autocert query for host %q", host) return autocert.HostWhitelist("solarsnoop.com", "www.solarsnoop.com")(ctx, host) diff --git a/go.mod b/go.mod index 5285618..1eaa5f8 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,24 @@ require ( ) require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.6.0 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.29.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.27.0 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3385c91..f0f248c 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,57 @@ +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= +modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= +modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/strava/strava.go b/internal/strava/strava.go index d08c2cb..fd47501 100644 --- a/internal/strava/strava.go +++ b/internal/strava/strava.go @@ -250,6 +250,11 @@ func formatSeconds(s float64) string { } func getAuthUrl(account *ApiParams) string { + host := account.Hostname + if host == "" { + + } + return fmt.Sprintf("https://www.strava.com/oauth/authorize?client_id=" + account.ClientId + "&response_type=code&redirect_uri=https://" + account.Hostname + "/strava/exchange_token/&approval_prompt=force&scope=activity:read_all") } From 222d2a622086b0cc577a9f03d190f3a190d89b19 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 12:27:16 -0500 Subject: [PATCH 05/25] go mod tidy --- .gitignore | 3 +++ go.mod | 18 ------------------ go.sum | 45 --------------------------------------------- 3 files changed, 3 insertions(+), 63 deletions(-) diff --git a/.gitignore b/.gitignore index fc5ebd7..675b7b0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ bin .idea secrets.yaml var + +# from docs here: https://github.com/google-github-actions/auth#prerequisites +gha-creds-*.json diff --git a/go.mod b/go.mod index 1eaa5f8..5285618 100644 --- a/go.mod +++ b/go.mod @@ -9,24 +9,6 @@ require ( ) require ( - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect - golang.org/x/tools v0.6.0 // indirect - lukechampine.com/uint128 v1.2.0 // indirect - modernc.org/cc/v3 v3.40.0 // indirect - modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.29.0 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/sqlite v1.27.0 // indirect - modernc.org/strutil v1.1.3 // indirect - modernc.org/token v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index f0f248c..a1c1813 100644 --- a/go.sum +++ b/go.sum @@ -1,57 +1,12 @@ -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= -modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8= -modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= -modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From a0f8cfc2c90e85597c61a800f6652f54d389a8cf Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 12:28:42 -0500 Subject: [PATCH 06/25] push depends on build --- .github/workflows/comment-test.yml | 46 ------------------------------ .github/workflows/go.yml | 1 + 2 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 .github/workflows/comment-test.yml diff --git a/.github/workflows/comment-test.yml b/.github/workflows/comment-test.yml deleted file mode 100644 index 93addaa..0000000 --- a/.github/workflows/comment-test.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: My Comment Tester - -on: - issue_comment: - types: [created, edited] - -jobs: - debug: - name: Debug - runs-on: ubuntu-latest - steps: - - run: echo 'hi there' && echo ${{ github.event.comment.body }} - - on-branch: - name: Branch Test - runs-on: ubuntu-latest - steps: - - name: Get PR branch - uses: xt0rted/pull-request-comment-branch@v1 - id: comment-branch - - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ steps.comment-branch.outputs.head_ref }} - - - run: echo my head ref is ${{ steps.comment-branch.outputs.head_ref }} - - - run: cat robots.txt - - - name: Add comment to PR - uses: actions/github-script@v6 - if: always() - with: - script: | - const name = '${{ github.workflow }}'; - const url = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'; - const success = '${{ job.status }}' === 'success'; - const body = `${name}: ${success ? 'succeeded ✅' : 'failed ❌'}\n${url}`; - - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: body - }) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 165db16..e50b30f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,6 +25,7 @@ jobs: push: runs-on: ubuntu-latest + needs: [build] steps: - uses: actions/checkout@v4 From 1b4c48f2ac4d643a35df05d22a047e0b1f1a7b40 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 12:29:14 -0500 Subject: [PATCH 07/25] make build --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 080fbe4..809c630 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -.PHONY: webapp init +.PHONY: build webapp init + +build: webapp webapp: @mkdir -p bin From cd34b5277b863d30c1102e1d8db2a231a4d6a2a9 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 12:34:41 -0500 Subject: [PATCH 08/25] merge steps --- .github/workflows/go.yml | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e50b30f..213f83b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -7,7 +7,7 @@ on: [push] jobs: - build: + build-and-push: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -23,24 +23,18 @@ jobs: - name: Test run: go test ./... - push: - runs-on: ubuntu-latest - needs: [build] - steps: - - uses: actions/checkout@v4 - - - name: Setup - run: mkdir -p bin/linux && cp bin/webapp bib/linux_amd64/webapp - - - name: Get shortsha - id: vars - run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - - name: Push to GCR - uses: RafikFarhad/push-to-gcr-github-action@v4.1 - with: - gcloud_service_key: ${{ secrets.GCP_SERVICE_ACCOUNT }} - registry: gcr.io - project_id: ian-rose - image_name: us-central1-docker.pkg.dev/ian-rose/docker-1/webapp - image_tag: latest,${{ steps.vars.outputs.sha_short }} + - name: Setup + run: mkdir -p bin/linux && cp bin/webapp bib/linux_amd64/webapp + + - name: Get shortsha + id: vars + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Push to GCR + uses: RafikFarhad/push-to-gcr-github-action@v4.1 + with: + gcloud_service_key: ${{ secrets.GCP_SERVICE_ACCOUNT }} + registry: gcr.io + project_id: ian-rose + image_name: us-central1-docker.pkg.dev/ian-rose/docker-1/webapp + image_tag: latest,${{ steps.vars.outputs.sha_short }} From e59cf5d194343060416f3a29a9cfc0f2ceb86695 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 12:36:11 -0500 Subject: [PATCH 09/25] typoe --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 213f83b..cf6a27e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -24,7 +24,7 @@ jobs: run: go test ./... - name: Setup - run: mkdir -p bin/linux && cp bin/webapp bib/linux_amd64/webapp + run: mkdir -p bin/linux && cp bin/webapp bin/linux_amd64/webapp - name: Get shortsha id: vars From 88a47618cb880f9b5a32257f893412794a06850c Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 12:47:55 -0500 Subject: [PATCH 10/25] try a docker arg --- .github/workflows/go.yml | 4 +--- Dockerfile | 3 ++- scripts/deploy.sh | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cf6a27e..ae70244 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -23,9 +23,6 @@ jobs: - name: Test run: go test ./... - - name: Setup - run: mkdir -p bin/linux && cp bin/webapp bin/linux_amd64/webapp - - name: Get shortsha id: vars run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT @@ -38,3 +35,4 @@ jobs: project_id: ian-rose image_name: us-central1-docker.pkg.dev/ian-rose/docker-1/webapp image_tag: latest,${{ steps.vars.outputs.sha_short }} + build_args: BIN=bin/webapp diff --git a/Dockerfile b/Dockerfile index d2ebe4e..913cee6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ FROM debian:bullseye-slim +ARG BIN WORKDIR /root/ RUN apt-get update RUN apt-get install -y ca-certificates # make cgo happy, see https://github.com/mattn/go-sqlite3/issues/855#issuecomment-1496136603 RUN apt-get install -y build-essential -COPY bin/linux_amd64/webapp /root +COPY $BIN /root diff --git a/scripts/deploy.sh b/scripts/deploy.sh index c1c28f3..95ec095 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -7,7 +7,7 @@ set -e make webapp-linux -docker buildx build --platform linux/amd64 -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . +docker buildx build --platform linux/amd64 --build-arg BIN=bin/linux_amd64/webapp -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . #docker build -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . docker push us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest From c51447b3302a05ecf63cfa13ce16eabb13841035 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 13:27:04 -0500 Subject: [PATCH 11/25] debug logging --- cmd/webapp/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 1201745..9571fea 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -81,6 +81,8 @@ func main() { log.Fatalf("failed to parse secrets: %s", err) } + log.Printf("hello, world! v1") + db, err := sql.Open("sqlite3", "file:"+*dbfile+"?cache=shared") if err != nil { log.Fatalf("failed to open sqlite connection: %s", err) @@ -96,7 +98,7 @@ func main() { } if *daemonize { - + } if *pidfile != "" { From 4e76680f7ceabd050271a6b4d3834b73f9787772 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 13:35:50 -0500 Subject: [PATCH 12/25] use GCR in startup.sh --- scripts/startup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/startup.sh b/scripts/startup.sh index e11cdde..2f8972d 100755 --- a/scripts/startup.sh +++ b/scripts/startup.sh @@ -6,7 +6,7 @@ set -e (podman rm webapp) || true gcloud auth --quiet print-access-token | podman login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev -podman pull us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest +docker pull gcr.io/ian-rose/us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest podman run -d -p 8080:80 -p 8443:443 --restart=no \ -v "$(pwd)/config":/root/config/ -v "$(pwd)/data":/root/data/ --name webapp \ - us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com + gcr.io/ian-rose/us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com From d487b61cb356161170f9710f0c82bea7e9b19e53 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 13:46:19 -0500 Subject: [PATCH 13/25] go back to artifact reg --- .github/workflows/go.yml | 2 +- scripts/startup.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ae70244..758bb69 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -31,7 +31,7 @@ jobs: uses: RafikFarhad/push-to-gcr-github-action@v4.1 with: gcloud_service_key: ${{ secrets.GCP_SERVICE_ACCOUNT }} - registry: gcr.io + registry: us-central1-docker.pkg.dev project_id: ian-rose image_name: us-central1-docker.pkg.dev/ian-rose/docker-1/webapp image_tag: latest,${{ steps.vars.outputs.sha_short }} diff --git a/scripts/startup.sh b/scripts/startup.sh index 2f8972d..e11cdde 100755 --- a/scripts/startup.sh +++ b/scripts/startup.sh @@ -6,7 +6,7 @@ set -e (podman rm webapp) || true gcloud auth --quiet print-access-token | podman login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev -docker pull gcr.io/ian-rose/us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest +podman pull us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest podman run -d -p 8080:80 -p 8443:443 --restart=no \ -v "$(pwd)/config":/root/config/ -v "$(pwd)/data":/root/data/ --name webapp \ - gcr.io/ian-rose/us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com + us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com From 9b302a3d75e8eca72fe9de2c0df91c42ab647add Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 10 Nov 2023 13:53:54 -0500 Subject: [PATCH 14/25] fix workflow? --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 758bb69..b987899 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -32,7 +32,7 @@ jobs: with: gcloud_service_key: ${{ secrets.GCP_SERVICE_ACCOUNT }} registry: us-central1-docker.pkg.dev - project_id: ian-rose - image_name: us-central1-docker.pkg.dev/ian-rose/docker-1/webapp + project_id: ian-rose/docker-1 + image_name: webapp image_tag: latest,${{ steps.vars.outputs.sha_short }} build_args: BIN=bin/webapp From d030a5dfe60ab22f66454d2d972f20fed7f4a8b2 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Sat, 11 Nov 2023 10:55:37 -0500 Subject: [PATCH 15/25] fight with glibc --- .github/workflows/go.yml | 5 ++++- scripts/deploy.sh | 20 +++++++++++++------- scripts/startup.sh | 6 +++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b987899..fb0320e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -8,7 +8,7 @@ on: jobs: build-and-push: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 @@ -20,6 +20,9 @@ jobs: - name: Build run: make build + - name: what gblic? + run: ldd --version ldd + - name: Test run: go test ./... diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 95ec095..5ef07bd 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -5,15 +5,21 @@ set -e # Getting permissions errors on local when trying to push? Run this: # gcloud auth configure-docker us-central1-docker.pkg.dev + +# Run on initial instance creation: +# > sudo apt-get update +# > sudo apt-get install podman + make webapp-linux -docker buildx build --platform linux/amd64 --build-arg BIN=bin/linux_amd64/webapp -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . +HOST_IP=$(gcloud compute --project ian-rose instances describe instance-1 --zone us-central1-a --format "get(networkInterfaces[0].accessConfigs.natIP)") + +#docker buildx build --platform linux/amd64 --build-arg BIN=bin/linux_amd64/webapp -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . #docker build -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . -docker push us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest +#docker push us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest -ssh ianrose14@34.66.56.67 mkdir -p config/ -scp config/* ianrose14@34.66.56.67:config/ -scp scripts/startup.sh ianrose14@34.66.56.67: +gcloud compute --project ian-rose ssh ianrose14@instance-1 --zone us-central1-a -- mkdir -p config/ +gcloud compute --project ian-rose scp --zone us-central1-a config/* ianrose14@instance-1:config/ +gcloud compute --project ian-rose scp --zone us-central1-a scripts/startup.sh ianrose14@instance-1: -#scp bin/linux_amd64/webapp ianrose14@34.66.56.67: -ssh ianrose14@34.66.56.67 bash ./startup.sh +ssh ianrose14@"${HOST_IP}" bash ./startup.sh diff --git a/scripts/startup.sh b/scripts/startup.sh index e11cdde..95bbe9b 100755 --- a/scripts/startup.sh +++ b/scripts/startup.sh @@ -5,7 +5,11 @@ set -e (podman stop webapp) || true (podman rm webapp) || true -gcloud auth --quiet print-access-token | podman login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev +mkdir -p data + +# ref 1: https://stackoverflow.com/questions/63790529/authenticate-to-google-container-registry-with-podman +# ref 2: https://github.com/containers/podman/issues/13691#issuecomment-1081913637 +gcloud auth --quiet print-access-token | podman login -u oauth2accesstoken --password-stdin us-central1-docker.pkg.dev podman pull us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest podman run -d -p 8080:80 -p 8443:443 --restart=no \ -v "$(pwd)/config":/root/config/ -v "$(pwd)/data":/root/data/ --name webapp \ From 0dc9084c90a5974a1d35a1cbeb5b617104e0369d Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Sat, 11 Nov 2023 12:14:49 -0500 Subject: [PATCH 16/25] make dockerfile match runner --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 913cee6..4110cb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim +FROM ubuntu:22.04 ARG BIN WORKDIR /root/ RUN apt-get update From fc0a41479bfe2dfd35b90fcd20368c07ca988262 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 24 Nov 2023 21:09:49 -0500 Subject: [PATCH 17/25] checkpoint --- cmd/webapp/main.go | 14 ++++---------- scripts/deploy.sh | 7 +++---- scripts/login.sh | 7 +++++++ scripts/startup.sh | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) create mode 100755 scripts/login.sh diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 9571fea..32cef77 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -49,9 +49,8 @@ func init() { func main() { certsDir := flag.String("certs", "certs", "Directory to store letsencrypt certs") - daemonize := flag.Bool("d", false, "Whether to daemonize on start") dbfile := flag.String("db", "store.sqlite", "sqlite database file") - host := flag.String("host", "", "optional hostname for webserver") + host := flag.String("host", "", "Optional hostname for webserver") pidfile := flag.String("pidfile", "", "Optional file to write process ID to") secretsFile := flag.String("secrets", "config/secrets.yaml", "Path to local secrets file") flag.Parse() @@ -81,8 +80,6 @@ func main() { log.Fatalf("failed to parse secrets: %s", err) } - log.Printf("hello, world! v1") - db, err := sql.Open("sqlite3", "file:"+*dbfile+"?cache=shared") if err != nil { log.Fatalf("failed to open sqlite connection: %s", err) @@ -97,10 +94,6 @@ func main() { log.Fatalf("failed to upsert database tables: %s", err) } - if *daemonize { - - } - if *pidfile != "" { if err := ioutil.WriteFile(*pidfile, []byte(strconv.Itoa(os.Getpid())), 0666); err != nil { log.Fatalf("failed to write pidfile: %+v", err) @@ -153,6 +146,7 @@ func main() { // TODO: in a handler wrapper, redirect http to https (in production only) if !inDev { + log.Printf("starting autocert manager") if err := os.MkdirAll(*certsDir, 0777); err != nil { log.Fatalf("failed to create certs dir: %s", err) } @@ -162,8 +156,8 @@ func main() { Prompt: autocert.AcceptTOS, Email: "ianrose14+autocert@gmail.com", HostPolicy: func(ctx context.Context, host string) error { - log.Printf("autocert query for host %q", host) - return autocert.HostWhitelist("solarsnoop.com", "www.solarsnoop.com")(ctx, host) + log.Printf("autocert query for host %q, responding with %v", host, []string{svr.host, "www." + svr.host}) + return autocert.HostWhitelist(svr.host, "www."+svr.host)(ctx, host) }, Cache: autocert.DirCache(*certsDir), } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 5ef07bd..cebe9aa 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -9,11 +9,11 @@ set -e # Run on initial instance creation: # > sudo apt-get update # > sudo apt-get install podman +# > sudo setcap cap_net_bind_service=+ep /usr/bin/podman +# > sudo setcap cap_net_bind_service=+ep $(which slirp4netns) make webapp-linux -HOST_IP=$(gcloud compute --project ian-rose instances describe instance-1 --zone us-central1-a --format "get(networkInterfaces[0].accessConfigs.natIP)") - #docker buildx build --platform linux/amd64 --build-arg BIN=bin/linux_amd64/webapp -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . #docker build -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . #docker push us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest @@ -21,5 +21,4 @@ HOST_IP=$(gcloud compute --project ian-rose instances describe instance-1 --zone gcloud compute --project ian-rose ssh ianrose14@instance-1 --zone us-central1-a -- mkdir -p config/ gcloud compute --project ian-rose scp --zone us-central1-a config/* ianrose14@instance-1:config/ gcloud compute --project ian-rose scp --zone us-central1-a scripts/startup.sh ianrose14@instance-1: - -ssh ianrose14@"${HOST_IP}" bash ./startup.sh +gcloud compute --project ian-rose ssh ianrose14@instance-1 --zone us-central1-a -- bash ./startup.sh diff --git a/scripts/login.sh b/scripts/login.sh new file mode 100755 index 0000000..5e8f750 --- /dev/null +++ b/scripts/login.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +gcloud auth login ianrose14@gmail.com +gcloud config set project ian-rose +gcloud config list diff --git a/scripts/startup.sh b/scripts/startup.sh index 95bbe9b..b1b34ce 100755 --- a/scripts/startup.sh +++ b/scripts/startup.sh @@ -11,6 +11,6 @@ mkdir -p data # ref 2: https://github.com/containers/podman/issues/13691#issuecomment-1081913637 gcloud auth --quiet print-access-token | podman login -u oauth2accesstoken --password-stdin us-central1-docker.pkg.dev podman pull us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest -podman run -d -p 8080:80 -p 8443:443 --restart=no \ +podman run -d -p 80:80 -p 443:443 --restart=no \ -v "$(pwd)/config":/root/config/ -v "$(pwd)/data":/root/data/ --name webapp \ us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com From b2a3dccb68dfe97dd801e20dd8450291b1f6d4c1 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 24 Nov 2023 21:23:19 -0500 Subject: [PATCH 18/25] debug output --- internal/strava/handlers.go | 8 +++++++- scripts/deploy.sh | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/strava/handlers.go b/internal/strava/handlers.go index ecd856a..22fe91a 100644 --- a/internal/strava/handlers.go +++ b/internal/strava/handlers.go @@ -3,6 +3,7 @@ package strava import ( "fmt" "html/template" + "log" "net/http" "net/url" "strconv" @@ -150,5 +151,10 @@ func TokenHandler(w http.ResponseWriter, r *http.Request, db KVDB, account *ApiP return } - http.Redirect(w, r, "/running/?username="+url.QueryEscape(profile.Username)+"&year="+strconv.Itoa(time.Now().Year()), http.StatusTemporaryRedirect) + qs := make(url.Values) + qs.Set("username", profile.Username) + qs.Set("year", strconv.Itoa(time.Now().Year())) + urlStr := "/running/?username=" + qs.Encode() + log.Printf("successful token exchange, redirecting to %s", urlStr) + http.Redirect(w, r, urlStr, http.StatusTemporaryRedirect) } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index cebe9aa..fa43576 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -12,7 +12,7 @@ set -e # > sudo setcap cap_net_bind_service=+ep /usr/bin/podman # > sudo setcap cap_net_bind_service=+ep $(which slirp4netns) -make webapp-linux +# make webapp-linux #docker buildx build --platform linux/amd64 --build-arg BIN=bin/linux_amd64/webapp -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . #docker build -t us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest . From 136bde7122f939aa9b29a8fc8f9b5ed9c83560a4 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 24 Nov 2023 21:32:32 -0500 Subject: [PATCH 19/25] more clean and debug --- cmd/webapp/main.go | 21 +++------------------ scripts/startup.sh | 2 +- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 32cef77..9069440 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -8,13 +8,11 @@ import ( "flag" "html/template" "io/fs" - "io/ioutil" "log" "net/http" "os" "path/filepath" "runtime" - "strconv" "time" "github.com/ianrose14/website/internal" @@ -51,7 +49,6 @@ func main() { certsDir := flag.String("certs", "certs", "Directory to store letsencrypt certs") dbfile := flag.String("db", "store.sqlite", "sqlite database file") host := flag.String("host", "", "Optional hostname for webserver") - pidfile := flag.String("pidfile", "", "Optional file to write process ID to") secretsFile := flag.String("secrets", "config/secrets.yaml", "Path to local secrets file") flag.Parse() @@ -67,14 +64,6 @@ func main() { } certsDir = &s - if *pidfile != "" { - s, err := filepath.Abs(*pidfile) - if err != nil { - log.Fatalf("failed to get absolute path of %q: %+v", *pidfile, err) - } - pidfile = &s - } - secrets, err := internal.ParseSecrets(*secretsFile) if err != nil { log.Fatalf("failed to parse secrets: %s", err) @@ -94,12 +83,6 @@ func main() { log.Fatalf("failed to upsert database tables: %s", err) } - if *pidfile != "" { - if err := ioutil.WriteFile(*pidfile, []byte(strconv.Itoa(os.Getpid())), 0666); err != nil { - log.Fatalf("failed to write pidfile: %+v", err) - } - } - svr := &server{ db: db, secrets: secrets, @@ -131,11 +114,13 @@ func main() { stravaDb := strava.NewSqliteDb(db) h := func(w http.ResponseWriter, r *http.Request) { + log.Printf("basic strava request to %v", r.URL) strava.Handler(w, r, stravaTemplate, stravaDb, stravaAccount) } mux.HandleFunc("/running/", h) mux.HandleFunc("/strava/", h) http.HandleFunc("/strava/exchange_token/", func(w http.ResponseWriter, r *http.Request) { + log.Printf("token exchange strava request to %v", r.URL) strava.TokenHandler(w, r, stravaDb, stravaAccount) }) @@ -146,7 +131,7 @@ func main() { // TODO: in a handler wrapper, redirect http to https (in production only) if !inDev { - log.Printf("starting autocert manager") + log.Printf("starting autocert manager with certsDir=%v", *certsDir) if err := os.MkdirAll(*certsDir, 0777); err != nil { log.Fatalf("failed to create certs dir: %s", err) } diff --git a/scripts/startup.sh b/scripts/startup.sh index b1b34ce..65fdfda 100755 --- a/scripts/startup.sh +++ b/scripts/startup.sh @@ -13,4 +13,4 @@ gcloud auth --quiet print-access-token | podman login -u oauth2accesstoken --pas podman pull us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest podman run -d -p 80:80 -p 443:443 --restart=no \ -v "$(pwd)/config":/root/config/ -v "$(pwd)/data":/root/data/ --name webapp \ - us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com + us-central1-docker.pkg.dev/ian-rose/docker-1/webapp:latest /root/webapp -host ianthomasrose.com -certs data/certs/ From 25543ffdc5658e68e7881fd3ac98b0d69fffdd67 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 24 Nov 2023 21:39:37 -0500 Subject: [PATCH 20/25] handler ordering --- cmd/webapp/main.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 9069440..5ccbb90 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -113,16 +113,18 @@ func main() { mux.HandleFunc("/dump/", svr.dumpHandler) stravaDb := strava.NewSqliteDb(db) - h := func(w http.ResponseWriter, r *http.Request) { - log.Printf("basic strava request to %v", r.URL) - strava.Handler(w, r, stravaTemplate, stravaDb, stravaAccount) - } - mux.HandleFunc("/running/", h) - mux.HandleFunc("/strava/", h) http.HandleFunc("/strava/exchange_token/", func(w http.ResponseWriter, r *http.Request) { log.Printf("token exchange strava request to %v", r.URL) strava.TokenHandler(w, r, stravaDb, stravaAccount) }) + { + h := func(w http.ResponseWriter, r *http.Request) { + log.Printf("basic strava request to %v", r.URL) + strava.Handler(w, r, stravaTemplate, stravaDb, stravaAccount) + } + mux.HandleFunc("/running/", h) + mux.HandleFunc("/strava/", h) + } mux.Handle("/favicon.ico", httpFS(staticFS, "static")) From fe4438e931b10d16b7d08103dd472d2fdc211728 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 24 Nov 2023 23:46:45 -0500 Subject: [PATCH 21/25] signal handling --- cmd/webapp/main.go | 59 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 5ccbb90..4b2876a 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -9,10 +9,13 @@ import ( "html/template" "io/fs" "log" + "net" "net/http" "os" + "os/signal" "path/filepath" "runtime" + "syscall" "time" "github.com/ianrose14/website/internal" @@ -52,12 +55,21 @@ func main() { secretsFile := flag.String("secrets", "config/secrets.yaml", "Path to local secrets file") flag.Parse() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + sig := <-c + log.Printf("received %s signal", sig) + cancel() + }() + if *host == "" { *host = "localhost" } - ctx := context.Background() - s, err := filepath.Abs(*certsDir) if err != nil { log.Fatalf("failed to get absolute path of %q: %+v", *certsDir, err) @@ -153,20 +165,49 @@ func main() { httpHandler = certManager.HTTPHandler(mux) + lis, err := net.Listen("tcp", ":https") + if err != nil { + log.Fatalf("failed to listen on port 443: %+v", err) + } + + log.Printf("listening on %s", lis.Addr()) + srv := &http.Server{Handler: httpHandler} + go func() { - log.Printf("listening on port 443") + <-ctx.Done() + if err := srv.Close(); err != nil { + log.Printf("error: failed to close https server: %+v", err) + } + }() - err := httpsSrv.ListenAndServeTLS("", "") - if err != nil { - log.Fatalf("httpsSrv.ListendAndServeTLS() failed: %s", err) + go func() { + if err := httpsSrv.ServeTLS(lis, "", ""); err != nil { + log.Fatalf("failure in https server: %+v", err) } }() } - log.Printf("listening on port 80") - if err := http.ListenAndServe(":http", httpHandler); err != nil { - log.Fatalf("http.ListenAndServe failed: %s", err) + lis, err := net.Listen("tcp", ":http") + if err != nil { + log.Fatalf("failed to listen on port 80: %+v", err) } + + log.Printf("listening on %s", lis.Addr()) + srv := &http.Server{Handler: httpHandler} + go func() { + <-ctx.Done() + if err := srv.Close(); err != nil { + log.Printf("error: failed to close http server: %+v", err) + } + }() + + if err := srv.Serve(lis); err != nil { + if err != http.ErrServerClosed { + log.Fatalf("failure in http server: %+v", err) + } + } + + log.Printf("clean exit - goodbye!") } func makeHTTPServer(mux *http.ServeMux) *http.Server { From 900a0327bdd16e7966d972e878302e7f475e3181 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Fri, 24 Nov 2023 23:48:28 -0500 Subject: [PATCH 22/25] fix --- cmd/webapp/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 4b2876a..3b097b8 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -125,7 +125,7 @@ func main() { mux.HandleFunc("/dump/", svr.dumpHandler) stravaDb := strava.NewSqliteDb(db) - http.HandleFunc("/strava/exchange_token/", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/strava/exchange_token/", func(w http.ResponseWriter, r *http.Request) { log.Printf("token exchange strava request to %v", r.URL) strava.TokenHandler(w, r, stravaDb, stravaAccount) }) @@ -182,7 +182,9 @@ func main() { go func() { if err := httpsSrv.ServeTLS(lis, "", ""); err != nil { - log.Fatalf("failure in https server: %+v", err) + if err != http.ErrServerClosed { + log.Fatalf("failure in https server: %+v", err) + } } }() } From 9cf2f481b04cd072faa40496490cd199fc438863 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Sat, 25 Nov 2023 00:04:22 -0500 Subject: [PATCH 23/25] fix querystring --- cmd/webapp/main.go | 4 +--- internal/strava/handlers.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/webapp/main.go b/cmd/webapp/main.go index 3b097b8..9470df1 100644 --- a/cmd/webapp/main.go +++ b/cmd/webapp/main.go @@ -62,7 +62,7 @@ func main() { signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { sig := <-c - log.Printf("received %s signal", sig) + log.Printf("received %q signal", sig) cancel() }() @@ -126,12 +126,10 @@ func main() { stravaDb := strava.NewSqliteDb(db) mux.HandleFunc("/strava/exchange_token/", func(w http.ResponseWriter, r *http.Request) { - log.Printf("token exchange strava request to %v", r.URL) strava.TokenHandler(w, r, stravaDb, stravaAccount) }) { h := func(w http.ResponseWriter, r *http.Request) { - log.Printf("basic strava request to %v", r.URL) strava.Handler(w, r, stravaTemplate, stravaDb, stravaAccount) } mux.HandleFunc("/running/", h) diff --git a/internal/strava/handlers.go b/internal/strava/handlers.go index 22fe91a..1881010 100644 --- a/internal/strava/handlers.go +++ b/internal/strava/handlers.go @@ -154,7 +154,7 @@ func TokenHandler(w http.ResponseWriter, r *http.Request, db KVDB, account *ApiP qs := make(url.Values) qs.Set("username", profile.Username) qs.Set("year", strconv.Itoa(time.Now().Year())) - urlStr := "/running/?username=" + qs.Encode() + urlStr := "/running/?" + qs.Encode() log.Printf("successful token exchange, redirecting to %s", urlStr) http.Redirect(w, r, urlStr, http.StatusTemporaryRedirect) } From 78d8a5bfb6376518d3bc98187e9b2af964bd892d Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Sat, 25 Nov 2023 00:22:25 -0500 Subject: [PATCH 24/25] update resume --- cmd/webapp/static/index.html | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/webapp/static/index.html b/cmd/webapp/static/index.html index 375bb3d..7736b3f 100644 --- a/cmd/webapp/static/index.html +++ b/cmd/webapp/static/index.html @@ -41,23 +41,27 @@

Ian Rose, PhD

-

I'm a software engineer based in Atlanta, GA. I help build and run FullStory, - an awesome tool for understanding your website visitors' behavior in detail. Think: pixel-perfect playback plus powerful find-anything search.

+

I'm a software engineer based in Atlanta, GA. I help build and run Wattch, + a complete monitoring & controls hardware/software stack for mid-sized (e.g. commercial and industrial scale) solar installations.

+ +

Previously, I was a founding engineer at FullStory, where I helped grow + the company from the original 8 of us to over 500 employees.

In 2011 I graduated from Harvard with a PhD in computer science, focusing on distributed systems and in particular on wireless networks (both 802.11 and 802.15.4). I then spent a year at Google working on development tools, with an emphasis on backend development. In July of 2012, I left Google to join 6 other ex-googlers at FullStory.

-

For all of the gory details, please see my - C.V.. You can also follow my witty banter on Twitter. +

For all of the gory details, please see my (seldom updated) + C.V. or LinkedIn profile.

- Harvard Graduation + Me Camping

Experience

-

2012-present: Software Engineer / Tech Lead, FullStory

+

2022-present: Senior Engineer, Wattch

+

2012-2022: Senior Engineer / Tech Lead / Engineering Manager, FullStory

2011-2012: Software Engineer, Google

2005-2011: PhD graduate student, Harvard University (advisor: Matt Welsh)

2007: Intern, Sun Microsystems, Project Darkstar From f3a2180f2f012e97e8fbc01d90ad361526206519 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Sun, 26 Nov 2023 10:34:21 -0500 Subject: [PATCH 25/25] comments --- scripts/deploy.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index fa43576..1164f67 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -9,8 +9,9 @@ set -e # Run on initial instance creation: # > sudo apt-get update # > sudo apt-get install podman -# > sudo setcap cap_net_bind_service=+ep /usr/bin/podman -# > sudo setcap cap_net_bind_service=+ep $(which slirp4netns) +# > systemctl --user enable podman.socket +# > loginctl enable-linger ianrose14 +# add "net.ipv4.ip_unprivileged_port_start=80" to /etc/sysctl.conf # make webapp-linux