<< Previous | Next >>

Daily Learnings: Wed, Oct 25, 2023

Know how to listen, and you will profit even from those who talk badly. — Plutarch

In my continuing quest to fix the agent that I wrote to sync Obsidian with my git repo, I converted the shell script to a Rust script instead today, continuing my very small, initial foray into learning Rust.

The original shell script was the following (with some things obfuscated):

#!/bin/bash

OBSIDIAN_VAULT_DIR="/Users/john/Library/Mobile Documents/iCloud~md~obsidian/Documents/Second Brain"
SYNC_COMMAND="rsync -rt --delete '$OBSIDIAN_VAULT_DIR'"
TODAY=$(date +"%a %b %d, %Y")

eval "$SYNC_COMMAND/.obsidian ."
eval "$SYNC_COMMAND/assets ."
eval "$SYNC_COMMAND/baking ."
eval "$SYNC_COMMAND/books ."
eval "$SYNC_COMMAND/church ."
eval "$SYNC_COMMAND/cooking ."
eval "$SYNC_COMMAND/dev ."
eval "$SYNC_COMMAND/family ."
eval "$SYNC_COMMAND/gospel ."
eval "$SYNC_COMMAND/journal ."
eval "$SYNC_COMMAND/misc-notes ."
eval "$SYNC_COMMAND/salesforce-study ."
eval "$SYNC_COMMAND/tags ."
eval "$SYNC_COMMAND/teaching ."
eval "$SYNC_COMMAND/templates ."

rsync -t "$OBSIDIAN_VAULT_DIR/.obsidian.vimrc" obsidian-root/.obsidian.vimrc
rsync -t "$OBSIDIAN_VAULT_DIR/dev-ideas.md" obsidian-root/dev-ideas.md
rsync -t "$OBSIDIAN_VAULT_DIR/home-base.md" obsidian-root/home-base.md

git add --all
git -c commit.gpgsign=false commit -m "Update for $TODAY"
git push

The translated rust script (thanks to the help of ChatGPT for the initial conversion) resulted in the following:

use std::process::exit;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

fn main() {
    // Define the source and destination directories
    let obsidian_vault_dir =
        "/Users/john/Library/Mobile Documents/iCloud~md~obsidian/Documents/Second Brain";
    let destination_dir = "."; // Current directory

    // Define the directories and files to sync
    let items_to_sync = &[
        ".obsidian",
        "assets",
        "baking",
        "books",
        "church",
        "cooking",
        "dev",
        "family",
        "gospel",
        "journal",
        "misc-notes",
        "salesforce-study",
        "tags",
        "teaching",
        "templates",
    ];

    // Perform directory and file sync using rsync
    for item in items_to_sync {
        if let Err(e) = sync_directory(&obsidian_vault_dir, item, &destination_dir) {
            eprintln!("Error syncing directory {}: {}", item, e);
            exit(1);
        }
    }

    // Special handling for specific files found in the root of the Obsidian vault
    let files_to_sync = &[
        (".obsidian.vimrc", "obsidian-root/.obsidian.vimrc"),
        ("dev-ideas.md", "obsidian-root/dev-ideas.md"),
        ("home-base.md", "obsidian-root/home-base.md"),
    ];

    for (source, destination) in files_to_sync {
        if let Err(e) = sync_file(&obsidian_vault_dir, source, &destination) {
            eprintln!("Error syncing file {}: {}", source, e);
            exit(1);
        }
    }

    // Commit and push to Git
    if let Err(e) = git_commit_push() {
        eprintln!("Error committing and pushing to Git: {}", e);
        exit(1);
    }
}

fn sync_directory(
    source_dir: &str,
    dir_name: &str,
    destination_dir: &str,
) -> Result<(), std::io::Error> {
    let source_path = format!("{}/{}", source_dir, dir_name);
    let destination_path = format!("{}", destination_dir);

    let status = Command::new("rsync")
        .args(&["-rt", "--delete", &source_path, &destination_path])
        .status()?;

    if !status.success() {
        Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            "Rsync failed",
        ))
    } else {
        Ok(())
    }
}

fn sync_file(
    source_dir: &str,
    source_file: &str,
    destination_file: &str,
) -> Result<(), std::io::Error> {
    let source_path = format!("{}/{}", source_dir, source_file);
    let destination_path = format!("./{}", destination_file);

    let status = Command::new("rsync")
        .args(&["-t", &source_path, &destination_path])
        .status()?;

    if !status.success() {
        Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            "Rsync failed",
        ))
    } else {
        Ok(())
    }
}

fn git_commit_push() -> Result<(), std::io::Error> {
    let git_status = Command::new("git").args(&["add", "--all"]).status()?;

    if git_status.success() {
        let now = SystemTime::now();
        let unix_time = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
        let today = format!("{}", unix_time.as_secs());

        let commit_message = format!("Update for {}", today);

        // NOTE: Skip signing commits for now in order to avoid user interaction
        let git_commit_status = Command::new("git")
            .args(&[
                "-c",
                "commit.gpgsign=false",
                "commit",
                "-m",
                &commit_message,
            ])
            .status()?;

        if git_commit_status.success() {
            let git_push_status = Command::new("git").args(&["push"]).status()?;

            if git_push_status.success() {
                Ok(())
            } else {
                Err(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "Git push failed",
                ))
            }
        } else {
            Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                "Git commit failed",
            ))
        }
    } else {
        Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            "Git add failed",
        ))
    }
}

Note: I know that this rust script could definitely be better, and I hope to continue to iterate on it as I learn more about more “rusty” ways to approach problems.

After loading this into the launchd via launchctl I think that I’ve finally fixed my issues with permissions, given that this is an executable and can be granted explicit access to files in my iCloud directory. 🤞🏻

Edit from the Future: This totally did fix my issue with permissions. Upon running this application, macOS asked if I wanted to grant it permissions to my iCloud Files. Once I agreed, the program worked as expected. Turns out there were some permission changes with macOS Sonoma that caused the shell script to not have access to the directories that I needed, especially when run via [[launchd-macos|launchd]].

References