Skip to content

Instantly share code, notes, and snippets.

@kousu
Last active June 6, 2025 16:15
Show Gist options
  • Save kousu/623b25dbad2d084e4ffea540c65d3ff1 to your computer and use it in GitHub Desktop.
Save kousu/623b25dbad2d084e4ffea540c65d3ff1 to your computer and use it in GitHub Desktop.
DIY markdown wiki sync

Cloud-reduced Markdown Wiki (WIP)

This is a way to write and share notes for yourself between multiple devices, without signing up for locked-in cloud services, using only future-proof formats and open tools!

This is built on Android and Obsidian, which are only semi-open, but because it's built on syncing with git and markdown if you need to replace the platform you can, because markdown and git work everywhere and will continue to work everywhere for a long time.

Install

(Android) phone:

  1. Diary.apk

  2. (Optional) Install Markor which is a more fully-featured markdown editor

  3. Termux

  4. Termux:Widget

    • pay attention to all the setup steps, especially "Draw over other apps"
    • add the Widget to your homescreen
  5. Termux:API

    • enable Draw Over Other Apps here (TODO: this is intentionally buried on Android 14+, explain where to find it)
    • pkg install termux-api to give Termux access to it
  6. pkg install jq openssh git

  7. Install scripts below to ~/.shortcuts/sync-notes and ~/.local/bin/android_ssh_askpass.

    Remember to chmod +x both of them.

  8. Make an ssh key

    1. ssh-keygen -t ed25519
    2. (optional but STRONGLY recommended): set a passphrase on the key
      1. cat ~/.ssh/id_ed25519.pub + copy the public key and send it to yourself
    3. Install the key on whatever git server you're using
  9. termux-setup-storage + make sure to grant Termux access to the path containing your diary

  10. Set up a git repo:

    cd storage/shared
    git clone git+ssh://yhour-server/your-path/to-diary-repo.git Diary
    

    OR if you're starting from scratch:

    Open Diary at least once so that it creates its default folder, then sync that folder to a git server.

    cd storage/shared/Diary
    git init
    git remote add origin git+ssh://yhour-server/your-path/to-diary-repo.git
    git push -u origin
    

    but it's probably easier to init the repo from anywhere that's not your phone -- do it from your desktop, with Obsidian and GitHub/Lab/BitBucket/Forgejo/your own server with ssh access handy.

  11. If you set a passphrase, make it remain unlocked in RAM

    (or you'll be re-unlocking it every 15 minutes)

    1. sv enable ssh-agent
    2. Install Termux:Boot
    • make sure to follow the setup steps, especially disabling battery optimizations for both Termux and Termux:Boot
    • Put this in ~/.termux/boot/start-services.
    ```sh
    #!/data/data/com.termux/files/usr/bin/sh
    termux-wake-lock
    . $PREFIX/etc/profile
    ```
    - Run `termux-wake-lock` at least once and accept the "Disable battery optimizations" prompt that comes up (**TODO:** why does this have to be done twice? did I just not do it right the first time?)
        - `termux-wake-lock` is, **I think**, equivalent to "disable battery optimizations". It doesn't actually mean that your device will be forced to chew through power, especially not with just `ssh-agent` running which is asleep until something calls on it.
    - Reboot.
    
  12. (optional) Tune git to better defaults

    (these are my preferences, read about them first)

    git config --global advice.mergeConflict false
    git config --global merge.conflictStyle diff3
    git config --global push.default current
    git config --global push.autosetupremote true
    
  13. Test:

    • run ~/.shortcuts/sync-notes from Termux
    • run 'sync-notes' from the Termux:Widget on your Homescreen
  14. If works, schedule it:

    termux-job-scheduler -s ~/.shortcuts/sync-notes --period-ms 900000 --persisted true
    

Now your notes should also automatically attempt a sync every 15 minutes.

If the sync fails due to network problems or a conflict, your notes will be left untouched but you will get a notification about it, and you should cd storage/shared/Diary and attempt git pull to investigate the situation. If you have conflicts you will have to fix them using Diary or Markor, or maybe vi if you're comfortable with vi, and then recommit and push.

Desktop:

  1. vscode or Obsidian or Zettlr
  • these are the only markdown editors that currently exist that understand folders.
  • install Obsidian-Git and configure it to
    • commit-and-sync every 15 minutes
    • commit-and-sync on opening
  • there are lot of plugins for obsidian that help; but I don't want to become reliant on them

Server:

  1. Git
  2. (Optional) GitHub/Gitea/Gitlab/Forgejo (to get a web-based diary) 2. TODO: link to a guide to setup mkdocs

Usage

  • Edit files on phone

  • Wait for sync to happen in the background.

    • If it errors you will get the error as a notification, and also in logcat
  • Run sync-notes manually if you don't want to wait; it will pop to the foreground.

    • If it errors, you will see the error message on screen, and also in logcat
  • It will prompt you if it needs the key (on first boot, or if it's been idle long enough that Android has killed the background termux session)

    demo of unlocking ssh key

Alts

  • Obsidian cloud
  • PuppyGit (see demo)
  • the various orgmode apps in fdroid? I haven't explored these too much, but I think some of them do syncing?
  • EverNote, Notion

Tips

View log:

adb logcat | grep sync_notes

Bugs

  • Android is very kill-happy. Termux:Boot doesn't seem to be doing it's job of providing ssh-agent, and therefore the unlock prompt comes up over and over again.
    • Workaround: launching Termux and just leaving it in the background keeps ssh-agent alive.
    • To investigate: does using termux-wake-lock as recommended prevent this? Does termux-wake-lock have other negative consequences?
    • To investigate: instead of using the Android job scheduler, use a Termux:Widget ~/.shortcuts/tasks instead. Can that stay up in the background without being killed?
#!/data/data/com.termux/files/usr/bin/sh
# XXX SSH_KEYFILE is a non-standard feature. It's hacked in.
if [ -n "${SSH_KEYFILE}" ]; then
PROMPT="Passphrase for ${SSH_KEYFILE}"
else
PROMPT="Passphrase"
fi
termux-dialog text -p -t "Unlock SSH Key" -i "${PROMPT}" | jq -r '.text'
#!/data/data/com.termux/files/usr/bin/bash
ORIG_0=$0
sv enable ssh-agent # ssh-agent is killed by Android a lot. make sure it's up.
sv start ssh-agent # ((termux's [recommended](https://wiki.termux.com/wiki/Remote_Access#SSH_Agent) solution
# is to use their `ssha` wrapper, but it's not fully transparent: it runs ssh-agent
# outside of sv, and it auto-adds all identities, so it gets messy when called by git)
# `sv start ssh-agent` + setting SSH_AUTH_SOCK is effectively all it does.
export SSH_AUTH_SOCK="${XDG_RUNTIME_DIR:-"${TERMUX__PREFIX:-"${PREFIX}"}/var/run"}"/ssh-agent.socket
export SSH_KEYFILE=$(ls -1 ~/.ssh/id_* | head -n 1) # hack; assumes only one ssh key exists.
export SSH_ASKPASS=~/.local/bin/android_ssh_askpass # TODO: move this to /etc/profile.d?
export SSH_ASKPASS_REQUIRE=force
export GIT_SSH_COMMAND="ssh -o AddKeysToAgent=yes"
gitsync() {
set -e
# The git commands are all `set -x` so that their output
# comes with a header explaining what it is in the log
# (but the logic around them is hidden)
#
(set -x; git stage --all .) # NB: --all means stage deletions too, not just new and modified files
if ! git diff --staged --quiet; then
#
(set -x; git commit -m "Syncing")
fi
# Instead of pull --rebase, do separate fetch + rebase,
# so that we can distinguish error cases:
# network/auth problems will fail at fetch
# and merge conflicts will fail at rebase
(set -x; git fetch)
# https://stackoverflow.com/questions/44953117/can-git-tell-me-if-a-rebase-will-conflict-without-actually-rebasing :
# TODO: read git config pull.rebase to decide between using rebase or merge
(set -x; git rebase) || (ORIG_RC=$?; (set -x; git rebase --abort) && exit $ORIG_RC);
(set -x; git push)
}
# Run a commmand; on **error**, output to logcat and android notifications
chrony() {
LOG=$(mktemp)
trap 'rm $LOG' EXIT
"$@" 2>&1 | tee $LOG
# Unlike the real chrony, this always outputs to stderr;
# this is good -for me- because I want to see what happens when
# I tap "sync", means this function is misleadingly named.
#
# is it possible to send stdout and stderr to the same log file
# while also both sending them to /dev/stdout and /dev/stderr?
RC="${PIPESTATUS[0]}"
if [ "$RC" -ne 0 ]; then
(cat $LOG; echo "Sync $(basename $(pwd)) Failed") | logger -t sync_notes
if ! tty -s; then
# if in batch mode, give feedback via notification
# (need to use tail because the notif gets cropped; unclear what the limit is
# and whether it's in lines, bytes, characters, it seems to bounce around)
tail -n 10 $LOG | termux-notification --id sync_notes --icon dangerous --title "Sync $(basename $(pwd)) Failed" --button1 Retry --button1-action "$ORIG_0"
fi
#else
# echo "Sync $(basename $(pwd)) Succeeded" | tee /dev/stderr | logger -t sync_notes
fi
exit $RC
}
cd ~/storage/shared/Diary/
chrony gitsync
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment