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
82 changes: 82 additions & 0 deletions net/owsync/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# SPDX-License-Identifier: MIT

include $(TOPDIR)/rules.mk

PKG_NAME:=owsync
PKG_VERSION:=1.2.0
PKG_RELEASE:=1

PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/pgaufillet/owsync.git
PKG_SOURCE_VERSION:=v1.2.0
PKG_MIRROR_HASH:=97f67ac2d762686c051f3e8c70b685212a9b37862934d40586919d3353330507

PKG_MAINTAINER:=Pierre Gaufillet <pierre.gaufillet@bergamote.eu>
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE

PKG_BUILD_PARALLEL:=1

include $(INCLUDE_DIR)/package.mk

define Package/owsync
SECTION:=net
CATEGORY:=Network
SUBMENU:=File Transfer
TITLE:=Lightweight bidirectional file synchronization daemon
URL:=https://github.com/pgaufillet/owsync
DEPENDS:=+libjson-c +OWSYNC_ENABLE_ENCRYPTION:libopenssl +rpcd
endef

define Package/owsync/description
owsync is a lightweight, secure, and robust high-availability file
synchronization tool designed specifically for OpenWrt routers.
It provides bi-directional, multi-master synchronization of
configuration files between redundant nodes.
endef

define Package/owsync/config
config OWSYNC_ENABLE_ENCRYPTION
bool "Enable encryption support (AES-256-GCM)"
default y
help
Build owsync with encryption support using OpenSSL.
If disabled, owsync can only be used in plain mode over
secure VPN connections (WireGuard/IPsec).
endef

MAKE_FLAGS += \
PKGCONFIG="$(PKG_CONFIG)" \
ENABLE_ENCRYPTION=$(if $(CONFIG_OWSYNC_ENABLE_ENCRYPTION),1,0)

define Package/owsync/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/owsync $(1)/usr/bin/

$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/owsync.config $(1)/etc/config/owsync

$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/owsync.init $(1)/etc/init.d/owsync

$(INSTALL_DIR) $(1)/etc/owsync

$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./files/owsync.rpcd $(1)/usr/libexec/rpcd/owsync
endef

define Package/owsync/conffiles
/etc/config/owsync
/etc/owsync
endef

define Package/owsync/prerm
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/owsync stop >/dev/null 2>&1
/etc/init.d/owsync disable >/dev/null 2>&1
fi
exit 0
endef

$(eval $(call BuildPackage,owsync))
26 changes: 26 additions & 0 deletions net/owsync/files/owsync.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
config owsync 'general'
option enabled '0'
option host '::'
option port '4321'
option dir '/etc/config'
option db '/etc/owsync/owsync.db'
option key 'GENERATE_WITH_OWSYNC_GENKEY'
option poll_interval '60'

# Plain mode (no encryption) - only use over secure VPN
# option plain_mode '1'

# Target Peers for automatic sync
# List peer hostnames/IPs (all use the same port)
# list peer '192.168.1.2'
# list peer 'fd00::2'

# Whitelist specific files (required - if empty, nothing is synced)
# Use 'list include *' to sync all files
# list include 'firewall'
# list include 'dhcp'

# Blacklist specific files (takes precedence over includes)
list exclude 'network'
list exclude 'system'
list exclude 'owsync'
143 changes: 143 additions & 0 deletions net/owsync/files/owsync.init
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/bin/sh /etc/rc.common
# SPDX-License-Identifier: MIT
# Copyright (c) 2025-2026 Pierre Gaufillet <pierre.gaufillet@bergamote.eu>

START=99
STOP=10
USE_PROCD=1

# Dependencies: network must be up before owsync can bind/connect
DEPEND="network"

CONF_FILE="/tmp/owsync.conf"

# Collect list items into shell variables
PEERS=""
INCLUDES=""
EXCLUDES=""

append_peer() {
PEERS="$PEERS $1"
}

append_include() {
INCLUDES="$INCLUDES $1"
}

append_exclude() {
EXCLUDES="$EXCLUDES $1"
}

generate_config() {
local host="$1"
local port="$2"
local key="$3"
local dir="$4"
local db="$5"
local poll_interval="$6"
local plain_mode="$7"

# Create config file with restricted permissions (key inside)
rm -f "$CONF_FILE"
touch "$CONF_FILE"
chmod 600 "$CONF_FILE"

cat > "$CONF_FILE" <<EOF
# Auto-generated by owsync init script
# Do not edit - changes will be overwritten on restart

bind_host=$host
port=$port
sync_dir=$dir
database=$db
poll_interval=$poll_interval
log_level=2
EOF

# Add encryption key or plain mode
if [ "$plain_mode" = "1" ]; then
echo "plain_mode=1" >> "$CONF_FILE"
else
echo "encryption_key=$key" >> "$CONF_FILE"
fi

# Add peers
for peer in $PEERS; do
echo "peer=$peer" >> "$CONF_FILE"
done

# Add includes
for inc in $INCLUDES; do
echo "include=$inc" >> "$CONF_FILE"
done

# Add excludes
for exc in $EXCLUDES; do
echo "exclude=$exc" >> "$CONF_FILE"
done
}

