├── .github └── workflows │ └── build-definitions.yml ├── Definitions ├── 1password.yaml ├── affinity-designer.yaml ├── affinity-photo.yaml ├── alfred.yaml ├── asciinema.yaml ├── awareness.yaml ├── bartender.yaml ├── bettertouchtool.yaml ├── cockatrice.yaml ├── dash.yaml ├── dolphin.yaml ├── dropshelf.yaml ├── due.yaml ├── enjoyable.yaml ├── epic-games-launcher.yaml ├── eslint.yaml ├── f-lux.yaml ├── fantastical.yaml ├── finder.yaml ├── fish.yaml ├── gallery-dl.yaml ├── geotoad.yaml ├── git.yaml ├── google-chrome.yaml ├── helix.yaml ├── ia-writer.yaml ├── iterm2.yaml ├── keka.yaml ├── keystroke-pro.yaml ├── klokki-slim.yaml ├── launchd-agents.yaml ├── little-snitch.yaml ├── livestreamer.yaml ├── lunar.yaml ├── macos-fonts.yaml ├── macos-sounds.yaml ├── macos-spelling.yaml ├── macupdater.yaml ├── maid.yaml ├── mail.yaml ├── massren.yaml ├── messages.yaml ├── mpv.yaml ├── multitouch.yaml ├── neovim.yaml ├── openemu.yaml ├── peek.yaml ├── phoenix.yaml ├── processing.yaml ├── pure-paste.yaml ├── rclone.yaml ├── reeder.yaml ├── rubocop.yaml ├── ruby.yaml ├── safari.yaml ├── script-editor.yaml ├── sip.yaml ├── spark.yaml ├── ssh.yaml ├── tape.yaml ├── textual.yaml ├── transmission.yaml ├── visual-studio-code.yaml ├── vmware-fusion.yaml ├── yacreader.yaml ├── yt-dlp.yaml └── zsh.yaml ├── LICENSE ├── README.md ├── Resources └── build-full-definition ├── definitions.json └── tape /.github/workflows/build-definitions.yml: -------------------------------------------------------------------------------- 1 | name: Build Definitions File 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | with: 16 | persist-credentials: true 17 | - name: Build definitions JSON 18 | run: | 19 | ./Resources/build-full-definition './Definitions' './definitions.json' 20 | - name: Set up git user 21 | run: | 22 | git config user.name "github-actions" 23 | git config user.email "actions@users.noreply.github.com" 24 | - name: Commit if there are changes then push 25 | run: | 26 | git add './definitions.json' 27 | git diff-index --quiet HEAD || git commit --message 'Updated definitions file' 28 | git push 29 | -------------------------------------------------------------------------------- /Definitions/1password.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 1Password 3 | ids: 4 | - com.agilebits.onepassword7 5 | - com.1password.1password 6 | 7 | paths: ~ 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/affinity-designer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Affinity Designer 3 | ids: 4 | - com.seriflabs.affinitydesigner2 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/affinity-photo.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Affinity Photo 3 | ids: 4 | - com.seriflabs.affinityphoto2 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/alfred.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Alfred 3 | ids: 4 | - com.runningwithcrayons.Alfred 5 | - com.runningwithcrayons.Alfred-Preferences 6 | 7 | paths: 8 | - ~/Library/Application Support/Alfred 9 | ... 10 | -------------------------------------------------------------------------------- /Definitions/asciinema.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: asciinema 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.asciinema 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/awareness.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Awareness 3 | ids: 4 | - com.futureproof.awareness 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/bartender.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bartender 3 | ids: 4 | - com.surteesstudios.Bartender 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/bettertouchtool.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: BetterTouchTool 3 | ids: 4 | - com.hegenberg.BetterTouchTool 5 | 6 | paths: 7 | - ~/Library/Application Support/BetterTouchTool 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/cockatrice.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cockatrice 3 | ids: 4 | - com.cockatrice.cockatrice 5 | 6 | paths: 7 | - ~/Library/Application Support/Cockatrice/Cockatrice/cards.xml 8 | - ~/Library/Application Support/Cockatrice/Cockatrice/customsets 9 | - ~/Library/Application Support/Cockatrice/Cockatrice/decks 10 | - ~/Library/Application Support/Cockatrice/Cockatrice/pics/CUSTOM 11 | - ~/Library/Application Support/Cockatrice/Cockatrice/replays 12 | - ~/Library/Application Support/Cockatrice/Cockatrice/settings 13 | - ~/Library/Application Support/Cockatrice/Cockatrice/themes 14 | - ~/Library/Application Support/Cockatrice/Cockatrice/tokens.xml 15 | ... 16 | -------------------------------------------------------------------------------- /Definitions/dash.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dash 3 | ids: 4 | - com.kapeli.dashdoc 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/dolphin.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dolphin 3 | ids: 4 | - org.dolphin-emu.dolphin 5 | 6 | paths: 7 | - ~/Library/Application Support/Dolphin/Config 8 | - ~/Library/Application Support/Dolphin/GC/EUR 9 | - ~/Library/Application Support/Dolphin/GC/JAP 10 | - ~/Library/Application Support/Dolphin/GC/USA 11 | - ~/Library/Application Support/Dolphin/Wii/title 12 | ... 13 | -------------------------------------------------------------------------------- /Definitions/dropshelf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dropshelf 3 | ids: 4 | - com.pilotmoon.Dropshelf 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/due.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Due 3 | ids: 4 | - com.phocusllp.duemac 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/enjoyable.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enjoyable 3 | ids: 4 | - com.yukkurigames.Enjoyable 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/epic-games-launcher.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Epic Games Launcher 3 | ids: 4 | - com.epicgames.EpicGamesLauncher 5 | 6 | paths: 7 | - ~/Library/Preferences/Unreal Engine/EpicGamesLauncher/Mac/GameUserSettings.ini 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/eslint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ESLint 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.eslintrc 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/f-lux.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: f.lux 3 | ids: 4 | - org.herf.Flux 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/fantastical.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Fantastical 3 | ids: 4 | - com.flexibits.fantastical 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/finder.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Finder 3 | ids: 4 | - com.apple.finder 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/fish.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: fish 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/fish 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/gallery-dl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: gallery-dl 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/gallery-dl 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/geotoad.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: GeoToad 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/GeoToad 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/git.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Git 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.gitconfig 7 | - ~/.gitignore 8 | - ~/.config/git 9 | ... 10 | -------------------------------------------------------------------------------- /Definitions/google-chrome.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Google Chrome 3 | ids: 4 | - com.google.Chrome 5 | 6 | paths: 7 | - ~/Library/Application Support/Google/Chrome/Default/Extensions 8 | - ~/Library/Application Support/Google/Chrome/Default/Local Extension Settings 9 | - ~/Library/Application Support/Google/Chrome/Default/Preferences 10 | - ~/Library/Application Support/Google/Chrome/Default/Secure Preferences 11 | ... 12 | -------------------------------------------------------------------------------- /Definitions/helix.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Helix 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/helix 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/ia-writer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: iA Writer 3 | ids: 4 | - pro.writer.mac 5 | 6 | paths: 7 | - ~/Library/Containers/pro.writer.mac/Data/Library/Application Support/iA Writer/Templates 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/iterm2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: iTerm2 3 | ids: 4 | - com.googlecode.iterm2 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/keka.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Keka 3 | ids: 4 | - com.aone.keka 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/keystroke-pro.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Keystroke Pro 3 | ids: 4 | - de.ixeau.KeystrokePro2 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/klokki-slim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Klokki Slim 3 | ids: 4 | - com.klokki-slim.macos 5 | 6 | paths: 7 | - ~/Library/Containers/com.klokki-slim.macos/Data/Library/Application Support/com.klokki-slim.macos 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/launchd-agents.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: launchd Agents 3 | ids: ~ 4 | 5 | paths: 6 | - ~/Library/LaunchAgents 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/little-snitch.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Little Snitch 3 | ids: 4 | - at.obdev.LittleSnitchAgent 5 | - at.obdev.LittleSnitchConfiguration 6 | - at.obdev.LittleSnitchNetworkMonitor 7 | - at.obdev.LittleSnitchSoftwareUpdate 8 | 9 | paths: 10 | - ~/Library/Application Support/Little Snitch 11 | ... 12 | -------------------------------------------------------------------------------- /Definitions/livestreamer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Livestreamer 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.livestreamerrc 7 | - ~/.config/livestreamer 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/lunar.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lunar 3 | ids: 4 | - fyi.lunar.Lunar 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/macos-fonts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: macOS Fonts 3 | ids: ~ 4 | 5 | paths: 6 | - ~/Library/Fonts 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/macos-sounds.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: macOS Sounds 3 | ids: ~ 4 | 5 | paths: 6 | - ~/Library/Sounds 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/macos-spelling.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: macOS Spelling 3 | ids: ~ 4 | 5 | paths: 6 | - ~/Library/Spelling/LocalDictionary 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/macupdater.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: MacUpdater 3 | ids: 4 | - com.corecode.MacUpdater 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/maid.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Maid 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.maid 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/mail.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mail 3 | ids: 4 | - com.apple.mail 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/massren.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Massren 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/massren/profile.sqlite 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/messages.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Messages 3 | ids: 4 | - com.apple.iChat 5 | 6 | paths: 7 | - ~/Library/Application Scripts/com.apple.iChat 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/mpv.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: mpv 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/mpv 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/multitouch.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Multitouch 3 | ids: 4 | - com.brassmonkery.Multitouch 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/neovim.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Neovim 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/nvim 7 | - ~/.local/share/nvim 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/openemu.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: OpenEmu 3 | ids: 4 | - org.openemu.OpenEmu 5 | 6 | paths: 7 | - ~/Library/Application Support/OpenEmu 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/peek.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Peek 3 | ids: 4 | - com.bigzlabs.peek 5 | 6 | Paths: 7 | - ~/Library/Containers/pro.writer.mac/Data/Library/Application Support/iA Writer/Templates 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/phoenix.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Phoenix 3 | ids: 4 | - org.khirviko.Phoenix 5 | 6 | paths: 7 | - ~/.phoenix.js 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/processing.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Processing 3 | ids: 4 | - org.processing.app 5 | 6 | paths: 7 | - ~/Library/Processing/preferences.txt 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/pure-paste.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pure Paste 3 | ids: 4 | - com.sindresorhus.Pure-Paste 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/rclone.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Rclone 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/rclone 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/reeder.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Reeder 3 | ids: 4 | - com.reederapp.5.macOS 5 | 6 | paths: 7 | - ~/Library/Containers/com.reederapp.5.macOS/Data/Library/Application Support/users.json 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/rubocop.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Rubocop 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.rubocop.yml 7 | - ~/.config/rubocop/config.yml 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/ruby.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ruby 3 | ids: ~ 4 | 5 | paths: 6 | - .gem/credentials 7 | - .gemrc 8 | - .irbrc 9 | - .pryrc 10 | ... 11 | -------------------------------------------------------------------------------- /Definitions/safari.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Safari 3 | ids: 4 | - com.apple.Safari 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/script-editor.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Script Editor 3 | ids: 4 | - com.apple.ScriptEditor2 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/sip.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Sip 3 | ids: 4 | - com.ruiaureliano.Sip 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/spark.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Spark 3 | ids: 4 | - com.readdle.smartemail-Mac 5 | 6 | paths: ~ 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/ssh.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: SSH 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.ssh 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/tape.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tape 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/tape/config.yaml 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/textual.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Textual 3 | ids: 4 | - com.codeux.irc.textual5 5 | 6 | paths: 7 | - ~/Library/Application Support/Textual IRC 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/transmission.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Transmission 3 | ids: 4 | - org.m0k.transmission 5 | 6 | paths: 7 | - ~/Library/Application Support/Transmission/blocklists 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/visual-studio-code.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Visual Studio Code 3 | ids: 4 | - com.microsoft.VSCode 5 | 6 | paths: 7 | - ~/Library/Application Support/Code/User 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/vmware-fusion.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: VMWare Fusion 3 | ids: 4 | - com.vmware.fusion 5 | 6 | paths: 7 | - ~/Library/Application Support/VMware Fusion 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/yacreader.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: YACReader 3 | ids: 4 | - com.yourcompany.YACReader 5 | 6 | paths: 7 | - ~/Library/Application Support/YACReader 8 | ... 9 | -------------------------------------------------------------------------------- /Definitions/yt-dlp.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: yt-dlp 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.config/yt-dlp 7 | ... 8 | -------------------------------------------------------------------------------- /Definitions/zsh.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Zsh 3 | ids: ~ 4 | 5 | paths: 6 | - ~/.zlogin 7 | - ~/.zlogout 8 | - ~/.zprofile 9 | - ~/.zsh_history 10 | - ~/.zshenv 11 | - ~/.zshrc 12 | ... 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Vítor Galvão 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # icon Tape 2 | 3 | Tape is a command-line tool to backup and restore software settings on macOS. It can back up preferences for apps (including from the Mac App Store or Apples’s own), command-line tools, and even macOS customisations like your sounds or local spelling dictionary. 4 | 5 | ## Installation 6 | 7 | Install with [Homebrew](https://brew.sh): 8 | 9 | ```shell 10 | brew install vitorgalvao/tiny-scripts/tape 11 | ``` 12 | 13 | Alternatively, download the executable at the root of this repository and call it directly. 14 | 15 | ## Usage 16 | 17 | ``` 18 | Backup and restore software settings on macOS 19 | 20 | Usage: 21 | tape backup Update definitions and backup settings 22 | tape restore [def] Restore settings from previous backup 23 | Giving a definition name restores only that software 24 | tape list Show names of supported software separated by what will be backed up 25 | tape list tokens Show tokens of supported software separated by what will be backed up 26 | tape launchd Load or unload an agent to perform daily backups 27 | tape update Force update of backup definitions 28 | tape version Show tape version 29 | tape help Show this help 30 | ``` 31 | 32 | If you intend to run Tape on-demand, run `tape backup` on occasion and you’re good to go. If you want to set it and forget it, run `tape launchd on` and it will automatically run backups for you everyday. Give them a look once in a blue moon to ensure everything is going smoothly. 33 | 34 | ## Supported software 35 | 36 | Run `tape list` to see what’s supported. 37 | 38 | ## Configuration 39 | 40 | Tape stores its configuration in `~/.config/tape/config.json`. If it doesn’t exist, it will be created on first run with sensible defaults. Quick example: 41 | 42 | ```json 43 | { 44 | "backup_to": "~/.config/tape/Backups", 45 | "keep": 5, 46 | "exclude": ["affinity-designer", "ssh"], 47 | "include": [] 48 | } 49 | ``` 50 | 51 | | Key | Type | Description | 52 | | ----------- | ------------ | ------------------------------------------------------------------------------------------------- | 53 | | `backup_to` | String | Directory to save backups to (leading `~` is expanded to your home directory). | 54 | | `keep` | Integer | Number of backups to keep. Must be higher than zero. | 55 | | `exclude` | String Array | By default, Tape backs up settings for every software it knows how, except the ones on this list. | 56 | | `include` | String Array | If set, *only* these will be backed up and the `exclude` list will be ignored. | 57 | 58 | To see what is included or excluded from backups, run `tape list`. To add to `include` or `exclude`, use the app token: (`tape list tokens`). 59 | 60 | By default, Tape will backup its own configuration with the others. 61 | 62 | ## How it works 63 | 64 | Tape backs up settings into compressed `.tgz` files. These are then used for restores when needed. This approach is conducive to experimentation, because as long as you keep a specific good configuration you can roll back to it. 65 | 66 | ## Contributing 67 | 68 | The whole script is the single file `tape` at the root. Pull requests will be reviewed but please keep changes manageable—multiple small contributions are preferred to a large one. 69 | 70 | To add support for new software, use one of the [definitions](https://github.com/vitorgalvao/tape/tree/main/Definitions) as a starting point. Two tips: 71 | 72 | * Use `mdls -raw -name kMDItemCFBundleIdentifier /path/to/the/app` to find the bundle identifier of an app. 73 | * Plist files in `~/Library/Preferences` are likely safe to skip because those are closely tied to the bundle identifier, thus implicitly taken care of by normal Tape backups. 74 | 75 | ## License 76 | 77 | 2-Clause BSD 78 | -------------------------------------------------------------------------------- /Resources/build-full-definition: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | require 'optparse' 5 | require 'pathname' 6 | require 'yaml' 7 | 8 | # Options 9 | ARGV.push('--help') if ARGV.count < 2 10 | 11 | OptionParser.new do |parser| 12 | parser.banner = <<~BANNER 13 | Join all YAML defintions into a single JSON. 14 | 15 | Usage: 16 | #{File.basename($PROGRAM_NAME)} 17 | BANNER 18 | end.parse! 19 | 20 | # Main 21 | Definitions_dir = Pathname.new(ARGV[0]) 22 | Full_definitions_file = Pathname.new(ARGV[1]) 23 | All_definition_files = Definitions_dir.children.select { |p| p.extname == '.yaml' }.sort 24 | 25 | Joined_definitions = All_definition_files.reduce({}) { |accumulator, current| 26 | key = current.basename(current.extname).to_path 27 | accumulator[key.to_sym] = YAML.load_file(current) 28 | accumulator 29 | } 30 | 31 | Full_definitions_file.write(JSON.pretty_generate(Joined_definitions)) 32 | -------------------------------------------------------------------------------- /definitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1password": { 3 | "name": "1Password", 4 | "ids": [ 5 | "com.agilebits.onepassword7", 6 | "com.1password.1password" 7 | ], 8 | "paths": null 9 | }, 10 | "affinity-designer": { 11 | "name": "Affinity Designer", 12 | "ids": [ 13 | "com.seriflabs.affinitydesigner2" 14 | ], 15 | "paths": null 16 | }, 17 | "affinity-photo": { 18 | "name": "Affinity Photo", 19 | "ids": [ 20 | "com.seriflabs.affinityphoto2" 21 | ], 22 | "paths": null 23 | }, 24 | "alfred": { 25 | "name": "Alfred", 26 | "ids": [ 27 | "com.runningwithcrayons.Alfred", 28 | "com.runningwithcrayons.Alfred-Preferences" 29 | ], 30 | "paths": [ 31 | "~/Library/Application Support/Alfred" 32 | ] 33 | }, 34 | "asciinema": { 35 | "name": "asciinema", 36 | "ids": null, 37 | "paths": [ 38 | "~/.asciinema" 39 | ] 40 | }, 41 | "awareness": { 42 | "name": "Awareness", 43 | "ids": [ 44 | "com.futureproof.awareness" 45 | ], 46 | "paths": null 47 | }, 48 | "bartender": { 49 | "name": "Bartender", 50 | "ids": [ 51 | "com.surteesstudios.Bartender" 52 | ], 53 | "paths": null 54 | }, 55 | "bettertouchtool": { 56 | "name": "BetterTouchTool", 57 | "ids": [ 58 | "com.hegenberg.BetterTouchTool" 59 | ], 60 | "paths": [ 61 | "~/Library/Application Support/BetterTouchTool" 62 | ] 63 | }, 64 | "cockatrice": { 65 | "name": "Cockatrice", 66 | "ids": [ 67 | "com.cockatrice.cockatrice" 68 | ], 69 | "paths": [ 70 | "~/Library/Application Support/Cockatrice/Cockatrice/cards.xml", 71 | "~/Library/Application Support/Cockatrice/Cockatrice/customsets", 72 | "~/Library/Application Support/Cockatrice/Cockatrice/decks", 73 | "~/Library/Application Support/Cockatrice/Cockatrice/pics/CUSTOM", 74 | "~/Library/Application Support/Cockatrice/Cockatrice/replays", 75 | "~/Library/Application Support/Cockatrice/Cockatrice/settings", 76 | "~/Library/Application Support/Cockatrice/Cockatrice/themes", 77 | "~/Library/Application Support/Cockatrice/Cockatrice/tokens.xml" 78 | ] 79 | }, 80 | "dash": { 81 | "name": "Dash", 82 | "ids": [ 83 | "com.kapeli.dashdoc" 84 | ], 85 | "paths": null 86 | }, 87 | "dolphin": { 88 | "name": "Dolphin", 89 | "ids": [ 90 | "org.dolphin-emu.dolphin" 91 | ], 92 | "paths": [ 93 | "~/Library/Application Support/Dolphin/Config", 94 | "~/Library/Application Support/Dolphin/GC/EUR", 95 | "~/Library/Application Support/Dolphin/GC/JAP", 96 | "~/Library/Application Support/Dolphin/GC/USA", 97 | "~/Library/Application Support/Dolphin/Wii/title" 98 | ] 99 | }, 100 | "dropshelf": { 101 | "name": "Dropshelf", 102 | "ids": [ 103 | "com.pilotmoon.Dropshelf" 104 | ], 105 | "paths": null 106 | }, 107 | "due": { 108 | "name": "Due", 109 | "ids": [ 110 | "com.phocusllp.duemac" 111 | ], 112 | "paths": null 113 | }, 114 | "enjoyable": { 115 | "name": "Enjoyable", 116 | "ids": [ 117 | "com.yukkurigames.Enjoyable" 118 | ], 119 | "paths": null 120 | }, 121 | "epic-games-launcher": { 122 | "name": "Epic Games Launcher", 123 | "ids": [ 124 | "com.epicgames.EpicGamesLauncher" 125 | ], 126 | "paths": [ 127 | "~/Library/Preferences/Unreal Engine/EpicGamesLauncher/Mac/GameUserSettings.ini" 128 | ] 129 | }, 130 | "eslint": { 131 | "name": "ESLint", 132 | "ids": null, 133 | "paths": [ 134 | "~/.eslintrc" 135 | ] 136 | }, 137 | "f-lux": { 138 | "name": "f.lux", 139 | "ids": [ 140 | "org.herf.Flux" 141 | ], 142 | "paths": null 143 | }, 144 | "fantastical": { 145 | "name": "Fantastical", 146 | "ids": [ 147 | "com.flexibits.fantastical" 148 | ], 149 | "paths": null 150 | }, 151 | "finder": { 152 | "name": "Finder", 153 | "ids": [ 154 | "com.apple.finder" 155 | ], 156 | "paths": null 157 | }, 158 | "fish": { 159 | "name": "fish", 160 | "ids": null, 161 | "paths": [ 162 | "~/.config/fish" 163 | ] 164 | }, 165 | "gallery-dl": { 166 | "name": "gallery-dl", 167 | "ids": null, 168 | "paths": [ 169 | "~/.config/gallery-dl" 170 | ] 171 | }, 172 | "geotoad": { 173 | "name": "GeoToad", 174 | "ids": null, 175 | "paths": [ 176 | "~/.config/GeoToad" 177 | ] 178 | }, 179 | "git": { 180 | "name": "Git", 181 | "ids": null, 182 | "paths": [ 183 | "~/.gitconfig", 184 | "~/.gitignore", 185 | "~/.config/git" 186 | ] 187 | }, 188 | "google-chrome": { 189 | "name": "Google Chrome", 190 | "ids": [ 191 | "com.google.Chrome" 192 | ], 193 | "paths": [ 194 | "~/Library/Application Support/Google/Chrome/Default/Extensions", 195 | "~/Library/Application Support/Google/Chrome/Default/Local Extension Settings", 196 | "~/Library/Application Support/Google/Chrome/Default/Preferences", 197 | "~/Library/Application Support/Google/Chrome/Default/Secure Preferences" 198 | ] 199 | }, 200 | "helix": { 201 | "name": "Helix", 202 | "ids": null, 203 | "paths": [ 204 | "~/.config/helix" 205 | ] 206 | }, 207 | "ia-writer": { 208 | "name": "iA Writer", 209 | "ids": [ 210 | "pro.writer.mac" 211 | ], 212 | "paths": [ 213 | "~/Library/Containers/pro.writer.mac/Data/Library/Application Support/iA Writer/Templates" 214 | ] 215 | }, 216 | "iterm2": { 217 | "name": "iTerm2", 218 | "ids": [ 219 | "com.googlecode.iterm2" 220 | ], 221 | "paths": null 222 | }, 223 | "keka": { 224 | "name": "Keka", 225 | "ids": [ 226 | "com.aone.keka" 227 | ], 228 | "paths": null 229 | }, 230 | "keystroke-pro": { 231 | "name": "Keystroke Pro", 232 | "ids": [ 233 | "de.ixeau.KeystrokePro2" 234 | ], 235 | "paths": null 236 | }, 237 | "klokki-slim": { 238 | "name": "Klokki Slim", 239 | "ids": [ 240 | "com.klokki-slim.macos" 241 | ], 242 | "paths": [ 243 | "~/Library/Containers/com.klokki-slim.macos/Data/Library/Application Support/com.klokki-slim.macos" 244 | ] 245 | }, 246 | "launchd-agents": { 247 | "name": "launchd Agents", 248 | "ids": null, 249 | "paths": [ 250 | "~/Library/LaunchAgents" 251 | ] 252 | }, 253 | "little-snitch": { 254 | "name": "Little Snitch", 255 | "ids": [ 256 | "at.obdev.LittleSnitchAgent", 257 | "at.obdev.LittleSnitchConfiguration", 258 | "at.obdev.LittleSnitchNetworkMonitor", 259 | "at.obdev.LittleSnitchSoftwareUpdate" 260 | ], 261 | "paths": [ 262 | "~/Library/Application Support/Little Snitch" 263 | ] 264 | }, 265 | "livestreamer": { 266 | "name": "Livestreamer", 267 | "ids": null, 268 | "paths": [ 269 | "~/.livestreamerrc", 270 | "~/.config/livestreamer" 271 | ] 272 | }, 273 | "lunar": { 274 | "name": "Lunar", 275 | "ids": [ 276 | "fyi.lunar.Lunar" 277 | ], 278 | "paths": null 279 | }, 280 | "macos-fonts": { 281 | "name": "macOS Fonts", 282 | "ids": null, 283 | "paths": [ 284 | "~/Library/Fonts" 285 | ] 286 | }, 287 | "macos-sounds": { 288 | "name": "macOS Sounds", 289 | "ids": null, 290 | "paths": [ 291 | "~/Library/Sounds" 292 | ] 293 | }, 294 | "macos-spelling": { 295 | "name": "macOS Spelling", 296 | "ids": null, 297 | "paths": [ 298 | "~/Library/Spelling/LocalDictionary" 299 | ] 300 | }, 301 | "macupdater": { 302 | "name": "MacUpdater", 303 | "ids": [ 304 | "com.corecode.MacUpdater" 305 | ], 306 | "paths": null 307 | }, 308 | "maid": { 309 | "name": "Maid", 310 | "ids": null, 311 | "paths": [ 312 | "~/.maid" 313 | ] 314 | }, 315 | "mail": { 316 | "name": "Mail", 317 | "ids": [ 318 | "com.apple.mail" 319 | ], 320 | "paths": null 321 | }, 322 | "massren": { 323 | "name": "Massren", 324 | "ids": null, 325 | "paths": [ 326 | "~/.config/massren/profile.sqlite" 327 | ] 328 | }, 329 | "messages": { 330 | "name": "Messages", 331 | "ids": [ 332 | "com.apple.iChat" 333 | ], 334 | "paths": [ 335 | "~/Library/Application Scripts/com.apple.iChat" 336 | ] 337 | }, 338 | "mpv": { 339 | "name": "mpv", 340 | "ids": null, 341 | "paths": [ 342 | "~/.config/mpv" 343 | ] 344 | }, 345 | "multitouch": { 346 | "name": "Multitouch", 347 | "ids": [ 348 | "com.brassmonkery.Multitouch" 349 | ], 350 | "paths": null 351 | }, 352 | "neovim": { 353 | "name": "Neovim", 354 | "ids": null, 355 | "paths": [ 356 | "~/.config/nvim", 357 | "~/.local/share/nvim" 358 | ] 359 | }, 360 | "openemu": { 361 | "name": "OpenEmu", 362 | "ids": [ 363 | "org.openemu.OpenEmu" 364 | ], 365 | "paths": [ 366 | "~/Library/Application Support/OpenEmu" 367 | ] 368 | }, 369 | "peek": { 370 | "name": "Peek", 371 | "ids": [ 372 | "com.bigzlabs.peek" 373 | ], 374 | "Paths": [ 375 | "~/Library/Containers/pro.writer.mac/Data/Library/Application Support/iA Writer/Templates" 376 | ] 377 | }, 378 | "phoenix": { 379 | "name": "Phoenix", 380 | "ids": [ 381 | "org.khirviko.Phoenix" 382 | ], 383 | "paths": [ 384 | "~/.phoenix.js" 385 | ] 386 | }, 387 | "processing": { 388 | "name": "Processing", 389 | "ids": [ 390 | "org.processing.app" 391 | ], 392 | "paths": [ 393 | "~/Library/Processing/preferences.txt" 394 | ] 395 | }, 396 | "pure-paste": { 397 | "name": "Pure Paste", 398 | "ids": [ 399 | "com.sindresorhus.Pure-Paste" 400 | ], 401 | "paths": null 402 | }, 403 | "rclone": { 404 | "name": "Rclone", 405 | "ids": null, 406 | "paths": [ 407 | "~/.config/rclone" 408 | ] 409 | }, 410 | "reeder": { 411 | "name": "Reeder", 412 | "ids": [ 413 | "com.reederapp.5.macOS" 414 | ], 415 | "paths": [ 416 | "~/Library/Containers/com.reederapp.5.macOS/Data/Library/Application Support/users.json" 417 | ] 418 | }, 419 | "rubocop": { 420 | "name": "Rubocop", 421 | "ids": null, 422 | "paths": [ 423 | "~/.rubocop.yml", 424 | "~/.config/rubocop/config.yml" 425 | ] 426 | }, 427 | "ruby": { 428 | "name": "Ruby", 429 | "ids": null, 430 | "paths": [ 431 | ".gem/credentials", 432 | ".gemrc", 433 | ".irbrc", 434 | ".pryrc" 435 | ] 436 | }, 437 | "safari": { 438 | "name": "Safari", 439 | "ids": [ 440 | "com.apple.Safari" 441 | ], 442 | "paths": null 443 | }, 444 | "script-editor": { 445 | "name": "Script Editor", 446 | "ids": [ 447 | "com.apple.ScriptEditor2" 448 | ], 449 | "paths": null 450 | }, 451 | "sip": { 452 | "name": "Sip", 453 | "ids": [ 454 | "com.ruiaureliano.Sip" 455 | ], 456 | "paths": null 457 | }, 458 | "spark": { 459 | "name": "Spark", 460 | "ids": [ 461 | "com.readdle.smartemail-Mac" 462 | ], 463 | "paths": null 464 | }, 465 | "ssh": { 466 | "name": "SSH", 467 | "ids": null, 468 | "paths": [ 469 | "~/.ssh" 470 | ] 471 | }, 472 | "tape": { 473 | "name": "Tape", 474 | "ids": null, 475 | "paths": [ 476 | "~/.config/tape/config.yaml" 477 | ] 478 | }, 479 | "textual": { 480 | "name": "Textual", 481 | "ids": [ 482 | "com.codeux.irc.textual5" 483 | ], 484 | "paths": [ 485 | "~/Library/Application Support/Textual IRC" 486 | ] 487 | }, 488 | "transmission": { 489 | "name": "Transmission", 490 | "ids": [ 491 | "org.m0k.transmission" 492 | ], 493 | "paths": [ 494 | "~/Library/Application Support/Transmission/blocklists" 495 | ] 496 | }, 497 | "visual-studio-code": { 498 | "name": "Visual Studio Code", 499 | "ids": [ 500 | "com.microsoft.VSCode" 501 | ], 502 | "paths": [ 503 | "~/Library/Application Support/Code/User" 504 | ] 505 | }, 506 | "vmware-fusion": { 507 | "name": "VMWare Fusion", 508 | "ids": [ 509 | "com.vmware.fusion" 510 | ], 511 | "paths": [ 512 | "~/Library/Application Support/VMware Fusion" 513 | ] 514 | }, 515 | "yacreader": { 516 | "name": "YACReader", 517 | "ids": [ 518 | "com.yourcompany.YACReader" 519 | ], 520 | "paths": [ 521 | "~/Library/Application Support/YACReader" 522 | ] 523 | }, 524 | "yt-dlp": { 525 | "name": "yt-dlp", 526 | "ids": null, 527 | "paths": [ 528 | "~/.config/yt-dlp" 529 | ] 530 | }, 531 | "zsh": { 532 | "name": "Zsh", 533 | "ids": null, 534 | "paths": [ 535 | "~/.zlogin", 536 | "~/.zlogout", 537 | "~/.zprofile", 538 | "~/.zsh_history", 539 | "~/.zshenv", 540 | "~/.zshrc" 541 | ] 542 | } 543 | } -------------------------------------------------------------------------------- /tape: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'fileutils' 5 | require 'json' 6 | require 'open3' 7 | require 'pathname' 8 | require 'tmpdir' 9 | 10 | Script_path = Pathname.new($PROGRAM_NAME).expand_path 11 | Script_name = Script_path.basename 12 | 13 | # Check for minimum ruby version 14 | abort "#{Script_name} requires Ruby 2.7 or higher" if RUBY_VERSION.to_f < 2.7 15 | 16 | # Exit cleanup 17 | Tmp_dir = Pathname.new(Dir.mktmpdir) 18 | at_exit do Tmp_dir.rmtree end 19 | 20 | # Helpers 21 | def app_running?(bundle_id) 22 | Open3.capture3( 23 | '/usr/bin/osascript', '-l', 'JavaScript', '-e', 24 | "function run(argv) { return Application(argv[0]).running() }", bundle_id 25 | ).first.strip == 'true' 26 | end 27 | 28 | def app_installed?(app_token, definitions = All_apps) 29 | definition = definitions[app_token] 30 | bundle_ids = definition['ids'] 31 | 32 | return true if bundle_ids&.any? { Open3.capture3('/usr/bin/defaults', 'read', _1).last.success? } 33 | return true if definition['paths']&.any? { Pathname.new(_1).expand_path.exist? } 34 | 35 | false 36 | end 37 | 38 | def copy_path(source, target) 39 | return unless Pathname.new(source).exist? # If path does not exist, do not try to copy it 40 | 41 | target.dirname.mkpath # Repeat directory structure of copied path 42 | FileUtils.rm_rf(target) # Delete target path before trying to copy 43 | FileUtils.cp_r(source, target) 44 | end 45 | 46 | def backup_app(app_token, definitions = All_apps) 47 | definition = definitions[app_token] 48 | backup_dir = Tmp_dir.join(definition['name']) 49 | bundle_ids = definition['ids'] 50 | 51 | puts "Backing up #{definition['name']}…" 52 | 53 | # Backup preferences 54 | backup_dir.mkpath 55 | 56 | bundle_ids&.each do 57 | system('/usr/bin/defaults', 'export', _1, backup_dir.join("#{_1}.plist").to_path) 58 | end 59 | 60 | # Backup paths 61 | definition['paths']&.each do 62 | copy_path(Pathname.new(_1).expand_path, backup_dir.join(_1)) 63 | end 64 | end 65 | 66 | def restore_app(app_token, definitions = All_apps) 67 | definition = definitions[app_token] 68 | backup_dir = Tmp_dir.join(definition['name']) 69 | bundle_ids = definition['ids'] 70 | 71 | puts "Restoring backup for #{definition['name']}…" 72 | 73 | # Restore preferences 74 | bundle_ids&.each do 75 | system('/usr/bin/defaults', 'import', _1, backup_dir.join("#{_1}.plist").to_path) 76 | end 77 | 78 | # Restore paths 79 | definition['paths']&.each do 80 | copy_path(backup_dir.join(_1), Pathname.new(_1).expand_path) 81 | end 82 | 83 | # Warn if app is running 84 | return unless bundle_ids&.any? { app_running?(_1) } 85 | 86 | puts "#{definition['name']} is running. You may need to restart it for settings to take effect." 87 | end 88 | 89 | def update_definitions(force: false) 90 | local_hash_file = Config_dir.join('latest_hash.txt') 91 | 92 | # Skip if updated in the last hour, unless forced 93 | return if !force && local_hash_file.exist? && (Time.now - local_hash_file.mtime) < 3600 94 | 95 | puts 'Updating definitions…' 96 | 97 | # Check if there have been any updates 98 | local_hash = local_hash_file.exist? ? local_hash_file.read.chomp : nil 99 | 100 | remote_hash = JSON.parse(Open3.capture2( 101 | '/usr/bin/curl', '--silent', 'https://api.github.com/repos/vitorgalvao/tape/commits/main' 102 | ).first)['sha'] 103 | 104 | local_hash_file.write(remote_hash) 105 | return if remote_hash == local_hash 106 | 107 | # Update 108 | system('/usr/bin/curl', '--silent', 'https://raw.githubusercontent.com/vitorgalvao/tape/main/definitions.json', '--output', Definitions_file.to_path) 109 | end 110 | 111 | # Usage 112 | def usage 113 | puts <<~USAGE 114 | Backup and restore software settings on macOS 115 | 116 | Usage: 117 | #{Script_name} backup Update definitions and backup settings 118 | #{Script_name} restore [def] Restore settings from previous backup 119 | Giving a definition name restores only that software 120 | #{Script_name} list Show names of supported software separated by what will be backed up 121 | #{Script_name} list tokens Show tokens of supported software separated by what will be backed up 122 | #{Script_name} launchd Load or unload an agent to perform daily backups 123 | #{Script_name} update Force update of backup definitions 124 | #{Script_name} version Show #{Script_name} version 125 | #{Script_name} help Show this help 126 | USAGE 127 | 128 | exit 129 | end 130 | 131 | usage if ARGV.empty? || ARGV.include?('help') || ARGV.include?('--help') || ARGV.include?('-h') 132 | 133 | # Load config 134 | Config_dir = Pathname.new(ENV['HOME']).join('.config', 'tape') 135 | Config_file = Config_dir.join('config.json') 136 | 137 | Default_config = { 138 | backup_to: Config_dir.join('Backups').to_path, # Directory to save backups (leading "~" is expanded) 139 | keep: 5, # Number of backups to keep 140 | exclude: ['ssh'], # By default, backup everything except what is on this list 141 | include: [] # If set, *only* these will be backed up and the "exclude" list will be ignored 142 | }.freeze 143 | 144 | unless Config_file.exist? 145 | Default_config_json = JSON.pretty_generate(Default_config) 146 | warn "No config file found. One with default settings was saved to #{Config_file}:\n#{Default_config_json}\n" 147 | 148 | Config_dir.mkpath 149 | Config_file.write(Default_config_json) 150 | end 151 | 152 | Config = JSON.load_file(Config_file) 153 | 154 | abort '"keep" value in configuration needs to be a whole number bigger than 0' unless Config['keep'].positive? 155 | 156 | # Collect software to backup or restore 157 | Backups_dir = Pathname.new(Config['backup_to']).expand_path 158 | Definitions_file = Config_dir.join('definitions.json') 159 | 160 | # Launchd for automatic backups 161 | Launchd_file = Pathname.new(ENV['HOME']).join('Library', 'LaunchAgents', 'com.vitorgalvao.tape.plist') 162 | 163 | Launchd_contents = <<~LAUNCHD_PLIST 164 | 165 | 166 | 167 | 168 | Label 169 | com.vitorgalvao.tape 170 | EnvironmentVariables 171 | 172 | PATH 173 | /opt/homebrew/opt/ruby/bin:/usr/local/opt/ruby/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin 174 | 175 | ProgramArguments 176 | 177 | #{Script_path} 178 | backup 179 | 180 | StandardOutPath 181 | /tmp/tape_backup.log 182 | StandardErrorPath 183 | /tmp/tape_backup.log 184 | StartCalendarInterval 185 | 186 | Hour 187 | 20 188 | Minute 189 | 0 190 | 191 | 192 | 193 | LAUNCHD_PLIST 194 | 195 | # Main 196 | # Update definitions 197 | update_definitions 198 | 199 | All_apps = JSON.load_file(Definitions_file) 200 | 201 | case ARGV[0] 202 | when 'backup' 203 | # Backup selected and valid apps 204 | (Config['include'].empty? ? All_apps.keys - Config['exclude'] : Config['include']) 205 | .select { app_installed?(_1) } 206 | .each do backup_app(_1) end 207 | 208 | # Archive 209 | puts 'Archiving…' 210 | Backups_dir.mkpath 211 | 212 | system( 213 | '/usr/bin/tar', '--gzip', '--create', 214 | '--file', Backups_dir.join("#{Time.now.strftime('%Y-%m-%d')}.tgz").to_path, 215 | '--directory', Tmp_dir.to_path, '.' 216 | ) 217 | 218 | # Remove older backups 219 | Backups_dir 220 | .children 221 | .select { _1.extname == '.tgz' } 222 | .sort 223 | .reverse 224 | .drop(Config['keep']) 225 | .each do _1.delete end 226 | when 'restore' 227 | abort 'Restore needs a path to a valid .tgz' if ARGV[1].nil? || Pathname.new(ARGV[1]).extname != '.tgz' 228 | 229 | # Unarchive 230 | system( 231 | '/usr/bin/tar', '--extract', 232 | '--file', ARGV[1], 233 | '--directory', Tmp_dir.to_path 234 | ) 235 | 236 | # Restore apps available in backup 237 | Apps_to_restore = ARGV[2] ? [ARGV[2]] : All_apps.keys 238 | 239 | Apps_to_restore.each do |app_token| 240 | begin 241 | name = All_apps[app_token]['name'] 242 | rescue 243 | abort "No defintion for #{app_token}" 244 | end 245 | 246 | next unless Tmp_dir.join(name).exist? # Skip if app directory does not exist in backup 247 | restore_app(app_token) 248 | end 249 | when 'list' 250 | included = (Config['include'].empty? ? All_apps.keys - Config['exclude'] : Config['include']).select { app_installed?(_1) } 251 | excluded = All_apps.keys - included 252 | 253 | if ARGV[1] == 'tokens' 254 | puts excluded.map { "\e[31m✗\e[0m #{_1}" }.join("\n") 255 | puts included.map { "\e[32m✗\e[0m #{_1}" }.join("\n") 256 | exit 257 | end 258 | 259 | puts excluded.map { All_apps[_1]['name'] }.map { "\e[31m✗\e[0m #{_1}" }.join("\n") 260 | puts included.map { All_apps[_1]['name'] }.map { "\e[32m✓\e[0m #{_1}" }.join("\n") 261 | when 'launchd' 262 | if ARGV[1] == 'on' 263 | Launchd_file.dirname.mkpath 264 | Launchd_file.write(Launchd_contents) 265 | 266 | system( 267 | '/bin/launchctl', 'bootstrap', 268 | "gui/#{Open3.capture2('/usr/bin/id', '-u', ENV['USER']).first.chomp}", 269 | Launchd_file.to_path 270 | ) 271 | 272 | exit 273 | end 274 | 275 | if ARGV[1] == 'off' 276 | system( 277 | '/bin/launchctl', 'bootout', 278 | "gui/#{Open3.capture2('/usr/bin/id', '-u', ENV['USER']).first.chomp}", 279 | Launchd_file.to_path 280 | ) 281 | 282 | Launchd_file.delete 283 | 284 | exit 285 | end 286 | 287 | # If we reach this point, wrong argument was given 288 | abort 'Launchd needs an argument of "on" or "off"' 289 | when 'update' 290 | update_definitions(force: true) 291 | when 'version' 292 | puts '2022.4' 293 | else 294 | abort <<~INVALID 295 | Invalid argument. Try: 296 | 297 | #{Script_path} help 298 | INVALID 299 | end 300 | --------------------------------------------------------------------------------