├── .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 | #
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 |
--------------------------------------------------------------------------------