start_service() {
config_load owsync

local enabled
config_get_bool enabled general enabled 0
[ "$enabled" -eq 1 ] || return 0

local host port key dir db poll_interval plain_mode
config_get host general host "::"
config_get port general port "4321"
config_get key general key
config_get dir general dir "/etc/config"
config_get db general db "/etc/owsync/owsync.db"
config_get poll_interval general poll_interval "60"
config_get_bool plain_mode general plain_mode 0

# Collect list items
PEERS=""
INCLUDES=""
EXCLUDES=""
config_list_foreach "general" "peer" append_peer
config_list_foreach "general" "include" append_include
config_list_foreach "general" "exclude" append_exclude

# Validate key (unless plain mode)
if [ "$plain_mode" != "1" ]; then
if [ -z "$key" ] || [ "$key" = "GENERATE_WITH_OWSYNC_GENKEY" ]; then
logger -t owsync -p daemon.err "Error: owsync key not configured"
return 1
fi
fi

# Validate peers for daemon mode
if [ -z "$PEERS" ]; then
logger -t owsync -p daemon.err "Error: owsync requires at least one peer for daemon mode"
return 1
fi

# Ensure DB directory exists
local db_dir
db_dir=$(dirname "$db")
[ -d "$db_dir" ] || mkdir -p "$db_dir"

# Generate config file (key stays in file, not command line)
generate_config "$host" "$port" "$key" "$dir" "$db" "$poll_interval" "$plain_mode"

# Start daemon with config file
procd_open_instance "owsync"
procd_set_param command /usr/bin/owsync daemon -c "$CONF_FILE"
procd_set_param respawn
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param file /etc/config/owsync
procd_close_instance
}

stop_service() {
# Clean up generated config file
rm -f "$CONF_FILE"
}

service_triggers() {
procd_add_reload_trigger "owsync"
}
133 changes: 133 additions & 0 deletions net/owsync/files/owsync.rpcd
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/bin/sh
# SPDX-License-Identifier: MIT
# Copyright (c) 2025-2026 Pierre Gaufillet <pierre.gaufillet@bergamote.eu>
#
# owsync rpcd handler - provides ubus status interface
# Usage: ubus call owsync status

. /usr/share/libubox/jshn.sh

case "$1" in
list)
# List available methods and their parameters
json_init
json_add_object "status"
json_close_object
json_dump
;;
call)
case "$2" in
status)
# Get owsync service status

# Initialize variables
status="STOPPED"
pid=0
enabled=0
uptime=0
encryption_enabled=0
peer_count=0
sync_dir=""
db_path=""
db_last_modified=0

# Check if owsync is enabled via standalone config or ha-cluster
if [ -f /etc/config/owsync ]; then
owsync_enabled=$(uci -q get owsync.config.enabled)
if [ "$owsync_enabled" = "1" ]; then
enabled=1
fi
fi

# Also check ha-cluster config
if [ -f /etc/config/ha-cluster ]; then
ha_enabled=$(uci -q get ha-cluster.config.enabled)
sync_method=$(uci -q get ha-cluster.config.sync_method)
if [ "$ha_enabled" = "1" ] && [ "$sync_method" = "owsync" ]; then
enabled=1
fi
fi

# Check if running
svc_pid=$(pgrep -o "owsync" 2>/dev/null)
if [ -n "$svc_pid" ]; then
status="RUNNING"
pid=$svc_pid

# Calculate uptime from /proc/PID/stat
start_time=$(awk '{print $22}' /proc/$svc_pid/stat 2>/dev/null)
if [ -n "$start_time" ]; then
uptime_jiffies=$(awk '{print $1 * 100}' /proc/uptime 2>/dev/null)
uptime=$(awk "BEGIN{print int(($uptime_jiffies - $start_time) / 100)}")
fi
fi

# Get config from generated runtime config or UCI
# Runtime config is at /tmp/owsync.conf (when using ha-cluster)
if [ -f /tmp/owsync.conf ]; then
config_file="/tmp/owsync.conf"
elif [ -f /etc/owsync/owsync.conf ]; then
config_file="/etc/owsync/owsync.conf"
else
config_file=""
fi

if [ -n "$config_file" ]; then
# Parse config file for status info
sync_dir=$(grep "^sync_dir=" "$config_file" 2>/dev/null | cut -d= -f2)
db_path=$(grep "^database=" "$config_file" 2>/dev/null | cut -d= -f2)

# Check encryption mode
plain_mode=$(grep "^plain_mode=" "$config_file" 2>/dev/null | cut -d= -f2)
if [ "$plain_mode" != "1" ] && [ "$plain_mode" != "true" ]; then
encryption_enabled=1
fi

# Count peers
peer_count=$(grep -c "^peer=" "$config_file" 2>/dev/null || echo 0)
fi

# Fallback to UCI if config not found
if [ -z "$sync_dir" ] && [ -f /etc/config/owsync ]; then
sync_dir=$(uci -q get owsync.config.sync_dir)
fi

# Default database path
db_path="${db_path:-/var/lib/owsync/owsync.db}"

# Get database last modified time
if [ -f "$db_path" ]; then
db_last_modified=$(date -r "$db_path" +%s 2>/dev/null || echo 0)
fi

# Build JSON response
json_init
json_add_string status "$status"
json_add_int pid "$pid"
json_add_boolean enabled "$enabled"
json_add_int uptime "$uptime"

json_add_object database
json_add_string path "$db_path"
json_add_int last_modified "$db_last_modified"
json_close_object

json_add_object config
json_add_boolean encryption_enabled "$encryption_enabled"
json_add_int peer_count "$peer_count"
json_add_string sync_dir "$sync_dir"
json_close_object

json_dump
;;

*)
json_init
json_add_boolean success 0
json_add_string error "Unknown method: $2"
json_dump
exit 1
;;
esac
;;
esac
Loading