Bear Special Backup and Versioning - Shortcuts

Bear Special Backup and Versioning - Shortcuts

The Bear built in backup functions has some limitations, so in wait for the developer to improve on this, I made a couple of new Apple Shortcuts:

  1. Bear Special Backup
  2. Bear Versioning (Incremental backup)

Bear Special Backup - Shortcut

What it does:

  1. Backup of Bear as standard .bear2bk archive .
    This archive is needed for full restore!
    (But is not very accessible for individual notes restore)
  2. Extacts this archive to date-stamped folder as .textbundles.
    This folder gives easy access to do individual notes restore.
  3. Python3 script is included, correcting created and modified dates
    on all .textbundles and text.md files (getting these dates from info.json).
    This makes it easy to browse search for individual notes you need to restore.
  4. Moves trashed, archived, and encrypted notes into respective subfolders.
    (Yes, the encrypted notes are part of the backup, but they are kept encrypted in the info.json file with a zero byte text.md file)
  5. The Python script included in this shortcut, was coded and debugged in Microsoft Visual Studio Code for Mac with Micosoft Python plugins.

Caveats etc.

  1. Only runs on MacOS
  2. Only been tested on my limited 1,300 plus notes library.
  3. Run this weekly or monthly in tandem with the Bear Versioning (Incremental backup) shortcut described below. Or as often as you like, manually or sheduled.
  4. Default export folder is set to ~/Downloads/, change this as you please.
  5. I have NOT found any way to restore individual encrypted notes, hoping the developers could let us know how to do that – @trix180 @matteo ?
  6. Make any changes, you like or need to, but please try to figure it out – this is shared with you, but comes without support :sunglasses:

Bear Versioning - Incremental backup

What it does:

  1. Exports only notes changes since last exported note’s modification date.
  2. Notes with attachments are exported as .textbundles
  3. Notes without attachments are exported as plaintext .md files
  4. Newer versions of same notes, will get numbers added at end of file/package name, so you can retain versioning.
  5. It is the companlion to the the Bear Special Backup shortcut above.
  6. Default export folder is /iCloud/bear-export/Versions yyyy-MM/
  7. First run will export @last3days
  8. Run it daily or at least once a week. Or even better, as often as you need,

Caveats etc.

  1. Does not export notes in Archive and Trash and also not Locked (encrypted) notes
  2. Must be run at least once a week, otherwise some updates may be missed.
  3. Safer to run it mostly on one device only, to avoid file conflicts.
  4. So using the default export folder in iCloud, may the best for accessing versions on the other devices.
  5. I’m running it on schedule twice daily on iPhone.
  6. Best open Bear and wait for it to sync from other devices before running shortcut. But even so, next time it’s run, it should catch up with new updates.
  7. Also, run it as often as you like, manually when you want to save versions. It will only export notes that has been changed.
  8. Make any changes, you like or need to, but please try to figure it out – this is shared with you, but comes without support :sunglasses:

Happy versioning :nerd_face:

PS.

  • I found the free EasyFind.app to be very easy for searching and browsing in either export folders (Bear Special Backup extacted folder and the Bear Versioning export folder).
  • FAF is also a goood tool for this.
  • And not to forget the Panda.app! My default app for .textbundles. If it only had the promised built-in file/folder browser, it would be so much better :wink::wink:

#bear/shortcuts

5 Likes

Pretty cool. I will have a look at it later on my Mac.

If I export the notes to iCloud drive than I can even access the content easily with other apps.

1 Like

Here is the Python code included in the Bear Special Backup shortcut.
Pretty cool that we can include Python in Shortcuts (Mac only unfortunately)

# python 3.12
# 2025-03-02 at 16:19 IST

import os
import json
import datetime
import time
import shutil

backup_folder = input()
sub_folders = ['encrypted', 'trashed', 'archived'] 


def main():
    count = 0
    for (textbundle, dirs, files) in os.walk(backup_folder, topdown=True):
        if 'info.json' in files:
            info_json = read_file(textbundle + '/' + 'info.json')
            info = json.loads(info_json)
            created = info['net.shinyfrog.bear']['creationDate']
            modified = info['net.shinyfrog.bear']['modificationDate']
            set_file_dates(textbundle, created, modified)
            count += 1

    make_sub_folders()
    move_to_subs()
    print(count, 'Bear .textbundles extracted and date-stamped')


def set_file_dates(textbundle, created, modified):
    cre_dt = iso_convert(created)
    mod_dt = iso_convert(modified)
    os.utime(textbundle, (-1, cre_dt))
    os.utime(textbundle, (-1, mod_dt))
    os.utime(textbundle + '/' + 'text.md', (-1, cre_dt))
    os.utime(textbundle + '/' + 'text.md', (-1, mod_dt))


def make_sub_folders():
    for folder in sub_folders:
        sub = backup_folder + '/' + folder
        if not os.path.exists(sub):
            os.makedirs(sub)


def move_to_subs():    
    ''' Moving archived, trashed and encrypted notes 
        to respective sub folders '''
    for (textbundle, dirs, files) in os.walk(backup_folder, topdown=True):
        if 'info.json' in files:
            info_json = read_file(textbundle + '/' + 'info.json')
            info = json.loads(info_json)

            for folder in sub_folders:
                sub = backup_folder + '/' + folder
                try:
                    if info['net.shinyfrog.bear'][folder] == 1:
                        path = sub + '/' + os.path.basename(os.path.normpath(textbundle))
                        if not os.path.exists(path):
                            shutil.move(textbundle, sub)
                except:
                    pass


def iso_convert(iso_time):
    iso_time = iso_time.replace('Z', '')
    dt = datetime.datetime.fromisoformat(iso_time)
    return time.mktime(dt.timetuple())


def read_file(file_name):
    f = open(file_name, "r", encoding='utf-8')
    file_content = f.read()
    f.close()
    return file_content


main()

1 Like

Nice example of using mac shortcuts @roar!

1 Like