Skip to content

Instantly share code, notes, and snippets.

@reagle
Last active April 12, 2026 14:33
Show Gist options
  • Select an option

  • Save reagle/e6da63b553662bd1554dabc5ed6661b8 to your computer and use it in GitHub Desktop.

Select an option

Save reagle/e6da63b553662bd1554dabc5ed6661b8 to your computer and use it in GitHub Desktop.
Create symbolic links from source Obsidian vault config to target vaults.
#!/usr/bin/env -S uv run
# /// script
# dependencies = []
# ///
"""Sync shared Obsidian configuration from main vault to other vaults.
Copies appearance, plugins, and settings while preserving vault-specific
workspace and cache files.
"""
import argparse
from pathlib import Path
from shutil import copy2, copytree, rmtree
def sync_config(main_vault: Path, other_vault: Path, dry_run: bool = False) -> None:
"""Sync shared config files from main vault to another vault.
Copies files/directories that should be shared across vaults while
excluding vault-specific files like workspaces and caches.
Args:
main_vault: Root of main vault
other_vault: Root of other vault
dry_run: If True, only show what would be done
"""
# Files to copy - str for vault root, tuple for (subfolder, filename)
shared_files = [
(".obsidian", "app.json"),
(".obsidian", "appearance.json"),
(".obsidian", "community-plugins.json"),
(".obsidian", "core-plugins.json"),
(".obsidian", "hotkeys.json"),
(".obsidian", "types.json"),
".export-ignore",
]
# Directories to copy (subfolder, dirname)
shared_dirs = [
(".obsidian", "plugins"),
(".obsidian", "snippets"),
(".obsidian", "themes"),
]
# Copy individual files
for file_spec in shared_files:
if isinstance(file_spec, tuple):
subfolder, filename = file_spec
src = main_vault / subfolder / filename
dst = other_vault / subfolder / filename
display_name = f"{subfolder}/{filename}"
else:
src = main_vault / file_spec
dst = other_vault / file_spec
display_name = file_spec
if src.exists():
if dry_run:
print(f" would copy {display_name}")
else:
dst.parent.mkdir(parents=True, exist_ok=True)
copy2(src, dst)
# print(f" copied {display_name}")
# Copy directories
for subfolder, dirname in shared_dirs:
src = main_vault / subfolder / dirname
dst = other_vault / subfolder / dirname
display_name = f"{subfolder}/{dirname}/"
if src.exists():
if dst.exists():
if dry_run:
print(f" would replace {display_name}")
else:
rmtree(dst)
copytree(src, dst)
# print(f" replaced {display_name}")
elif dry_run:
print(f" would copy {display_name}")
else:
dst.parent.mkdir(parents=True, exist_ok=True)
copytree(src, dst)
# print(f" copied {display_name}")
def process_args(argv: list[str] | None = None) -> argparse.Namespace:
"""Process command line arguments."""
parser = argparse.ArgumentParser(
description="Sync Obsidian config from main vault to others"
)
parser.add_argument(
"--main",
type=Path,
default=Path("~/data/2web/reagle.org/joseph/plan/ob-plan"),
help="Main vault directory",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be done without making changes",
)
return parser.parse_args(argv)
def sync_templates(main_vault: Path, other_vault: Path, dry_run: bool = False) -> None:
"""Sync template files between vaults."""
templates_dir = "_templates"
src = main_vault / templates_dir
dst = other_vault / templates_dir
if src.exists():
if dst.exists():
if dry_run:
print(f" would sync {templates_dir}/")
else:
rmtree(dst)
copytree(src, dst)
elif dry_run:
print(f" would copy {templates_dir}/")
else:
copytree(src, dst)
def main() -> None:
"""Sync Obsidian configuration across vaults."""
args = process_args()
main_vault = args.main.expanduser()
others = [
Path(t).expanduser()
for t in [
"~/data/2web/reagle.org/joseph/ob-codex",
"~/data/2web/reagle.org/joseph/bl-codex",
"~/data/2web/goatee.net/bl-goatee",
"~/data/2misc/ob-misc",
"~/data/mom/ob-mom",
]
]
if not main_vault.exists():
print(f"Error: Main vault not found: {main_vault}")
return
print(f"Main vault: {main_vault}")
if args.dry_run:
print("DRY RUN - no changes will be made")
for other in others:
print(f"Syncing to {other.name}")
sync_config(main_vault, other, args.dry_run)
sync_templates(main_vault, other, args.dry_run)
if __name__ == "__main__":
main()
@DearVikki

Copy link
Copy Markdown

Thanks ☕️, super handy.

@mikeschlottig

Copy link
Copy Markdown

dope

@reagle

reagle commented Mar 28, 2026

Copy link
Copy Markdown
Author

@mikeschlottig I just posted the current version of that script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment