Add Windows one-click zip and Debian timer/tray packaging
Bundles forked rclone + rclone-bisyncd. Does not vendor dist binaries.
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
dist/
|
||||||
|
*.exe
|
||||||
|
rclone-linux-amd64
|
||||||
|
rclone-bisyncd-linux-amd64
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
rclone-dropbox-bisync 1.0.0
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Forked rclone (ListR + --delta-list, fail-closed) plus a tray/timer.
|
||||||
|
rclone-ui (Tauri GUI) is NOT inside this zip: this host cannot cross-build
|
||||||
|
NSIS. Point rclone-ui at the bundled rclone.exe after install.
|
||||||
|
|
||||||
|
Windows (one-click)
|
||||||
|
1. Unzip. Double-click Install.cmd
|
||||||
|
2. Edit %LOCALAPPDATA%\rclone-dropbox-bisync\config.json
|
||||||
|
set enabled=true, path1, path2, workdir (NTFS share)
|
||||||
|
3. Put RCLONE_TEST on both sides
|
||||||
|
4. Tray icon + HKCU Run + schtasks every 15 min
|
||||||
|
Flags baked in: --delta-list --check-access --max-delete 10
|
||||||
|
--compare size,modtime,checksum --slow-hash-sync-only
|
||||||
|
--resilient --recover --conflict-resolve none
|
||||||
|
|
||||||
|
Linux (.deb)
|
||||||
|
sudo apt install ./rclone-dropbox-bisync_1.0.0_amd64.deb
|
||||||
|
(or ar x / dpkg -i)
|
||||||
|
Log in: autostart enables systemd --user rclone-bisync.timer
|
||||||
|
Edit ~/.config/rclone-dropbox-bisync/config.json (enabled=true)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
ROOT="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||||
|
DIST="$ROOT/dist"
|
||||||
|
SRC_RCLONE="${RCLONE_SRC:-$ROOT/../rclone}"
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
|
rm -rf "$DIST"
|
||||||
|
mkdir -p "$DIST/win" "$DIST/deb-root"
|
||||||
|
|
||||||
|
echo "==> rclone linux amd64"
|
||||||
|
( cd "$SRC_RCLONE" && go build -trimpath -o "$DIST/rclone-linux-amd64" )
|
||||||
|
echo "==> rclone windows amd64"
|
||||||
|
( cd "$SRC_RCLONE" && GOOS=windows GOARCH=amd64 go build -trimpath -o "$DIST/rclone.exe" )
|
||||||
|
echo "==> rclone-bisyncd"
|
||||||
|
( cd "$ROOT/cmd/rclone-bisyncd" && go build -trimpath -o "$DIST/rclone-bisyncd-linux-amd64" )
|
||||||
|
( cd "$ROOT/cmd/rclone-bisyncd" && GOOS=windows GOARCH=amd64 go build -trimpath -o "$DIST/rclone-bisyncd.exe" )
|
||||||
|
|
||||||
|
echo "==> windows zip"
|
||||||
|
WIN="$DIST/rclone-dropbox-bisync-windows-amd64"
|
||||||
|
rm -rf "$WIN"
|
||||||
|
mkdir -p "$WIN"
|
||||||
|
cp "$DIST/rclone.exe" "$DIST/rclone-bisyncd.exe" \
|
||||||
|
"$ROOT/windows/Install.ps1" "$ROOT/windows/Install.cmd" "$ROOT/windows/tray.ps1" \
|
||||||
|
"$ROOT/windows/config.example.json" "$WIN/"
|
||||||
|
( cd "$DIST" && zip -r -q rclone-dropbox-bisync-windows-amd64.zip rclone-dropbox-bisync-windows-amd64 )
|
||||||
|
|
||||||
|
echo "==> deb"
|
||||||
|
DEB="$DIST/deb-root"
|
||||||
|
mkdir -p \
|
||||||
|
"$DEB/opt/rclone-dropbox-bisync/bin" \
|
||||||
|
"$DEB/usr/lib/systemd/user" \
|
||||||
|
"$DEB/etc/xdg/autostart" \
|
||||||
|
"$DEB/usr/share/doc/rclone-dropbox-bisync" \
|
||||||
|
"$DEB/DEBIAN"
|
||||||
|
cp "$DIST/rclone-linux-amd64" "$DEB/opt/rclone-dropbox-bisync/bin/rclone"
|
||||||
|
cp "$DIST/rclone-bisyncd-linux-amd64" "$DEB/opt/rclone-dropbox-bisync/bin/rclone-bisyncd"
|
||||||
|
cp "$ROOT/linux/session-start.sh" "$DEB/opt/rclone-dropbox-bisync/bin/session-start.sh"
|
||||||
|
chmod 755 "$DEB/opt/rclone-dropbox-bisync/bin/"*
|
||||||
|
cp "$ROOT/linux/systemd/rclone-bisync.service" "$ROOT/linux/systemd/rclone-bisync.timer" \
|
||||||
|
"$DEB/usr/lib/systemd/user/"
|
||||||
|
cp "$ROOT/linux/autostart/rclone-bisync-tray.desktop" "$DEB/etc/xdg/autostart/"
|
||||||
|
cp "$ROOT/config.example.json" "$DEB/usr/share/doc/rclone-dropbox-bisync/"
|
||||||
|
cat > "$DEB/DEBIAN/postinst" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
if [ -n "${SUDO_USER:-}" ]; then
|
||||||
|
uhome=$(getent passwd "$SUDO_USER" | cut -d: -f6)
|
||||||
|
mkdir -p "$uhome/.config/rclone-dropbox-bisync"
|
||||||
|
if [ ! -f "$uhome/.config/rclone-dropbox-bisync/config.json" ]; then
|
||||||
|
cp /usr/share/doc/rclone-dropbox-bisync/config.example.json \
|
||||||
|
"$uhome/.config/rclone-dropbox-bisync/config.json"
|
||||||
|
chown -R "$SUDO_USER:" "$uhome/.config/rclone-dropbox-bisync"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod 755 "$DEB/DEBIAN/postinst"
|
||||||
|
cp "$ROOT/linux/control" "$DEB/DEBIAN/control"
|
||||||
|
# Installed-Size in KiB
|
||||||
|
size=$(du -sk "$DEB" | awk '{print $1}')
|
||||||
|
echo "Installed-Size: $size" >> "$DEB/DEBIAN/control"
|
||||||
|
|
||||||
|
# ar-based .deb (no dpkg-deb on this host)
|
||||||
|
pkg="$DIST/deb-pkg"
|
||||||
|
rm -rf "$pkg"
|
||||||
|
mkdir -p "$pkg/data" "$pkg"
|
||||||
|
( cd "$DEB" && tar --owner=0 --group=0 -cJf "$pkg/data.tar.xz" \
|
||||||
|
opt usr etc )
|
||||||
|
( cd "$DEB/DEBIAN" && tar --owner=0 --group=0 -czf "$pkg/control.tar.gz" . )
|
||||||
|
echo 2.0 > "$pkg/debian-binary"
|
||||||
|
( cd "$pkg" && ar r "$DIST/rclone-dropbox-bisync_1.0.0_amd64.deb" \
|
||||||
|
debian-binary control.tar.gz data.tar.xz )
|
||||||
|
ls -lh "$DIST"/*.deb "$DIST"/*.zip "$DIST"/rclone*
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module rclone-bisyncd
|
||||||
|
|
||||||
|
go 1.22
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Path1 string `json:"path1"`
|
||||||
|
Path2 string `json:"path2"`
|
||||||
|
Workdir string `json:"workdir"`
|
||||||
|
IntervalMinutes int `json:"interval_minutes"`
|
||||||
|
Rclone string `json:"rclone"`
|
||||||
|
CheckFilename string `json:"check_filename"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfig() Config {
|
||||||
|
return Config{
|
||||||
|
Enabled: false,
|
||||||
|
IntervalMinutes: 15,
|
||||||
|
Rclone: "rclone",
|
||||||
|
CheckFilename: "RCLONE_TEST",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func configPath() string {
|
||||||
|
if p := os.Getenv("RCLONE_BISYNC_CONFIG"); p != "" {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
base := os.Getenv("LOCALAPPDATA")
|
||||||
|
if base == "" {
|
||||||
|
base = os.Getenv("USERPROFILE")
|
||||||
|
}
|
||||||
|
return filepath.Join(base, "rclone-dropbox-bisync", "config.json")
|
||||||
|
}
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
return filepath.Join(home, ".config", "rclone-dropbox-bisync", "config.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig() (Config, error) {
|
||||||
|
cfg := defaultConfig()
|
||||||
|
b, err := os.ReadFile(configPath())
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if cfg.IntervalMinutes < 5 {
|
||||||
|
cfg.IntervalMinutes = 5
|
||||||
|
}
|
||||||
|
if cfg.Rclone == "" {
|
||||||
|
cfg.Rclone = "rclone"
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func notify(title, body string) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
_ = exec.Command("notify-send", "-a", "rclone-bisync", title, body).Run()
|
||||||
|
case "windows":
|
||||||
|
ps := fmt.Sprintf(`Add-Type -AssemblyName System.Windows.Forms; $n=New-Object System.Windows.Forms.NotifyIcon; $n.Icon=[System.Drawing.SystemIcons]::Information; $n.Visible=$true; $n.ShowBalloonTip(4000,'%s','%s',[System.Windows.Forms.ToolTipIcon]::Info); Start-Sleep -Seconds 5; $n.Dispose()`,
|
||||||
|
escapePS(title), escapePS(body))
|
||||||
|
_ = exec.Command("powershell", "-NoProfile", "-Command", ps).Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapePS(s string) string {
|
||||||
|
out := make([]byte, 0, len(s))
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == '\'' {
|
||||||
|
out = append(out, '\'', '\'')
|
||||||
|
} else {
|
||||||
|
out = append(out, s[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runOnce(cfg Config) error {
|
||||||
|
if !cfg.Enabled {
|
||||||
|
return fmt.Errorf("disabled in %s", configPath())
|
||||||
|
}
|
||||||
|
if cfg.Path1 == "" || cfg.Path2 == "" || cfg.Workdir == "" {
|
||||||
|
return fmt.Errorf("path1, path2, and workdir must be set in %s", configPath())
|
||||||
|
}
|
||||||
|
args := []string{
|
||||||
|
"bisync", cfg.Path1, cfg.Path2,
|
||||||
|
"--workdir", cfg.Workdir,
|
||||||
|
"--delta-list",
|
||||||
|
"--check-access",
|
||||||
|
"--check-filename", cfg.CheckFilename,
|
||||||
|
"--compare", "size,modtime,checksum",
|
||||||
|
"--slow-hash-sync-only",
|
||||||
|
"--max-delete", "10",
|
||||||
|
"--resilient",
|
||||||
|
"--recover",
|
||||||
|
"--conflict-resolve", "none",
|
||||||
|
}
|
||||||
|
cmd := exec.Command(cfg.Rclone, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
once := false
|
||||||
|
loop := true
|
||||||
|
for _, a := range os.Args[1:] {
|
||||||
|
switch a {
|
||||||
|
case "--once":
|
||||||
|
once = true
|
||||||
|
loop = false
|
||||||
|
case "--help", "-h":
|
||||||
|
fmt.Fprintf(os.Stderr, "rclone-bisyncd [--once]\nconfig: %s\n", configPath())
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
cfg, err := loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "config: %v\n", err)
|
||||||
|
notify("rclone-bisync", "No config yet — edit "+configPath())
|
||||||
|
if once {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else if !cfg.Enabled {
|
||||||
|
fmt.Println("disabled; sleeping")
|
||||||
|
} else if err := runOnce(cfg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "bisync: %v\n", err)
|
||||||
|
notify("rclone-bisync failed", err.Error())
|
||||||
|
if once {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notify("rclone-bisync", "Run finished")
|
||||||
|
if once {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !loop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mins := 15
|
||||||
|
if cfg, err := loadConfig(); err == nil && cfg.IntervalMinutes > 0 {
|
||||||
|
mins = cfg.IntervalMinutes
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(mins) * time.Minute)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"path1": "/mnt/ntfs/Dropbox",
|
||||||
|
"path2": "dropbox:",
|
||||||
|
"workdir": "/mnt/ntfs/rclone-bisync",
|
||||||
|
"interval_minutes": 15,
|
||||||
|
"rclone": "/opt/rclone-dropbox-bisync/bin/rclone",
|
||||||
|
"check_filename": "RCLONE_TEST"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=rclone Dropbox bisync
|
||||||
|
Comment=Enable user timer and show notifications for Dropbox bisync
|
||||||
|
Exec=/opt/rclone-dropbox-bisync/bin/session-start.sh
|
||||||
|
Icon=folder-remote
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;FileTransfer;
|
||||||
|
X-GNOME-Autostart-enabled=true
|
||||||
|
StartupNotify=false
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Package: rclone-dropbox-bisync
|
||||||
|
Version: 1.0.0
|
||||||
|
Section: net
|
||||||
|
Priority: optional
|
||||||
|
Architecture: amd64
|
||||||
|
Maintainer: local <root@localhost>
|
||||||
|
Depends: libnotify-bin
|
||||||
|
Description: Forked rclone Dropbox bisync with fail-closed defaults
|
||||||
|
Bundles a patched rclone (ListR + --delta-list) and a user timer/tray
|
||||||
|
helper. Does not run until ~/.config/rclone-dropbox-bisync/config.json
|
||||||
|
has enabled=true and valid paths.
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
systemctl --user daemon-reload 2>/dev/null || true
|
||||||
|
systemctl --user enable --now rclone-bisync.timer 2>/dev/null || true
|
||||||
|
if command -v notify-send >/dev/null 2>&1; then
|
||||||
|
notify-send -a rclone-bisync "rclone-bisync" "Background timer enabled. Edit ~/.config/rclone-dropbox-bisync/config.json (set enabled=true)."
|
||||||
|
fi
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=rclone Dropbox bisync (fail-closed)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/opt/rclone-dropbox-bisync/bin/rclone-bisyncd --once
|
||||||
|
Nice=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=rclone Dropbox bisync timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=3min
|
||||||
|
OnUnitActiveSec=15min
|
||||||
|
Persistent=true
|
||||||
|
Unit=rclone-bisync.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Install.ps1"
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# One-click user install (no admin required).
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$Dest = Join-Path $env:LOCALAPPDATA 'rclone-dropbox-bisync'
|
||||||
|
New-Item -ItemType Directory -Force -Path (Join-Path $Dest 'bin') | Out-Null
|
||||||
|
Copy-Item -Force (Join-Path $Root 'rclone.exe') (Join-Path $Dest 'bin\rclone.exe')
|
||||||
|
Copy-Item -Force (Join-Path $Root 'rclone-bisyncd.exe') (Join-Path $Dest 'bin\rclone-bisyncd.exe')
|
||||||
|
Copy-Item -Force (Join-Path $Root 'tray.ps1') (Join-Path $Dest 'tray.ps1')
|
||||||
|
$cfg = Join-Path $Dest 'config.json'
|
||||||
|
if (-not (Test-Path $cfg)) {
|
||||||
|
Copy-Item (Join-Path $Root 'config.example.json') $cfg
|
||||||
|
$j = Get-Content $cfg -Raw | ConvertFrom-Json
|
||||||
|
$j.rclone = Join-Path $Dest 'bin\rclone.exe'
|
||||||
|
$j | ConvertTo-Json | Set-Content -Encoding UTF8 $cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
$bin = Join-Path $Dest 'bin'
|
||||||
|
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||||
|
if ($userPath -notlike "*$bin*") {
|
||||||
|
[Environment]::SetEnvironmentVariable('Path', "$bin;$userPath", 'User')
|
||||||
|
}
|
||||||
|
|
||||||
|
$run = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run'
|
||||||
|
New-ItemProperty -Path $run -Name 'rclone-dropbox-bisync' -PropertyType String -Force `
|
||||||
|
-Value "powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$Dest\tray.ps1`"" | Out-Null
|
||||||
|
|
||||||
|
schtasks /Create /F /TN "rclone-dropbox-bisync" /SC MINUTE /MO 15 /TR `
|
||||||
|
"`"$Dest\bin\rclone-bisyncd.exe`" --once" /ST 00:00 | Out-Null
|
||||||
|
|
||||||
|
Start-Process powershell.exe -WindowStyle Hidden -ArgumentList @(
|
||||||
|
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $Dest 'tray.ps1')
|
||||||
|
)
|
||||||
|
|
||||||
|
Add-Type -AssemblyName PresentationFramework
|
||||||
|
[System.Windows.MessageBox]::Show("Installed to $Dest`r`nEdit config.json (enabled, path1, path2, workdir).`r`nPut RCLONE_TEST on both sides. Tray + 15-min task are on.", 'rclone-dropbox-bisync')
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"path1": "D:\\Dropbox",
|
||||||
|
"path2": "dropbox:",
|
||||||
|
"workdir": "D:\\rclone-bisync",
|
||||||
|
"interval_minutes": 15,
|
||||||
|
"rclone": "rclone.exe",
|
||||||
|
"check_filename": "RCLONE_TEST"
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
$ErrorActionPreference = 'SilentlyContinue'
|
||||||
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
|
||||||
|
$Dest = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$Daemon = Join-Path $Dest 'bin\rclone-bisyncd.exe'
|
||||||
|
$Cfg = Join-Path $Dest 'config.json'
|
||||||
|
|
||||||
|
$icon = New-Object System.Windows.Forms.NotifyIcon
|
||||||
|
$icon.Icon = [System.Drawing.SystemIcons]::Application
|
||||||
|
$icon.Text = 'rclone Dropbox bisync'
|
||||||
|
$icon.Visible = $true
|
||||||
|
|
||||||
|
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
||||||
|
$run = $menu.Items.Add('Run bisync now')
|
||||||
|
$run.add_Click({ Start-Process $Daemon -ArgumentList '--once' -WindowStyle Hidden })
|
||||||
|
$edit = $menu.Items.Add('Edit config.json')
|
||||||
|
$edit.add_Click({ Start-Process notepad.exe $Cfg })
|
||||||
|
$quit = $menu.Items.Add('Quit tray')
|
||||||
|
$quit.add_Click({ $icon.Visible = $false; [System.Windows.Forms.Application]::Exit() })
|
||||||
|
$icon.ContextMenuStrip = $menu
|
||||||
|
$icon.ShowBalloonTip(4000, 'rclone-bisync', 'Tray running. 15-min task is registered.', [System.Windows.Forms.ToolTipIcon]::Info)
|
||||||
|
|
||||||
|
[System.Windows.Forms.Application]::Run()
|
||||||
Reference in New Issue
Block a user