mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07: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:
+17
-3
@@ -406,8 +406,13 @@ func test() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func doBeforeClose() {
|
func sendTelemetryWrapper() {
|
||||||
pcloud.SendTelemetry()
|
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
|
// watch stdin, kill server if stdin is closed
|
||||||
@@ -417,7 +422,7 @@ func stdinReadWatch() {
|
|||||||
_, err := os.Stdin.Read(buf)
|
_, err := os.Stdin.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("stdin closed/error, shutting down: %v\n", err)
|
log.Printf("stdin closed/error, shutting down: %v\n", err)
|
||||||
doBeforeClose()
|
sendTelemetryWrapper()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||||
}
|
}
|
||||||
@@ -491,6 +496,15 @@ func main() {
|
|||||||
log.Printf("[error] resetting window focus: %v\n", err)
|
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 stdinReadWatch()
|
||||||
go runWebSocketServer()
|
go runWebSocketServer()
|
||||||
gr := mux.NewRouter()
|
gr := mux.NewRouter()
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
DROP TABLE activity;
|
DROP TABLE activity;
|
||||||
|
|
||||||
|
ALTER TABLE client DROP COLUMN clientopts;
|
||||||
|
|||||||
@@ -10,3 +10,5 @@ CREATE TABLE activity (
|
|||||||
clientversion varchar(20) NOT NULL,
|
clientversion varchar(20) NOT NULL,
|
||||||
clientarch 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/packet"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/comp"
|
"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/remote"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||||
@@ -159,6 +160,7 @@ func init() {
|
|||||||
|
|
||||||
registerCmdFn("client", ClientCommand)
|
registerCmdFn("client", ClientCommand)
|
||||||
registerCmdFn("client:set", ClientSetCommand)
|
registerCmdFn("client:set", ClientSetCommand)
|
||||||
|
registerCmdFn("client:show", ClientShowCommand)
|
||||||
|
|
||||||
registerCmdFn("history", HistoryCommand)
|
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) {
|
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) {
|
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 {
|
func formatTermOpts(termOpts sstore.TermOpts) string {
|
||||||
|
|||||||
+128
-1
@@ -1,5 +1,132 @@
|
|||||||
package pcloud
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
@@ -99,12 +100,18 @@ func MigratePrintVersion() error {
|
|||||||
func MigrateCommandOpts(opts []string) error {
|
func MigrateCommandOpts(opts []string) error {
|
||||||
var err error
|
var err error
|
||||||
if opts[0] == "--migrate-up" {
|
if opts[0] == "--migrate-up" {
|
||||||
|
fmt.Printf("migrate-up %v\n", GetSessionDBName())
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
err = MigrateUp()
|
err = MigrateUp()
|
||||||
} else if opts[0] == "--migrate-down" {
|
} else if opts[0] == "--migrate-down" {
|
||||||
|
fmt.Printf("migrate-down %v\n", GetSessionDBName())
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
err = MigrateDown()
|
err = MigrateDown()
|
||||||
} else if opts[0] == "--migrate-goto" {
|
} else if opts[0] == "--migrate-goto" {
|
||||||
n, err := strconv.Atoi(opts[1])
|
n, err := strconv.Atoi(opts[1])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
fmt.Printf("migrate-goto %v => %d\n", GetSessionDBName(), n)
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
err = MigrateGoto(uint(n))
|
err = MigrateGoto(uint(n))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ type ActivityType struct {
|
|||||||
ClientArch string `json:"clientarch"`
|
ClientArch string `json:"clientarch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientOptsType struct {
|
||||||
|
NoTelemetry bool `json:"notelemetry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClientData struct {
|
type ClientData struct {
|
||||||
ClientId string `json:"clientid"`
|
ClientId string `json:"clientid"`
|
||||||
UserId string `json:"userid"`
|
UserId string `json:"userid"`
|
||||||
@@ -141,6 +145,7 @@ type ClientData struct {
|
|||||||
UserPublicKey *ecdsa.PublicKey `json:"-"`
|
UserPublicKey *ecdsa.PublicKey `json:"-"`
|
||||||
ActiveSessionId string `json:"activesessionid"`
|
ActiveSessionId string `json:"activesessionid"`
|
||||||
WinSize ClientWinSizeType `json:"winsize"`
|
WinSize ClientWinSizeType `json:"winsize"`
|
||||||
|
ClientOpts ClientOptsType `json:"clientopts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientData) ToMap() map[string]interface{} {
|
func (c *ClientData) ToMap() map[string]interface{} {
|
||||||
@@ -151,6 +156,7 @@ func (c *ClientData) ToMap() map[string]interface{} {
|
|||||||
rtn["userpublickeybytes"] = c.UserPublicKeyBytes
|
rtn["userpublickeybytes"] = c.UserPublicKeyBytes
|
||||||
rtn["activesessionid"] = c.ActiveSessionId
|
rtn["activesessionid"] = c.ActiveSessionId
|
||||||
rtn["winsize"] = quickJson(c.WinSize)
|
rtn["winsize"] = quickJson(c.WinSize)
|
||||||
|
rtn["clientopts"] = quickJson(c.ClientOpts)
|
||||||
return rtn
|
return rtn
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +171,7 @@ func ClientDataFromMap(m map[string]interface{}) *ClientData {
|
|||||||
quickSetBytes(&c.UserPublicKeyBytes, m, "userpublickeybytes")
|
quickSetBytes(&c.UserPublicKeyBytes, m, "userpublickeybytes")
|
||||||
quickSetStr(&c.ActiveSessionId, m, "activesessionid")
|
quickSetStr(&c.ActiveSessionId, m, "activesessionid")
|
||||||
quickSetJson(&c.WinSize, m, "winsize")
|
quickSetJson(&c.WinSize, m, "winsize")
|
||||||
|
quickSetJson(&c.ClientOpts, m, "clientopts")
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1005,3 +1012,12 @@ func EnsureClientData(ctx context.Context) (*ClientData, error) {
|
|||||||
}
|
}
|
||||||
return &rtn, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user