From 99bcd5a30cb731e6effd8a266df1c630ef2a9e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Wed, 20 Aug 2025 14:45:46 +0200 Subject: [PATCH] added icons + README, MVP finished --- README.md | 45 ++++++++++++++++++++++++++++++++++ app/tray.go | 25 +++++++++++++------ assets/icon.xcf | Bin 0 -> 11810 bytes assets/iconDefault.png | Bin 323 -> 1358 bytes assets/iconDownload.png | Bin 307 -> 1414 bytes assets/iconInit.png | Bin 316 -> 905 bytes assets/iconUpload.png | Bin 358 -> 1395 bytes assets/iconUploadConflict.png | Bin 448 -> 1767 bytes 8 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 README.md create mode 100644 assets/icon.xcf diff --git a/README.md b/README.md new file mode 100644 index 0000000..861c2eb --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +kpsync +====== + +A small util to launch keepassXC while the database file is on a remote webDAV server (e.g. Nextcloud). + +# Usage + +1. simply start `kpsync` +2. On first start a example config in `~/.config/kpsync.json` will be created, probably needs to be edited +3. Afterwards start it again + +# Functionality + +kpsync starts by downloading the latest db file from the webDAV to the (configured) temp directory +(if there already exists a local file, matching with the server version (via ETag), the download will be skipped) + +If teh download fails, teh user gets the option to open a local (fallback) file (e.g. if the computer has no network) + +Then KeepassXC is launched. + +The temp directory is being watched (inotify) and on file changes they are uploaded to the server. + +If there are conflicts (e.g. two clients editing the file at the same time) we ask the user what to do (via `notify-send`) + +# Prerequisites + +Tested on Linux + Arch + KDE. + +Needs `notify-send` to send desktop notifications. +Needs `inotify` to watch the directory for changes. +Needs `keepassxc` to be installed. duh. + +# Config (example) + +```json +{ + "webdav_url": "https://cloud.example.com/remote.php/dav/files/YourUser/example.kdbx", + "webdav_user": "user", + "webdav_pass": "hunter2", + "local_fallback": "/home/user/example.kdbx", + "work_dir": "/tmp/kpsync", + "debounce": 3500, + "terminal_emulator": "konsole -e" +} +``` \ No newline at end of file diff --git a/app/tray.go b/app/tray.go index 7e45f33..0480c9d 100644 --- a/app/tray.go +++ b/app/tray.go @@ -16,20 +16,28 @@ func (app *Application) initTray() { systray.SetIcon(assets.IconInit) systray.SetTitle("KeepassXC Sync") - app.currSysTrayTooltip = "Initializing..." + app.currSysTrayTooltip = "KPSync | " + "Initializing..." systray.SetTooltip(app.currSysTrayTooltip) miSync := systray.AddMenuItem("Sync Now (checked)", "") miSyncForce := systray.AddMenuItem("Sync Now (forced)", "") miShowLogFifo := systray.AddMenuItem("Show Log (fifo)", "") miShowLogFile := systray.AddMenuItem("Show Log (file)", "") - systray.AddMenuItem("", "") + + systray.AddMenuItem("", "").Disable() + app.trayItemChecksum = systray.AddMenuItem("Checksum: {...}", "") app.trayItemETag = systray.AddMenuItem("ETag: {...}", "") app.trayItemLastModified = systray.AddMenuItem("LastModified: {...}", "") - systray.AddMenuItem("", "") + + systray.AddMenuItem("", "").Disable() + miQuit := systray.AddMenuItem("Quit", "") + app.trayItemChecksum.Disable() + app.trayItemETag.Disable() + app.trayItemLastModified.Disable() + app.LogDebug("SysTray initialized") app.LogLine() @@ -87,7 +95,8 @@ func (app *Application) setTrayState(txt string, icon []byte) func() { defer app.masterLock.Unlock() systray.SetIcon(icon) - systray.SetTooltip(txt) + app.currSysTrayTooltip = "KPSync | " + txt + systray.SetTooltip(app.currSysTrayTooltip) var finDone = false @@ -104,7 +113,7 @@ func (app *Application) setTrayState(txt string, icon []byte) func() { } systray.SetIcon(assets.IconDefault) - app.currSysTrayTooltip = "Sleeping..." + app.currSysTrayTooltip = "KPSync | " + "Sleeping..." systray.SetTooltip(app.currSysTrayTooltip) finDone = true @@ -122,7 +131,9 @@ func (app *Application) setTrayStateDirect(txt string, icon []byte) { defer app.masterLock.Unlock() systray.SetIcon(icon) - systray.SetTooltip(txt) + + app.currSysTrayTooltip = "KPSync | " + txt + systray.SetTooltip(app.currSysTrayTooltip) } func (app *Application) setTrayTooltip(txt string) { @@ -134,6 +145,6 @@ func (app *Application) setTrayTooltip(txt string) { defer app.masterLock.Unlock() systray.SetTooltip(txt) - app.currSysTrayTooltip = txt + app.currSysTrayTooltip = "KPSync | " + txt systray.SetTooltip(app.currSysTrayTooltip) } diff --git a/assets/icon.xcf b/assets/icon.xcf new file mode 100644 index 0000000000000000000000000000000000000000..773988d3c591d1d7c534905e7df82c04d93add33 GIT binary patch literal 11810 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 literal 0 HcmV?d00001 diff --git a/assets/iconDefault.png b/assets/iconDefault.png index d2c7c4bd9bd9543435f269809da5b58ad863496e..a14488f1f0e934951a171ba8ff4351f6d4e41b8f 100644 GIT binary patch delta 1271 zcmVaNjURAdo!6WOj|5Lz2uh_GRll?IlzbrFH)ZW%?Z6q!QS?*6*iBfPxKyff#_ zyyxBE{4dX$dFFZk=gd4`BUPwSf1yH!3Kc3u0_Xr90QLeCz!d2)mC4)-+z)ijHRD$T z2PqGSfy+|pZW0*3MABE1R+h~B2}$dWF(+e$v?WkvR||}nq*dA4t@{Ij^^)GPygDQ4 zn55}M#yV#lWAIxdUYz?^0yYGPV zwdk!3|#s9!i$XS$%fNhS`cX{2o3xR(d7;~aNPRL?|k31yY z7UPXlxV_Vsg|-H6lUh8VV|&%&xn{V<&|KgJNekG4_RLI8DsvoJYVh+d57cqot}ACxyG1tl5COS!@v!(zrJy}z0X|k z2G|~Os&A>CpXCGRJaazu==yPTw+L)@xV`He%y}x%wS5n~ofS7i@Rmo{YaMQJQ&Ho) zvT(E>uY2a*-lQtff0F?^zv1vY-9-rKb7XAGJo7q%Q;ChA0j>(rd6~mo_2o6b-SPGN z1Lwb(n1In7x^K12{}E`MOJ3=mc$|n7_pVWJ#q)~ zwCG|;csisg8bQF>yzQ+9Zf{#NqQkDxvUtQF@NIVCB2=gH%Hr9An~Juia-0EUKvzVI zuD2DfF|S($)&vqT8S)pjq%{`Pk^<%hG)qs~I>JR;e{{_xVP}!O2P^@;Y4GJkjCU10 zhRaNs3%8^kBm^1nY0;H;K_XLRu&5##31bC}pBq+*k3EQ`a3D*mU^74IIOpx-9&`c(CAMoIuyDnd$ hLWK$yDpdGi;y=#neMi9`_UZrt002ovPDHLkV1oPVaKr!r delta 228 zcmX@db(l%9Gr-TCmrII^fq{Y7)59eQNGpIa2OE$quB!SnQ8BY#m|cdcskCw(P-v#7 zi(^Q{;kQ#y^EMdpIA1>3e51Sb2KPq(ITHk4sr%iX=3Mh?<$13;E=Qgg?NrnL!j|*A z%JzM&<)HxP{EBlfGMxUJ=UU1$9XaQna`^L9Na1Nr#xi*CzFHYRA-aM>H<(Pt=y#)plRY;DO%s bjuX6xXZp3OGp#)ibR~nQtDnm{r-UW|@JUry diff --git a/assets/iconDownload.png b/assets/iconDownload.png index 37f884098f7b19728019fd1967d7453031f3ad43..176583d45304728375cda711056d1dd1e5152d05 100644 GIT binary patch delta 1328 zcmV-01<(4k0)`7AiBL{Q4GJ0x0000DNk~Le0000$0000$2nGNE0IF$m-jN|`e-sQT z7*)TddjJ3g+(|@1RCwC$noDS1RTPH5)drHNeN41IQnl9lfDmncP_@;WLtAaqI1p=# zAn}1I+Nw!K5!<37Ef$QUq^JY63Jzkx=)eS8p@yI#s9@W~w&H7K8=D4_{0_2(%W>a( z&dE9V+$)@axYxaVt$*#c&sux!e+@Zu8@M^NNNTS zc|;SyF<>SovI~J#z=a-Ji4ay0eko~b8T2i6a|dxkQe7n+f6(bM=5I;M z%cygiq`y3RJF+HU%#h9uba#04o|SZYmJaCg7;#e41sUkBm2}LbzdI}Pt0m2N4A`9E z0f4Efh0}w1hd(3QG4DvOO@XCPb7;s}( zGdq`dCHSm|`~(p9%YU7RJixWC*olJ|fHR&7aeJzzu%u%gMG*ch;{3J#-hy6sbli-0|;I6%@G;31D_f2Wz9uQYozn*w~5 z;KPz`PviiXQucd(cNqAjTKW-Yb_f{oeBk$orNyuaco10cNqg;PrYdVnwL9AiaR<;k zF9%4f2ljeI-DWmaZ7n%7`xZFp5xr5WL@yNqPXSj2#?AsSQoZPOHWs1*crum)BrOCU z_lS0z*>Ba#-r44hf7&M;r^peo#I|SS$j@JY(A=)kc_e*F4FGVX>P#v|$2l z@raJcalo)=AFXh?HVl1bjPNLPw*%J%#!dp?MRtIh%>eKCf6|PkOG6BJ6!@$R#gV-wR)(|I50vl6uLO-xEjJ%|qfk!-|9i^Hh7U3{72&@Z;jsWYz&I4_uP4&R1 zz`f-p?`-`Ff4C#w*!@&E# zCP|blsd+jf4*A+5Ma#$EYr+u|kC;+8m9 zQjXw;z;=k5=L^ZNE>iODh)O+w2CV;=oIWViyAd?2x_;twR8A)Q$pQZ-Lx{!NSS^``HU)RHpj zTTkIU6HZ>Pc!#a_K&2Yl?cv&wXO)s3PZs9q~z1y4mFaRCH1+QU^c~2pLKKgN!nC( m&*gLE$dMyQjvW6>`~xEXblzFjkaz$9002ov22Mn-LSTZ@J$&5& delta 212 zcmZqU-pr)f8Q|y6%O%Cdz`(%k>ERLtq!mDzgAGU)S5_L+Yfd;P{0@-`460R^Zzhe;d z>Q0zt*&sGeFjn&7rJ^rh-S09P?iMfJ+3=3f!JoP3%Nf=iQNBH2PW=8cE4$~(l9;SW zzoNxcMA`*zd!#Ngkq_2NvJ9R5^oiN|nHh6Wy}V?+XiK@t0tSX}o<_eUJ}>VDx{ATm L)z4*}Q$iB}u*Omd diff --git a/assets/iconInit.png b/assets/iconInit.png index 2b77ac9041d937a02127ec79ad44c66a99436b12..af1eb6e5d5050bcb24248778cb115d0a9e915aa0 100644 GIT binary patch delta 815 zcmV+~1JL}u0*MD9iBL{Q4GJ0x0000DNk~Le0000$0000$2nGNE0IF$m-jN|`e-sQT z0;t_x0RR93-$_J4RCwC$noUbqK@`XTqk>t1ZYoQKBHEW_wCO`szV$^EU-~4O8m)R+ zicq*H%Yul$fTotMYLyuj=<{pgg+gZ3=ecv|&c*pJ@4)$=bMDNXmpM{GLPA19LPA1L z04>06U=COZw(*DUOy(SL3uuYWfB0@-iEwxiocE!-@nHNZNgpM32lo4hq^ri5?|FnY zc~In03C2rOPsUx|4gmTkJ+&-tNm`M#~FFq#a^9u}7^`px=>9Bowm2($v94toUly9C!NdI^r^z6AFX6nhwSRfnUEe+Y9WLQ@etV9Hw} zRMAv;+gmF9UorIBB6R6U6hjp%hx-dDhf_o3A4yl%W~xd{%>NS|aIZp5(a9pn+KzNv zJR=;gz{AK;(Zq`}gvC;x#t!g&$j8xgpXh+u5}(Ii4Ud@^Zr>0C5|gD5NMwpkgjOV) z5t#$3#RZ_f0KRs|e~{EV(AIDZEvHh0k{(-@wj?b}s(G@%NxNfmCoOA3#+WB%B_alR zL3wzdH|+Tsl|8N9QQ5A*{{O1oQQ2>Kw^lA>3fz$NE}-!rB;Cwwyzk@ClGWw&Sx!*% txYcW!%z5BWbmQ^~2?+@a2?>?NU-ERLtq!mDzgAGU)S5OvEj z9qLzSRSOiFF)>E4iJdhumOQR~`LgCE1D4RnFDtLwoNSqLE_jvV#J$(1znLz_F;`i) VI3-D^mj&oN22WQ%mvv4FO#mp(R%ZYJ diff --git a/assets/iconUpload.png b/assets/iconUpload.png index be2f92d5fa9eaf4ac930750b2b52db1b9a10944c..42064de684da965bb2dee754894b2023bc22cfed 100644 GIT binary patch delta 1308 zcmV+%1>^eW0`m$XiBL{Q4GJ0x0000DNk~Le0000$0000$2nGNE0IF$m-jN|`e-sQT z618egWdHyK$w@>(RCwC$np=pKRTRg6D+R}H`cifoOEV#2sJs+bSWgPkLl;NKhp@|I z*OAJ~De9rgGD$Oti11QDT0Llp(4mJ$ngTPc!G|cDnbgSaDV=l#XZ}6R4&-BJf_%^b;zyoaGpb3YG$7{V&At&>UZ|%7m}vdf#1|HcM|=QS{h-1e=bLue zBhP7){&x7T&Rh9%f^_A;d$q&&grv*zG{9y@h(nUj&4KqMNe3PNoAOeAj-(MsfMq!@ zz*2|*h@>0q9t9-r0Tw2PzX5JFGo_v9y2S@tfXBn18~aLa!;y0I0JqgDhea~&0JbHD zM}Y-q_HCr{JAuVvs07>T40Ker1td)d);Oe1X0|T}<)e&XWHz& zBk)^d?1D50Q3KHpT$mU;0=yNS2$|WbP;V#kprrXtxcWB$UiJ61nVqb*7T5-S<^0c^ zp-^fjY+&pKe<@iEZLhi<4h7E>PLXj7u%wArzul<>m8NK`cpqS9`+*M~(%ObRN%>*V zOMl-tv%Q&IiG`DO$O(%po4^5{bZ&!ARw~+}3}9xz18Y6QNV+sq;PEauPF4~2o^eQP z%*|!!^m>rBj<)F_F-fe@DgxM z82Sl#v9HwDA0bP*hmqxGw!NN*&K~Dot~c+nD5=MhU7KC>Bc1gQKPB5CGtB-~`cS~B*bnWs53KS?%pg@8DCH@8dR%C8A Sk-rlF0000v&3g!YKiBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPQHe-jKc zDzK6_nE(I)yh%hsR9M69SIG^4FbpGB$Eg^Dp*u#!KJem$P|4xY4}S3=@qt^3Wtj|# zE?BqyxSo5f=6!`h{CNT+a%&2b;o2fXvLe@tK!m)vDxTt*5_lqiBGtZUO2D$zn}jgP zM{{p8V3K>vE1tG%JD?Gd4CnWmU6DAjE)*|dCD)45rhsUPqJ=D(k@r^R%>W|hV$75; z;#VKT89-+IQ~_C^3Ny)v0yK*mS2vihJUs!+mA9nRkD;*tbbkD>;sm$Wd0?1Ma-IMH N002ovPDHLkV1n6XZ=3)C diff --git a/assets/iconUploadConflict.png b/assets/iconUploadConflict.png index 7fa0b0bbe70018a9504ae4176f3d2f7b4a990497..5bed2c2db94d931231b7a8b6eeea7005b0b4ce13 100644 GIT binary patch delta 1683 zcmV;E25kAj1LqAPiBL{Q4GJ0x0000DNk~Le0000$0000$2nGNE0IF$m-jN|`e-sQT z9=zNEP5=M~JxN4CRCwC$nrnzvRTRg6JEd{dP>M92@r~N!aD0{d%%DC5<%h~~v7ZS_zGuMA7)=U?b+wvbM8HJ z?*#Y%?d<3Juf6tJYp=bbj55k7f1`{t$|$2ih)7ovc}hgKib$h~+>rEe!;HCAM4l9p zuAPm1wTSGK;^Cl(3{FFKl>+%Yfd-(u$a+5wj8jVeS;!!_q#($LB_Izlz|37b3lNbR zz-C8PGjJSeNr`M1U^vjjQCFjs+ER*r-%UhLIQHib5$W9l^Yu1k+c+U2f89!~8 zmqlbi2hQ17ME-WnTUTuIQG(PLVeUG|yq83ze=!xX!6C#c5$Rrpxhq8ExMTkH#UejO zL|PmI%yhL6e7{S+b4B$Pks8PREh6$jhkF4L*$qs!iuVJPl~NKCz7nVdrUBDDttBCO z1?&QL0xLbOuUT^J0v^gGe@CiufQZyO$hQLXE%Jkb1Hcl~{`ou%;5pNGDX>5A{ef2f z0yB3Q6M>ozu>j+0egfDkz8RDlL#}HZ_W3JU%$ODW9UJrf0Yr^g@dIBp`u>cVn3;f_v*3B5) z{Pd0ix5k+B5l`!HesV_Y}?l;1OM5!hsl4opx=9n9MTL_`5=9qsl3pM}2b zW0D70?pnhk?=3JZ!tE`{xV^yldzf}{amKNoFlFZw zj59m%{q8Z`Vr^au5Rpn?m7{9CQtD9Xd%D?kaqy<6_2Zt_e;4cuPwNYw){jNZNrb|l z2wA~lV594wk}X8fW&tk)gRHVkCNK$ON=$)22bNi8-#_qu+uhXzU&I`9ilgr(tE?J$ zDUkw1qzmwZqiTgx$~MzD@wWiWGU0IG`vU{t-wQMWO@Z(4$wYdl%~I1R7smf*)3?dy zvY;0Yr`Q%Sf7Y@3CnJ7RanfN&JgtM!_s76wYb8GlZ3hhmF~)Vxy#{Es$_7Tc=qLpY ziP-zs9p$&j5cHRj*;iTQGng9qem_^AEV!ecUsviHu))!Gm}`K7^Xs10l?l7F#2j}< zF^>097I4y0HcvzIWqyJ0_p|nWN98IPdOyKYqA`^yU;KRV(#GUKyRPU8KMtF~ zq9Gimg_9%+ucQ{nAdgAAy_(SNxx(KMa*FSwAs^cULivHCWxGmH-WeK%r+FfiQD002ovPDHLkV1k1RA^ZRU delta 354 zcmV-o0iFKm4Zs5-iBL{Q4GJ0x0000DNk~Le0000W0000W2nGNE0CReJ^pPQHe-jKd zAOAYS`v3p|7D+@wR9M5!SKAGPFbph~hv*Q9fihqQiaG>av|mt46Z`z2YQ9hizW988 zFx>*{u`JK?-rU+`vB6Q$a zEK>upJ0D%g@sN7DES^xk507*qoM6N<$f-mQr AwEzGB