
Security News
ESLint Adds Official Support for Linting HTML
ESLint now supports HTML linting with 48 new rules, expanding its language plugin system to cover more of the modern web development stack.
Primitive Sync - Bidirectional and unidirectional sync over SFTP. Multiplatform Python script optimized for the Primitive FTPd Android SFTP server.
Bidirectional and unidirectional sync over SFTP. Multiplatform Python script optimized for the Primitive FTPd Android SFTP server (required minimum version is 7.3).
Why another sync solution? Because none of the professional solutions can write SD cards and follow local symlinks, or are extremely slow or full of bugs (Syncthing, Resilio Sync, rsync, osync, rclone, Unison). I gave up and wrote it.
See my other project, https://github.com/lmagyar/prim-ctrl, for remote control of your phone's Primitive FTPd SFTP server and optionally Tailscale VPN.
See my other project, https://github.com/lmagyar/prim-batch, for batch execution of prim-ctrl and prim-sync commands.
Note: These are my first ever Python projects, any comments on how to make them better are appreciated.
You need to install:
Primitive FTPd on your phone - see: https://github.com/wolpi/prim-ftpd
Install from F-Droid (not from Google Play) (required minimum version is 7.3)
Python 3.12+, pip and venv on your laptop - see: https://www.python.org/downloads/ or
sudo apt update
sudo apt upgrade
sudo apt install python3 python3-pip python3-venv
winget install Python.Python.3.12
choco install python3 -y
pipx - see: https://pipx.pypa.io/stable/installation/#installing-pipx or
sudo apt install pipx
pipx ensurepath
py -m pip install --user pipx
py -m pipx ensurepath
This repo
pipx install prim-sync
Optionally, if you want to edit or even contribute to the source, you also need to install:
pipx install poetry
You have to enable Primitive FTPd to run as much in the background as possible, please see the relevant Readme section.
Either use the built-in zeroconf (DNS-SD) functionality in Primitive FTPd (see below), or set up a constant address (IP or host name, for the -a option) for your phone (fixed LAN IP, VPN, hosts file, your choice).
You need to generate an SSH key pair:
Execute:
sudo apt install openssh-client
mkdir ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_sftp -N ""
Note: See below later, how to protect the private SSH key with passphrase.
Go to Settings / System / Optional features / Add an optional feature and add "OpenSSH Client"
Execute:
mkdir %USERPROFILE%\.ssh
ssh-keygen -t ed25519 -f %USERPROFILE%\.ssh\id_ed25519_sftp -N ""
Note: See below later, how to protect the private SSH key with passphrase.
Then install it in Primitive FTPd:
Use your favorite SFTP client (eg. WinSCP, FileZilla) to access the Primitive FTPd, use username/password to authenticate.
Note: Even if you plan to access Primitive FTPd through zeroconf (DNS-SD), use it's hostname or IP to connect to it at this step.
Open for editing the /fs/storage/emulated/0/Android/data/org.primftpd/files/.ssh/authorized_keys
file.
Append the content of the previously generated .ssh/id_ed25519_sftp.pub
file to it. It is something like "ssh-ed25519 XXXxxxXXXxxx you@your-device"
Then add your phone to the known_hosts file if your favorite SFTP client hasn't done it:
Use ssh to access the Primitive FTPd, use username/password to authenticate.
Note: Even if you plan to access Primitive FTPd through zeroconf (DNS-SD), use it's hostname or IP to connect to it at this step.
ssh -oUserKnownHostsFile=~/.ssh/known_hosts -oPort=2222 sftp@your.phone.host.name
ssh -oUserKnownHostsFile=%USERPROFILE%\.ssh\known_hosts -oPort=2222 sftp@your.phone.host.name
Acceph host key
The error "shell request failed on channel 0" is OK, there is no SSH server in Primitive FTPd, our goal was to connect and store the server key in the known_hosts file.
If you plan to access Primitive FTPd through zeroconf (DNS-SD):
[your.phone.host.name]:2222 ssh-ed25519 XXXxxxXXXxxx
[your.phone.host.name]:2222
text with the Primitive FTPd Servername configuration option, see above (that is sg. like your-phone-pftpd
), so it will look sg. like:
your-phone-pftpd ssh-ed25519 XXXxxxXXXxxx
We can protect the private SSH key generated above with a passphrase and use the ssh-agent to store the unprotected key in memory and help the SSH client in prim-sync to authenticate with Primitive FTPd.
Protect the already generated key with a passphrase:
ssh-keygen -p -f ~/.ssh/id_ed25519_sftp
Install ssh-agent as a systemd service:
Useful commands:
Note: Identities that you've added will not be available after reboot.
ssh-add ~/.ssh/id_ed25519_sftp # Adds private key identities to the agent
ssh-add -L # Lists public key parameters of all identities currently represented by the agent
ssh-add -d ~/.ssh/id_ed25519_sftp # Removes private key identities from the agent
Protect the already generated key with a passphrase:
ssh-keygen -p -f %USERPROFILE%\.ssh\id_ed25519_sftp
Start ssh-agent (OpenSSH Authentication Agent) automatically as a service (use an administrative PowerShell terminal):
Get-Service ssh-agent | Set-Service -StartupType Automatic -PassThru | Start-Service
Useful commands:
Note: Identities that you've added will be available even after reboot.
ssh-add %USERPROFILE%\.ssh\id_ed25519_sftp # Adds private key identities to the agent
ssh-add -L # Lists public key parameters of all identities currently represented by the agent
ssh-add -d %USERPROFILE%\.ssh\id_ed25519_sftp # Removes private key identities from the agent
Create a backup of your files!!! Really!!! If you use symlinks, this is only question of time when will you delete something unintendedly!!!
The first upload is better done over USB connection and manual copy, because copying files over Wi-Fi is much slower.
The first run will be longer than a regular run, because without prior knowledge, the prim-sync script handles all files on both sides as newly created and compares them or their hashes (hashing is much faster than downloading and comparing the content).
On regular runs the meaning of the log lines are:
Notes:
prim-sync your-phone-pftpd id_ed25519_sftp -t -sh -rs "/fs/storage/emulated/0" "~/Mobile" "/fs/storage/XXXX-XXXX" "/saf" "Camera" "DCIM/Camera"
prim-sync your-phone-pftpd id_ed25519_sftp -t -sh -rs "/fs/storage/emulated/0" -uo -m --overwrite-destination "~/Mobile" "/fs/storage/XXXX-XXXX" "/saf" "Music" "*"
prim-sync your-phone-pftpd id_ed25519_sftp -t -sh -rs "/fs/storage/emulated/0" -a your.phone.host.name 2222 "~/Mobile" "/fs/storage/emulated/0" "*" "Screenshots" "DCIM/Screenshots"
prim-sync your-phone-pftpd id_ed25519_sftp -t -sh -rs "/fs/storage/emulated/0" "D:\Mobile" "/fs/storage/XXXX-XXXX" "/saf" "Camera" "DCIM/Camera"
prim-sync your-phone-pftpd id_ed25519_sftp -t -sh -rs "/fs/storage/emulated/0" -uo -m --overwrite-destination "D:\Mobile" "/fs/storage/XXXX-XXXX" "/saf" "Music" "*"
prim-sync your-phone-pftpd id_ed25519_sftp -t -sh -rs "/fs/storage/emulated/0" -a your.phone.host.name 2222 "D:\Mobile" "/fs/storage/emulated/0" "*" "Screenshots" "DCIM/Screenshots"
usage: prim-sync [-h] [-a host port] [-ui | -uo] [-d] [-D] [-rs PATH] [--overwrite-destination] [--folder-symlink-as-destination] [--ignore-locks [MINUTES]] [-t] [-s] [-ss] [-sh] [--debug] [-M] [-C] [-H]
[-n | -o] [-cod | -doc] [-l [PATTERN ...]] [-r [PATTERN ...]] [-m [PATTERN ...]]
server-name keyfile local-prefix remote-read-prefix remote-write-prefix local-folder remote-folder
Bidirectional and unidirectional sync over SFTP. Multiplatform Python script optimized for the Primitive FTPd Android SFTP server (https://github.com/wolpi/prim-ftpd), for more details see https://github.com/lmagyar/prim-sync
positional arguments:
server-name unique name for the server (if zeroconf is used, then the Servername configuration option from Primitive FTPd, otherwise see the --address option also)
keyfile private SSH key filename located under your .ssh folder
local-prefix local path to the parent of the folder to be synchronized
remote-read-prefix read-only remote path to the parent of the folder to be synchronized, eg. /fs/storage/XXXX-XXXX or /rosaf
remote-write-prefix read-write remote path to the parent of the folder to be synchronized, eg. /saf (you can use * if this is the same as the read-only remote path above)
local-folder the local folder name to be synchronized
remote-folder the remote folder name to be synchronized (you can use * if this is the same as the local folder name above)
options:
-h, --help show this help message and exit
-a host port, --address host port if zeroconf is not used, then the address of the server
-ui, --unidirectional-inward unidirectional inward sync (default is bidirectional sync)
-uo, --unidirectional-outward unidirectional outward sync (default is bidirectional sync)
-d, --dry no files changed in the synchronized folder(s), only internal state gets updated and temporary files get cleaned up
-D, --dry-on-conflict in case of unresolved conflict(s), run dry
-rs PATH, --remote-state-prefix PATH
stores remote state in a common .prim-sync folder under PATH instead of under the remote-folder argument (decreases SD card wear), eg. /fs/storage/emulated/0
Note: currently only the .lock file is stored here
Note: if you access the same server from multiple clients, you have to specify the same --remote-state-prefix option everywhere to prevent concurrent access
--overwrite-destination don't use temporary files and renaming for failsafe updates - it is faster, but you will definitely shoot yourself in the foot when used with bidirectional sync
--folder-symlink-as-destination enables writing and deleting symlinked folders and files in them on the local side - it can make sense, but you will definitely shoot yourself in the foot
--ignore-locks [MINUTES] ignore locks left over from previous run, optionally only if they are older than MINUTES minutes
logging:
-t, --timestamp prefix each message with a timestamp
-s, --silent only errors printed
-ss, --silent-scanning don't print scanned remote folders as progress indicator
-sh, --silent-headers don't print headers
--debug use debug level logging and add stack trace for exceptions, disables the --silent and enables the --timestamp options
comparison:
-M, --dont-use-mtime-for-comparison
beyond size, modification time or content must be equal, if both are disabled, only size is compared
-C, --dont-use-content-for-comparison
beyond size, modification time or content must be equal, if both are disabled, only size is compared
-H, --dont-use-hash-for-content-comparison
not all sftp servers support hashing, but downloading content for comparison is much slower than hashing
bidirectional conflict resolution:
-n, --newer-wins in case of conflict, newer file wins
-o, --older-wins in case of conflict, older file wins
-cod, --change-wins-over-deletion in case of conflict, changed/new file wins over deleted file
-doc, --deletion-wins-over-change in case of conflict, deleted file wins over changed/new file
-l [PATTERN ...], --local-wins-patterns [PATTERN ...]
in case of conflict, local files matching this Unix shell PATTERN win, multiple values are allowed, separated by space
if no PATTERN is specified, local always wins
-r [PATTERN ...], --remote-wins-patterns [PATTERN ...]
in case of conflict, remote files matching this Unix shell PATTERN win, multiple values are allowed, separated by space
if no PATTERN is specified, remote always wins
unidirectional conflict resolution:
-m [PATTERN ...], --mirror-patterns [PATTERN ...]
in case of conflict, mirror source side files matching this Unix shell PATTERN to destination side, multiple values are allowed, separated by space
if no PATTERN is specified, all files will be mirrored
FAQs
Primitive Sync - Bidirectional and unidirectional sync over SFTP. Multiplatform Python script optimized for the Primitive FTPd Android SFTP server.
We found that prim-sync demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ESLint now supports HTML linting with 48 new rules, expanding its language plugin system to cover more of the modern web development stack.
Security News
CISA is discontinuing official RSS support for KEV and cybersecurity alerts, shifting updates to email and social media, disrupting automation workflows.
Security News
The MCP community is launching an official registry to standardize AI tool discovery and let agents dynamically find and install MCP servers.