Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ jobs:
strategy:
matrix:
include:
- os: windows
runs-on: warp-windows-latest-2204-4x
- os: darwin
runs-on: macos-latest
arch: arm64
- os: linux
runs-on: ubuntu-latest
arch: amd64

steps:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@

.claude/
**/.claude/

CLAUDE.md
**/CLAUDE.md
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,33 @@ Pre-compiled binaries for various platforms are available in the [releases](http
## Running

Run ./bin/sqlrsync <params>

## Stored Settings

Settings and defaults are stored in your user directory at ~/.config/sqlrsync. Within that directory, there are two files:

1) defaults.toml
Contains default settings for all sqlrsync databases, like server URL, public/private, to generate a new unique clientSideEncryptionKey

2) local-secrets.toml
Contains this-machine-specific settings, including the path to the local SQLite files, push keys, and encryption keys.

```toml
[local]
# When a new SQLRsync Replica is created on the server, we can use this prefix to identify this machine
hostname = "homelab3"
defaultClientSideEncryptionKey = ""

[[sqlrsync-databases]]
path = "/home/matt/webapps/hedgedoc/data/data.db"
private-push-key = "abcd1234abcd1234"
lastUpdated = "2023-01-01T00:00:00Z"
clientSideEncryptionKey = ""

[[sqlrsync-databases]]
path = "/home/matt/webapps/wikijs/data/another.db"
private-push-key = "efgh5678efgh5678"
lastUpdated = "2023-01-01T00:00:00Z"
clientSideEncryptionKey = ""
```

50 changes: 35 additions & 15 deletions client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,20 +187,7 @@ func runSync(cmd *cobra.Command, args []string) error {
path := args[0]
if isLocal(path) {
// IF ORIGIN:LOCAL (no REPLICA) - push to default remote path
config, err := LoadDefaultSecretsConfig()
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to load config: %w", err)
}

dbname := filepath.Base(path)
var remotePath string
if config != nil && config.Config.DefaultPrefix != "" {
remotePath = config.Config.DefaultPrefix + "/" + dbname
} else {
remotePath = dbname
}

return runPushSync(path, remotePath)
return runPushSync(path, "")
} else {
// IF REPLICA:REMOTE (no ORIGIN) - pull to default local name
dbname := filepath.Base(path)
Expand Down Expand Up @@ -272,7 +259,7 @@ func runPushSync(localPath string, remotePath string) error {

// Check if we have a namespace push token
if config.GetPrivateToken() == "" {
fmt.Print("No namespace push token found. Please enter your namespace push token: ")
fmt.Print("A Namespace Push Key is required. Visit https://sqlrsync.com/namespaces?getkey. Enter the Namespace Push Key: ")
reader := bufio.NewReader(os.Stdin)
token, err := reader.ReadString('\n')
if err != nil {
Expand Down Expand Up @@ -423,6 +410,39 @@ func runPullSync(remotePath string, localPath string) error {
}
}

// Load or create secrets config
config, err := LoadDefaultSecretsConfig()
if err != nil {
// If config doesn't exist or parent directories don't exist, create a new one
config = &SecretsConfig{
Config: Config{},
Dbs: make(map[string]DatabaseConfig),
}
}
// Check if we have a namespace push token
if config.GetPrivateToken() == "" {
fmt.Print("A Namespace Push Key is required. Visit https://sqlrsync.com/namespaces?getkey. Enter the Namespace Push Key: ")
reader := bufio.NewReader(os.Stdin)
token, err := reader.ReadString('\n')
if err != nil {
return fmt.Errorf("failed to read namespace push token: %w", err)
}
token = strings.TrimSpace(token)

if token == "" {
return fmt.Errorf("namespace push token cannot be empty")
}

config.SetPrivateToken(token)

// Save the updated config
if err := SaveDefaultSecretsConfig(config); err != nil {
return fmt.Errorf("failed to save secrets config: %w", err)
}

fmt.Println("Namespace push token saved to ~/.config/sqlrsync/secrets.yml")
}

// Create remote client for WebSocket transport
remoteClient, err := remote.New(&remote.Config{
ServerURL: serverURL + "/pull/" + remotePath,
Expand Down
12 changes: 9 additions & 3 deletions client/remote/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ func (c *Client) Connect() error {
c.logger.Info("Connecting to remote server", zap.String("url", c.config.ServerURL))

u, err := url.Parse(c.config.ServerURL)
if err != nil {
if err != nil || !strings.HasPrefix(u.Scheme, "ws") {
fmt.Println("Server should be in the format: wss://server.com")
return fmt.Errorf("invalid server URL: %w", err)
}

Expand All @@ -252,14 +253,19 @@ func (c *Client) Connect() error {
defer connectCancel()

headers := http.Header{}
headers.Set("Authorization", c.config.AuthToken)
if c.config.AuthToken == "" || len(c.config.AuthToken) <= 20 {
return fmt.Errorf("invalid authtoken: %s", c.config.AuthToken)
} else {
headers.Set("Authorization", c.config.AuthToken)
}

conn, response, err := dialer.DialContext(connectCtx, u.String(), headers)
defer response.Body.Close()
if err != nil {
fmt.Println("Failed to connect:", err)
respStr, _ := io.ReadAll(response.Body)
return fmt.Errorf("%s", respStr)
}
defer response.Body.Close()

c.mu.Lock()
c.conn = conn
Expand Down
Loading