mirror of
https://github.com/Mikescher/kpsync.git
synced 2025-08-25 08:38:03 +02:00
non-functional wip state
This commit is contained in:
commit
e7293464c1
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
########## GOLAND ##########
|
||||
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea/**/aws.xml
|
||||
.idea/**/contentModel.xml
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
.idea/**/mongoSettings.xml
|
||||
*.iws
|
||||
atlassian-ide-plugin.xml
|
||||
.idea/httpRequests
|
||||
.idea/caches/build_file_checksums.ser
|
||||
.idea/$CACHE_FILE$
|
||||
|
||||
########## Linux ##########
|
||||
|
||||
*~
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
.nfs*
|
||||
|
||||
########## Custom ##########
|
||||
|
||||
|
||||
_out/*
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
9
.idea/kpsync.iml
generated
Normal file
9
.idea/kpsync.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/kpsync.iml" filepath="$PROJECT_DIR$/.idea/kpsync.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
43
Makefile
Normal file
43
Makefile
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
# ./firefox-sync-client
|
||||
#########################
|
||||
|
||||
build: enums
|
||||
CGO_ENABLED=0 go build -o _out/kpsync ./cmd/kpsync
|
||||
|
||||
run: build
|
||||
./_out/ffsclient
|
||||
|
||||
clean:
|
||||
go clean
|
||||
rm -rf ./_out/*
|
||||
|
||||
enums:
|
||||
go generate ./...
|
||||
|
||||
package:
|
||||
#
|
||||
# Manually do beforehand:
|
||||
# - Update version in version.go
|
||||
# - Create tag
|
||||
# - Commit
|
||||
#
|
||||
|
||||
go clean
|
||||
rm -rf ./_out/*
|
||||
|
||||
GOARCH=386 GOOS=linux CGO_ENABLED=0 go build -o _out/kpsync_linux-386-static ./cmd/cli # Linux - 32 bit
|
||||
GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -o _out/kpsync_linux-amd64-static ./cmd/cli # Linux - 64 bit
|
||||
GOARCH=arm64 GOOS=linux CGO_ENABLED=0 go build -o _out/kpsync_linux-arm64-static ./cmd/cli # Linux - ARM
|
||||
GOARCH=386 GOOS=linux go build -o _out/kpsync_linux-386 ./cmd/cli # Linux - 32 bit
|
||||
GOARCH=amd64 GOOS=linux go build -o _out/kpsync_linux-amd64 ./cmd/cli # Linux - 64 bit
|
||||
GOARCH=arm64 GOOS=linux go build -o _out/kpsync_linux-arm64 ./cmd/cli # Linux - ARM
|
||||
GOARCH=386 GOOS=windows go build -o _out/kpsync_win-386.exe -tags timetzdata -ldflags "-w -s" ./cmd/cli # Windows - 32 bit
|
||||
GOARCH=amd64 GOOS=windows go build -o _out/kpsync_win-amd64.exe -tags timetzdata -ldflags "-w -s" ./cmd/cli # Windows - 64 bit
|
||||
GOARCH=arm64 GOOS=windows go build -o _out/kpsync_win-arm64.exe -tags timetzdata -ldflags "-w -s" ./cmd/cli # Windows - ARM
|
||||
GOARCH=amd64 GOOS=darwin go build -o _out/kpsync_macos-amd64 ./cmd/cli # macOS - 32 bit
|
||||
GOARCH=amd64 GOOS=darwin go build -o _out/kpsync_macos-amd64 ./cmd/cli # macOS - 64 bit
|
||||
GOARCH=amd64 GOOS=openbsd go build -o _out/kpsync_openbsd-amd64 ./cmd/cli # OpenBSD - 64 bit
|
||||
GOARCH=arm64 GOOS=openbsd go build -o _out/kpsync_openbsd-arm64 ./cmd/cli # OpenBSD - ARM
|
||||
GOARCH=amd64 GOOS=freebsd go build -o _out/kpsync_freebsd-amd64 ./cmd/cli # FreeBSD - 64 bit
|
||||
GOARCH=arm64 GOOS=freebsd go build -o _out/kpsync_freebsd-arm64 ./cmd/cli # FreeBSD - ARM
|
19
TODO.md
Normal file
19
TODO.md
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
- log to linux fd (pipe) to open terminal
|
||||
|
||||
- show state in tray
|
||||
|
||||
- tray menu
|
||||
- force sync
|
||||
- ~ current Etag
|
||||
- ~ current SHA
|
||||
- ~ last sync ts
|
||||
- check sync
|
||||
- quit
|
||||
- show log (open terminal /w log)
|
||||
|
||||
- config via json + params override
|
||||
|
||||
- colorful log
|
||||
- download/upload progress log
|
73
app/application.go
Normal file
73
app/application.go
Normal file
@ -0,0 +1,73 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"fyne.io/systray"
|
||||
"mikescher.com/kpsync"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
config kpsync.Config
|
||||
|
||||
trayReady bool
|
||||
sigStopChan chan bool
|
||||
sigErrChan chan error
|
||||
|
||||
dbFile string
|
||||
stateFile string
|
||||
}
|
||||
|
||||
func NewApplication() *Application {
|
||||
|
||||
cfg := kpsync.LoadConfig()
|
||||
|
||||
return &Application{
|
||||
config: cfg,
|
||||
trayReady: false,
|
||||
sigStopChan: make(chan bool, 128),
|
||||
sigErrChan: make(chan error, 128),
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Application) Run() {
|
||||
|
||||
go func() { app.initTray() }()
|
||||
|
||||
go func() {
|
||||
err := app.initSync()
|
||||
if err != nil {
|
||||
app.sigErrChan <- err
|
||||
return
|
||||
}
|
||||
err = app.runSyncLoop()
|
||||
if err != nil {
|
||||
app.sigErrChan <- err
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
sigTerm := make(chan os.Signal, 1)
|
||||
signal.Notify(sigTerm, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case <-sigTerm:
|
||||
|
||||
// TODO term
|
||||
|
||||
case _ = <-app.sigErrChan:
|
||||
|
||||
// TODO stop
|
||||
|
||||
case _ = <-app.sigStopChan:
|
||||
|
||||
// TODO stop
|
||||
}
|
||||
|
||||
if app.trayReady {
|
||||
systray.Quit()
|
||||
}
|
||||
|
||||
}
|
67
app/sync.go
Normal file
67
app/sync.go
Normal file
@ -0,0 +1,67 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
||||
"mikescher.com/kpsync/assets"
|
||||
"mikescher.com/kpsync/log"
|
||||
)
|
||||
|
||||
func (app *Application) initSync() error {
|
||||
|
||||
err := os.MkdirAll(app.config.WorkDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return exerr.Wrap(err, "").Build()
|
||||
}
|
||||
|
||||
app.dbFile = path.Join(app.config.WorkDir, path.Base(app.config.LocalFallback))
|
||||
app.stateFile = path.Join(app.config.WorkDir, "kpsync.state")
|
||||
|
||||
state := app.readState()
|
||||
|
||||
needsDownload := true
|
||||
|
||||
if state != nil && fileExists(app.dbFile) {
|
||||
localCS, err := app.calcLocalChecksum()
|
||||
if err != nil {
|
||||
log.LogError("Failed to calculate local database checksum", err)
|
||||
} else if localCS == state.Checksum {
|
||||
remoteETag, err := app.getRemoteETag()
|
||||
if err != nil {
|
||||
log.LogError("Failed to get remote ETag", err)
|
||||
} else if remoteETag == state.ETag {
|
||||
|
||||
log.LogInfo(fmt.Sprintf("Found local database matching remote database - skip initial download"))
|
||||
log.LogInfo(fmt.Sprintf("Checksum (cached) := %s", state.Checksum))
|
||||
log.LogInfo(fmt.Sprintf("Checksum (local) := %s", localCS))
|
||||
log.LogInfo(fmt.Sprintf("ETag (cached) := %s", state.ETag))
|
||||
log.LogInfo(fmt.Sprintf("ETag (remote) := %s", remoteETag))
|
||||
needsDownload = false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if needsDownload {
|
||||
func() {
|
||||
fin := app.setTrayState("Downloading database", assets.IconDefault)
|
||||
defer fin()
|
||||
|
||||
log.LogInfo(fmt.Sprintf("Downloading remote database to %s", app.dbFile))
|
||||
|
||||
etag, err := app.downloadDatabase()
|
||||
if err != nil {
|
||||
log.LogError("Failed to download remote database", err)
|
||||
app.sigErrChan <- exerr.Wrap(err, "Failed to download remote database").Build()
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Application) runSyncLoop() error {
|
||||
|
||||
}
|
20
app/tray.go
Normal file
20
app/tray.go
Normal file
@ -0,0 +1,20 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/systray"
|
||||
"mikescher.com/kpsync/assets"
|
||||
)
|
||||
|
||||
func (app *Application) initTray() {
|
||||
|
||||
trayOnReady := func() {
|
||||
|
||||
systray.SetIcon(assets.IconInit)
|
||||
systray.SetTitle("KeepassXC Sync")
|
||||
systray.SetTooltip("Initializing...")
|
||||
|
||||
app.trayReady = true
|
||||
}
|
||||
|
||||
systray.Run(trayOnReady, nil)
|
||||
}
|
47
app/utils.go
Normal file
47
app/utils.go
Normal file
@ -0,0 +1,47 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/cryptext"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
||||
)
|
||||
|
||||
func fileExists(p string) bool {
|
||||
_, err := os.Stat(p)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type State struct {
|
||||
ETag string `json:"etag"`
|
||||
Size int64 `json:"size"`
|
||||
Checksum string `json:"checksum"`
|
||||
}
|
||||
|
||||
func (app *Application) readState() *State {
|
||||
bin, err := os.ReadFile(app.stateFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var state State
|
||||
err = json.Unmarshal(bin, &state)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &state
|
||||
}
|
||||
|
||||
func (app *Application) calcLocalChecksum() (string, error) {
|
||||
bin, err := os.ReadFile(app.dbFile)
|
||||
if err != nil {
|
||||
return "", exerr.Wrap(err, "").Build()
|
||||
}
|
||||
|
||||
return cryptext.BytesSha256(bin), nil
|
||||
}
|
5
app/webdav.go
Normal file
5
app/webdav.go
Normal file
@ -0,0 +1,5 @@
|
||||
package app
|
||||
|
||||
func (app *Application) initSync() {
|
||||
|
||||
}
|
14
assets/assets.go
Normal file
14
assets/assets.go
Normal file
@ -0,0 +1,14 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed IconInit.png
|
||||
var IconInit []byte
|
||||
|
||||
//go:embed iconDefault.png
|
||||
var IconDefault []byte
|
||||
|
||||
//go:embed iconDownload.png
|
||||
var IconDownload []byte
|
BIN
assets/iconDefault.png
Normal file
BIN
assets/iconDefault.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
17
cmd/cli/main.go
Normal file
17
cmd/cli/main.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/exerr"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||
"mikescher.com/kpsync/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
exerr.Init(exerr.ErrorPackageConfigInit{
|
||||
ZeroLogErrTraces: langext.PFalse,
|
||||
ZeroLogAllTraces: langext.PFalse,
|
||||
})
|
||||
|
||||
kpApp := app.NewApplication()
|
||||
kpApp.Run()
|
||||
}
|
104
config.go
Normal file
104
config.go
Normal file
@ -0,0 +1,104 @@
|
||||
package kpsync
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||
"mikescher.com/kpsync/log"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
WebDAVURL string `json:"webdav_url"`
|
||||
WebDAVUser string `json:"webdav_user"`
|
||||
WebDAVPass string `json:"webdav_pass"`
|
||||
|
||||
LocalFallback string `json:"local_fallback"`
|
||||
|
||||
WorkDir string `json:"work_dir"`
|
||||
|
||||
Debounce int `json:"debounce"`
|
||||
}
|
||||
|
||||
func LoadConfig() Config {
|
||||
var configPath string
|
||||
flag.StringVar(&configPath, "config", "~/.config/kpsync.json", "Path to the configuration file")
|
||||
|
||||
var webdavURL string
|
||||
flag.StringVar(&webdavURL, "webdav_url", "", "WebDAV URL")
|
||||
|
||||
var webdavUser string
|
||||
flag.StringVar(&webdavUser, "webdav_user", "", "WebDAV User")
|
||||
|
||||
var webdavPass string
|
||||
flag.StringVar(&webdavPass, "webdav_pass", "", "WebDAV Password")
|
||||
|
||||
var localFallback string
|
||||
flag.StringVar(&localFallback, "local_fallback", "", "Local fallback database")
|
||||
|
||||
var workDir string
|
||||
flag.StringVar(&workDir, "work_dir", "", "Temporary working directory")
|
||||
|
||||
var debounce int
|
||||
flag.IntVar(&debounce, "debounce", 0, "Debounce before sync (in seconds)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if strings.HasSuffix(configPath, "~") {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
log.FatalErr("Failed to query users home directory", err)
|
||||
}
|
||||
fmt.Println(usr.HomeDir)
|
||||
|
||||
configPath = strings.TrimSuffix(configPath, "~")
|
||||
configPath = fmt.Sprintf("%s/%s", usr.HomeDir, configPath)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) && configPath != "" {
|
||||
_ = os.WriteFile(configPath, langext.Must(json.Marshal(Config{
|
||||
WebDAVURL: "https://your-nextcloud-domain.example/remote.php/dav/files/keepass.kdbx",
|
||||
WebDAVUser: "",
|
||||
WebDAVPass: "",
|
||||
LocalFallback: "",
|
||||
WorkDir: "/tmp/kpsync",
|
||||
Debounce: 3500,
|
||||
})), 0644)
|
||||
}
|
||||
|
||||
cfgBin, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
log.FatalErr("Failed to read config file from "+configPath, err)
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
err = json.Unmarshal(cfgBin, &cfg)
|
||||
if err != nil {
|
||||
log.FatalErr("Failed to parse config file from "+configPath, err)
|
||||
}
|
||||
|
||||
if webdavURL != "" {
|
||||
cfg.WebDAVURL = webdavURL
|
||||
}
|
||||
if webdavUser != "" {
|
||||
cfg.WebDAVUser = webdavUser
|
||||
}
|
||||
if webdavPass != "" {
|
||||
cfg.WebDAVPass = webdavPass
|
||||
}
|
||||
if localFallback != "" {
|
||||
cfg.LocalFallback = localFallback
|
||||
}
|
||||
if workDir != "" {
|
||||
cfg.WorkDir = workDir
|
||||
}
|
||||
if debounce > 0 {
|
||||
cfg.Debounce = debounce
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module mikescher.com/kpsync
|
||||
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.11.0 // indirect
|
||||
git.blackforestbytes.com/BlackForestBytes/goext v0.0.594 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
)
|
8
go.sum
Normal file
8
go.sum
Normal file
@ -0,0 +1,8 @@
|
||||
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
||||
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
git.blackforestbytes.com/BlackForestBytes/goext v0.0.594 h1:tFbvEAe7FMvLuW9/RuXXlxsi8/Ve7xVrFxZnk8e/0yU=
|
||||
git.blackforestbytes.com/BlackForestBytes/goext v0.0.594/go.mod h1:vczjjViG013HjA5Ka3VTE7axDgqMChn1EsvEVg9LZnU=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
27
log/logger.go
Normal file
27
log/logger.go
Normal file
@ -0,0 +1,27 @@
|
||||
package log
|
||||
|
||||
//TODO
|
||||
|
||||
func Fatal(msg string) {
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
func FatalErr(msg string, err error) {
|
||||
if err != nil {
|
||||
panic("ERROR: " + msg + "\n" + err.Error())
|
||||
} else {
|
||||
panic("ERROR: " + msg)
|
||||
}
|
||||
}
|
||||
|
||||
func LogError(msg string, err error) {
|
||||
if err != nil {
|
||||
println("ERROR: " + msg + "\n" + err.Error())
|
||||
} else {
|
||||
println("ERROR: " + msg)
|
||||
}
|
||||
}
|
||||
|
||||
func LogInfo(msg string) {
|
||||
println("INFO: " + msg)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user