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]].