mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-23 02:51:26 +01:00
send telemetry data to pcloud. pcloud dev settings (with PCLOUD_ENDPOINT). /client:show and /client:set commands (for no-telemetry)
This commit is contained in:
parent
dc051beeb8
commit
ea897bf53c
@ -406,8 +406,13 @@ func test() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func doBeforeClose() {
|
||||
pcloud.SendTelemetry()
|
||||
func sendTelemetryWrapper() {
|
||||
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancelFn()
|
||||
err := pcloud.SendTelemetry(ctx)
|
||||
if err != nil {
|
||||
log.Printf("[error] sending telemetry: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// watch stdin, kill server if stdin is closed
|
||||
@ -417,7 +422,7 @@ func stdinReadWatch() {
|
||||
_, err := os.Stdin.Read(buf)
|
||||
if err != nil {
|
||||
log.Printf("stdin closed/error, shutting down: %v\n", err)
|
||||
doBeforeClose()
|
||||
sendTelemetryWrapper()
|
||||
time.Sleep(1 * time.Second)
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||
}
|
||||
@ -491,6 +496,15 @@ func main() {
|
||||
log.Printf("[error] resetting window focus: %v\n", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("PCLOUD_ENDPOINT=%s\n", pcloud.GetEndpoint())
|
||||
time.Sleep(1 * time.Minute)
|
||||
for {
|
||||
sendTelemetryWrapper()
|
||||
// send new telemetry every 8-hours
|
||||
time.Sleep(8 * time.Hour)
|
||||
}
|
||||
}()
|
||||
go stdinReadWatch()
|
||||
go runWebSocketServer()
|
||||
gr := mux.NewRouter()
|
||||
|
@ -1 +1,3 @@
|
||||
DROP TABLE activity;
|
||||
|
||||
ALTER TABLE client DROP COLUMN clientopts;
|
||||
|
@ -10,3 +10,5 @@ CREATE TABLE activity (
|
||||
clientversion varchar(20) NOT NULL,
|
||||
clientarch varchar(20) NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE client ADD COLUMN clientopts json NOT NULL DEFAULT '';
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/comp"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/pcloud"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||
@ -159,6 +160,7 @@ func init() {
|
||||
|
||||
registerCmdFn("client", ClientCommand)
|
||||
registerCmdFn("client:set", ClientSetCommand)
|
||||
registerCmdFn("client:show", ClientShowCommand)
|
||||
|
||||
registerCmdFn("history", HistoryCommand)
|
||||
|
||||
@ -2083,11 +2085,71 @@ func KillServerCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
|
||||
}
|
||||
|
||||
func ClientCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
return nil, fmt.Errorf("/client requires a subcommand: %s", formatStrs([]string{"set"}, "or", false))
|
||||
return nil, fmt.Errorf("/client requires a subcommand: %s", formatStrs([]string{"set", "show"}, "or", false))
|
||||
}
|
||||
|
||||
func boolToStr(v bool, trueStr string, falseStr string) string {
|
||||
if v {
|
||||
return trueStr
|
||||
}
|
||||
return falseStr
|
||||
}
|
||||
|
||||
func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot retrieve client data: %v\n", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "userid", clientData.UserId))
|
||||
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "clientid", clientData.ClientId))
|
||||
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "telemetry", boolToStr(clientData.ClientOpts.NoTelemetry, "off", "on")))
|
||||
update := sstore.ModelUpdate{
|
||||
Info: &sstore.InfoMsgType{
|
||||
InfoTitle: fmt.Sprintf("client info"),
|
||||
InfoLines: splitLinesForInfo(buf.String()),
|
||||
},
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot retrieve client data: %v\n", err)
|
||||
}
|
||||
var varsUpdated []string
|
||||
if pk.Kwargs["telemetry"] != "" {
|
||||
noTelemetry := !resolveBool(pk.Kwargs["telemetry"], true)
|
||||
if clientData.ClientOpts.NoTelemetry != noTelemetry {
|
||||
clientOpts := clientData.ClientOpts
|
||||
clientOpts.NoTelemetry = noTelemetry
|
||||
err = sstore.SetClientOpts(ctx, clientOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error trying to update client telemetry: %v", err)
|
||||
}
|
||||
log.Printf("client telemetry setting updated to %v\n", !noTelemetry)
|
||||
err = pcloud.SendNoTelemetryUpdate(ctx, noTelemetry)
|
||||
if err != nil {
|
||||
// ignore error, just log
|
||||
log.Printf("[error] sending no-telemetry update: %v\n", err)
|
||||
log.Printf("note that telemetry update has still taken effect locally, and will be respected by the client\n")
|
||||
}
|
||||
} else {
|
||||
log.Printf("client telemetry setting unchanged, is %v\n", !noTelemetry)
|
||||
}
|
||||
varsUpdated = append(varsUpdated, "telemetry")
|
||||
}
|
||||
if len(varsUpdated) == 0 {
|
||||
return nil, fmt.Errorf("/client:set no updates, can set %s", formatStrs([]string{"telemetry"}, "or", false))
|
||||
}
|
||||
update := sstore.ModelUpdate{
|
||||
Info: &sstore.InfoMsgType{
|
||||
InfoMsg: fmt.Sprintf("client updated %s", formatStrs(varsUpdated, "and", false)),
|
||||
TimeoutMs: 2000,
|
||||
},
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func formatTermOpts(termOpts sstore.TermOpts) string {
|
||||
|
@ -1,5 +1,132 @@
|
||||
package pcloud
|
||||
|
||||
func SendTelemetry() error {
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||
)
|
||||
|
||||
const PCloudEndpoint = "https://api.getprompt.dev/central"
|
||||
const PCloudEndpointVarName = "PCLOUD_ENDPOINT"
|
||||
const APIVersion = 1
|
||||
|
||||
type NoTelemetryInputType struct {
|
||||
ClientId string `json:"clientid"`
|
||||
Value bool `json:"value"`
|
||||
}
|
||||
|
||||
type TelemetryInputType struct {
|
||||
UserId string `json:"userid"`
|
||||
ClientId string `json:"clientid"`
|
||||
Activity []*sstore.ActivityType `json:"activity"`
|
||||
}
|
||||
|
||||
func GetEndpoint() string {
|
||||
if !scbase.IsDevMode() {
|
||||
return PCloudEndpoint
|
||||
}
|
||||
endpoint := os.Getenv(PCloudEndpointVarName)
|
||||
if endpoint == "" || !strings.HasPrefix(endpoint, "https://") {
|
||||
panic("Invalid PCloud dev endpoint, PCLOUD_ENDPOINT not set or invalid")
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
||||
func makePostReq(ctx context.Context, apiUrl string, data interface{}) (*http.Request, error) {
|
||||
var dataReader io.Reader
|
||||
if data != nil {
|
||||
byteArr, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling json for %s request: %v", apiUrl, err)
|
||||
}
|
||||
dataReader = bytes.NewReader(byteArr)
|
||||
}
|
||||
fullUrl := GetEndpoint() + apiUrl
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", fullUrl, dataReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating %s request: %v", apiUrl, err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-PromptAPIVersion", strconv.Itoa(APIVersion))
|
||||
req.Header.Set("X-PromptAPIUrl", apiUrl)
|
||||
req.Close = true
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func doRequest(req *http.Request, outputObj interface{}) (*http.Response, error) {
|
||||
apiUrl := req.Header.Get("X-PromptAPIUrl")
|
||||
log.Printf("sending request %v\n", req.URL)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error contacting pcloud %q service: %v", apiUrl, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("error reading %q response body: %v", apiUrl, err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return resp, fmt.Errorf("error contacting pcloud %q service: %s", apiUrl, resp.Status)
|
||||
}
|
||||
if outputObj != nil && resp.Header.Get("Content-Type") == "application/json" {
|
||||
err = json.Unmarshal(bodyBytes, outputObj)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("error decoding json: %v", err)
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func SendTelemetry(ctx context.Context) error {
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot retrieve client data: %v", err)
|
||||
}
|
||||
if clientData.ClientOpts.NoTelemetry {
|
||||
return nil
|
||||
}
|
||||
activity, err := sstore.GetNonUploadedActivity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get activity: %v", err)
|
||||
}
|
||||
if len(activity) == 0 {
|
||||
return nil
|
||||
}
|
||||
log.Printf("sending telemetry data\n")
|
||||
input := TelemetryInputType{UserId: clientData.UserId, ClientId: clientData.ClientId, Activity: activity}
|
||||
req, err := makePostReq(ctx, "/telemetry", input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = doRequest(req, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SendNoTelemetryUpdate(ctx context.Context, noTelemetryVal bool) error {
|
||||
clientData, err := sstore.EnsureClientData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot retrieve client data: %v", err)
|
||||
}
|
||||
req, err := makePostReq(ctx, "/no-telemetry", NoTelemetryInputType{ClientId: clientData.ClientId, Value: noTelemetryVal})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = doRequest(req, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
@ -99,12 +100,18 @@ func MigratePrintVersion() error {
|
||||
func MigrateCommandOpts(opts []string) error {
|
||||
var err error
|
||||
if opts[0] == "--migrate-up" {
|
||||
fmt.Printf("migrate-up %v\n", GetSessionDBName())
|
||||
time.Sleep(3 * time.Second)
|
||||
err = MigrateUp()
|
||||
} else if opts[0] == "--migrate-down" {
|
||||
fmt.Printf("migrate-down %v\n", GetSessionDBName())
|
||||
time.Sleep(3 * time.Second)
|
||||
err = MigrateDown()
|
||||
} else if opts[0] == "--migrate-goto" {
|
||||
n, err := strconv.Atoi(opts[1])
|
||||
if err == nil {
|
||||
fmt.Printf("migrate-goto %v => %d\n", GetSessionDBName(), n)
|
||||
time.Sleep(3 * time.Second)
|
||||
err = MigrateGoto(uint(n))
|
||||
}
|
||||
} else {
|
||||
|
@ -132,6 +132,10 @@ type ActivityType struct {
|
||||
ClientArch string `json:"clientarch"`
|
||||
}
|
||||
|
||||
type ClientOptsType struct {
|
||||
NoTelemetry bool `json:"notelemetry,omitempty"`
|
||||
}
|
||||
|
||||
type ClientData struct {
|
||||
ClientId string `json:"clientid"`
|
||||
UserId string `json:"userid"`
|
||||
@ -141,6 +145,7 @@ type ClientData struct {
|
||||
UserPublicKey *ecdsa.PublicKey `json:"-"`
|
||||
ActiveSessionId string `json:"activesessionid"`
|
||||
WinSize ClientWinSizeType `json:"winsize"`
|
||||
ClientOpts ClientOptsType `json:"clientopts"`
|
||||
}
|
||||
|
||||
func (c *ClientData) ToMap() map[string]interface{} {
|
||||
@ -151,6 +156,7 @@ func (c *ClientData) ToMap() map[string]interface{} {
|
||||
rtn["userpublickeybytes"] = c.UserPublicKeyBytes
|
||||
rtn["activesessionid"] = c.ActiveSessionId
|
||||
rtn["winsize"] = quickJson(c.WinSize)
|
||||
rtn["clientopts"] = quickJson(c.ClientOpts)
|
||||
return rtn
|
||||
}
|
||||
|
||||
@ -165,6 +171,7 @@ func ClientDataFromMap(m map[string]interface{}) *ClientData {
|
||||
quickSetBytes(&c.UserPublicKeyBytes, m, "userpublickeybytes")
|
||||
quickSetStr(&c.ActiveSessionId, m, "activesessionid")
|
||||
quickSetJson(&c.WinSize, m, "winsize")
|
||||
quickSetJson(&c.ClientOpts, m, "clientopts")
|
||||
return &c
|
||||
}
|
||||
|
||||
@ -1005,3 +1012,12 @@ func EnsureClientData(ctx context.Context) (*ClientData, error) {
|
||||
}
|
||||
return &rtn, nil
|
||||
}
|
||||
|
||||
func SetClientOpts(ctx context.Context, clientOpts ClientOptsType) error {
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `UPDATE client SET clientopts = ?`
|
||||
tx.ExecWrap(query, quickJson(clientOpts))
|
||||
return nil
|
||||
})
|
||||
return txErr
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user