pico
created pr with
35.1
added 35.2
1: 77aaa29 ! 1: 8d56535 reactor(metric-drain): use caddy json format
-: ------- > 2: a336041 wip
-: ------- > 3: 7ae45b3 chore: wrap
-: ------- > 4: bfa5c4f done
added 35.3
1: 8d56535 < -: ------- reactor(metric-drain): use caddy json format
-: ------- > 1: c7eeb12 reactor(metric-drain): use caddy access logs
2: a336041 < -: ------- wip
3: 7ae45b3 < -: ------- chore: wrap
4: bfa5c4f < -: ------- done
added 35.4
1: c7eeb12 ! 1: 4e0839a reactor(metric-drain): use caddy access logs
changed status to
accepted
cmds
checkout latest patchset:
ssh pr.pico.sh print 35 | git am -3checkout any patchset in a patch request:
ssh pr.pico.sh print 35.[rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 35set PR to open (enables RSS notifications):
ssh pr.pico.sh pr open 35set PR to draft (stops RSS notifications):
ssh pr.pico.sh pr draft 35
Patchset
35.1
reactor(metric-drain): use caddy json format
Eric Bower
2024-11-15T15:02:24ZSemantic diff summary
5 added,
4 modified,
0 signature changed,
2 removed
across 4 analyzed files
(1 file skipped: unsupported file type)
+92
-4
auth/api.go
#
| ... | ... | @@ -642,6 +642,89 @@ func handler(routes []shared.Route, client *Client) http.HandlerFunc { | |
| 642 | 642 | } | |
| 643 | 643 | } | |
| 644 | 644 | ||
| 645 | + | type AccessLogReq struct { | |
| 646 | + | RemoteIP string `json:"remote_ip"` | |
| 647 | + | RemotePort string `json:"remote_port"` | |
| 648 | + | ClientIP string `json:"client_ip"` | |
| 649 | + | Method string `json:"method"` | |
| 650 | + | Host string `json:"host"` | |
| 651 | + | Uri string `json:"uri"` | |
| 652 | + | Headers struct { | |
| 653 | + | UserAgent string `json:"User-Agent"` | |
| 654 | + | Referer string `json:"Referer"` | |
| 655 | + | } `json:"headers"` | |
| 656 | + | Tls struct { | |
| 657 | + | ServerName string `json:"server_name"` | |
| 658 | + | } `json:"tls"` | |
| 659 | + | } | |
| 660 | + | ||
| 661 | + | type CaddyAccessLog struct { | |
| 662 | + | Request AccessLogReq `json:"request"` | |
| 663 | + | Status int `json:"status"` | |
| 664 | + | } | |
| 665 | + | ||
| 666 | + | func deserializeCaddyAccessLog(dbpool db.DB, access *CaddyAccessLog) (*db.AnalyticsVisits, error) { | |
| 667 | + | spaceRaw := strings.SplitN(access.Request.Tls.ServerName, ".", 2) | |
| 668 | + | space := spaceRaw[0] | |
| 669 | + | host := access.Request.Host | |
| 670 | + | path := access.Request.Uri | |
| 671 | + | subdomain := "" | |
| 672 | + | ||
| 673 | + | // grab subdomain based on host | |
| 674 | + | if strings.HasSuffix(host, "tuns.sh") { | |
| 675 | + | subdomain = strings.TrimSuffix(host, ".tuns.sh") | |
| 676 | + | } else if strings.HasSuffix(host, "pgs.sh") { | |
| 677 | + | subdomain = strings.TrimSuffix(host, ".pgs.sh") | |
| 678 | + | } else if strings.HasSuffix(host, "prose.sh") { | |
| 679 | + | subdomain = strings.TrimSuffix(host, ".prose.sh") | |
| 680 | + | } else { | |
| 681 | + | subdomain = shared.GetCustomDomain(host, space) | |
| 682 | + | } | |
| 683 | + | ||
| 684 | + | // get user and namespace details from subdomain | |
| 685 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 686 | + | if err != nil { | |
| 687 | + | return nil, err | |
| 688 | + | } | |
| 689 | + | // get user ID | |
| 690 | + | user, err := dbpool.FindUserForName(props.Username) | |
| 691 | + | if err != nil { | |
| 692 | + | return nil, err | |
| 693 | + | } | |
| 694 | + | ||
| 695 | + | projectID := "" | |
| 696 | + | postID := "" | |
| 697 | + | if space == "pgs" { // figure out project ID | |
| 698 | + | project, err := dbpool.FindProjectByName(user.ID, props.ProjectName) | |
| 699 | + | if err != nil { | |
| 700 | + | return nil, err | |
| 701 | + | } | |
| 702 | + | projectID = project.ID | |
| 703 | + | } else if space == "prose" { // figure out post ID | |
| 704 | + | if path == "" || path == "/" { | |
| 705 | + | } else { | |
| 706 | + | post, err := dbpool.FindPostWithSlug(path, user.ID, space) | |
| 707 | + | if err != nil { | |
| 708 | + | return nil, err | |
| 709 | + | } | |
| 710 | + | postID = post.ID | |
| 711 | + | } | |
| 712 | + | } | |
| 713 | + | ||
| 714 | + | return &db.AnalyticsVisits{ | |
| 715 | + | UserID: user.ID, | |
| 716 | + | ProjectID: projectID, | |
| 717 | + | PostID: postID, | |
| 718 | + | Namespace: space, | |
| 719 | + | Host: host, | |
| 720 | + | Path: path, | |
| 721 | + | IpAddress: access.Request.ClientIP, | |
| 722 | + | UserAgent: access.Request.Headers.UserAgent, | |
| 723 | + | Referer: access.Request.Headers.Referer, // TODO: I don't see referer in the access log | |
| 724 | + | Status: access.Status, | |
| 725 | + | }, nil | |
| 726 | + | } | |
| 727 | + | ||
| 645 | 728 | func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secret string) { | |
| 646 | 729 | conn := shared.NewPicoPipeClient() | |
| 647 | 730 | stdoutPipe, err := pubsub.RemoteSub("sub metric-drain -k", ctx, conn) |
| ... | ... | @@ -654,14 +737,19 @@ func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secr | |
| 654 | 737 | scanner := bufio.NewScanner(stdoutPipe) | |
| 655 | 738 | for scanner.Scan() { | |
| 656 | 739 | line := scanner.Text() | |
| 657 | - | visit := db.AnalyticsVisits{} | |
| 658 | - | err := json.Unmarshal([]byte(line), &visit) | |
| 740 | + | accessLog := CaddyAccessLog{} | |
| 741 | + | err := json.Unmarshal([]byte(line), &accessLog) | |
| 659 | 742 | if err != nil { | |
| 660 | 743 | logger.Error("json unmarshal", "err", err) | |
| 661 | 744 | continue | |
| 662 | 745 | } | |
| 663 | 746 | ||
| 664 | - | err = shared.AnalyticsVisitFromVisit(&visit, dbpool, secret) | |
| 747 | + | visit, err := deserializeCaddyAccessLog(dbpool, &accessLog) | |
| 748 | + | if err != nil { | |
| 749 | + | logger.Error("cannot deserialize access log", "err", err) | |
| 750 | + | continue | |
| 751 | + | } | |
| 752 | + | err = shared.AnalyticsVisitFromVisit(visit, dbpool, secret) | |
| 665 | 753 | if err != nil { | |
| 666 | 754 | if !errors.Is(err, shared.ErrAnalyticsDisabled) { | |
| 667 | 755 | logger.Info("could not record analytics visit", "reason", err) |
| ... | ... | @@ -669,7 +757,7 @@ func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secr | |
| 669 | 757 | } | |
| 670 | 758 | ||
| 671 | 759 | logger.Info("inserting visit", "visit", visit) | |
| 672 | - | err = dbpool.InsertVisit(&visit) | |
| 760 | + | err = dbpool.InsertVisit(visit) | |
| 673 | 761 | if err != nil { | |
| 674 | 762 | logger.Error("could not insert visit record", "err", err) | |
| 675 | 763 | } |
+40
-0
caddy.json
#
| ... | ... | @@ -0,0 +1,40 @@ | |
| 1 | + | { | |
| 2 | + | "level": "info", | |
| 3 | + | "ts": 1731644477.313701, | |
| 4 | + | "logger": "http.log.access", | |
| 5 | + | "msg": "handled request", | |
| 6 | + | "request": { | |
| 7 | + | "remote_ip": "127.0.0.1", | |
| 8 | + | "remote_port": "40400", | |
| 9 | + | "client_ip": "127.0.0.1", | |
| 10 | + | "proto": "HTTP/2.0", | |
| 11 | + | "method": "GET", | |
| 12 | + | "host": "pgs.sh", | |
| 13 | + | "uri": "/", | |
| 14 | + | "headers": { "User-Agent": ["Blackbox Exporter/0.24.0"] }, | |
| 15 | + | "tls": { | |
| 16 | + | "resumed": false, | |
| 17 | + | "version": 772, | |
| 18 | + | "cipher_suite": 4865, | |
| 19 | + | "proto": "h2", | |
| 20 | + | "server_name": "pgs.sh" | |
| 21 | + | } | |
| 22 | + | }, | |
| 23 | + | "bytes_read": 0, | |
| 24 | + | "user_id": "", | |
| 25 | + | "duration": 0.001207084, | |
| 26 | + | "size": 3718, | |
| 27 | + | "status": 200, | |
| 28 | + | "resp_headers": { | |
| 29 | + | "Referrer-Policy": ["no-referrer-when-downgrade"], | |
| 30 | + | "Strict-Transport-Security": ["max-age=31536000;"], | |
| 31 | + | "X-Content-Type-Options": ["nosniff"], | |
| 32 | + | "X-Frame-Options": ["DENY"], | |
| 33 | + | "Server": ["Caddy"], | |
| 34 | + | "Alt-Svc": ["h3=\":443\"; ma=2592000"], | |
| 35 | + | "Date": ["Fri, 15 Nov 2024 04:21:17 GMT"], | |
| 36 | + | "Content-Type": ["text/html; charset=utf-8"], | |
| 37 | + | "X-Xss-Protection": ["1; mode=block"], | |
| 38 | + | "Permissions-Policy": ["interest-cohort=()"] | |
| 39 | + | } | |
| 40 | + | } |
+1
-1
pgs/tunnel.go
#
| ... | ... | @@ -51,7 +51,7 @@ func createHttpHandler(apiConfig *shared.ApiConfig) CtxHttpBridge { | |
| 51 | 51 | "pubkey", pubkeyStr, | |
| 52 | 52 | ) | |
| 53 | 53 | ||
| 54 | - | props, err := getProjectFromSubdomain(subdomain) | |
| 54 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 55 | 55 | if err != nil { | |
| 56 | 56 | log.Error(err.Error()) | |
| 57 | 57 | return http.HandlerFunc(shared.UnauthorizedHandler) |
+2
-19
pgs/web.go
#
| ... | ... | @@ -174,7 +174,7 @@ func (web *WebRouter) checkHandler(w http.ResponseWriter, r *http.Request) { | |
| 174 | 174 | ||
| 175 | 175 | if !strings.Contains(hostDomain, appDomain) { | |
| 176 | 176 | subdomain := shared.GetCustomDomain(hostDomain, cfg.Space) | |
| 177 | - | props, err := getProjectFromSubdomain(subdomain) | |
| 177 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 178 | 178 | if err != nil { | |
| 179 | 179 | logger.Error( | |
| 180 | 180 | "could not get project from subdomain", |
| ... | ... | @@ -330,7 +330,7 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro | |
| 330 | 330 | "host", r.Host, | |
| 331 | 331 | ) | |
| 332 | 332 | ||
| 333 | - | props, err := getProjectFromSubdomain(subdomain) | |
| 333 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 334 | 334 | if err != nil { | |
| 335 | 335 | logger.Info( | |
| 336 | 336 | "could not determine project from subdomain", |
| ... | ... | @@ -447,20 +447,3 @@ func (web *WebRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
| 447 | 447 | ctx = context.WithValue(ctx, shared.CtxSubdomainKey{}, subdomain) | |
| 448 | 448 | router.ServeHTTP(w, r.WithContext(ctx)) | |
| 449 | 449 | } | |
| 450 | - | ||
| 451 | - | type SubdomainProps struct { | |
| 452 | - | ProjectName string | |
| 453 | - | Username string | |
| 454 | - | } | |
| 455 | - | ||
| 456 | - | func getProjectFromSubdomain(subdomain string) (*SubdomainProps, error) { | |
| 457 | - | props := &SubdomainProps{} | |
| 458 | - | strs := strings.SplitN(subdomain, "-", 2) | |
| 459 | - | props.Username = strs[0] | |
| 460 | - | if len(strs) == 2 { | |
| 461 | - | props.ProjectName = strs[1] | |
| 462 | - | } else { | |
| 463 | - | props.ProjectName = props.Username | |
| 464 | - | } | |
| 465 | - | return props, nil | |
| 466 | - | } |