मैंने मशीन माइग्रेशन से बचने पर एक आर्टिकल पब्लिश किया। एक हफ़्ते बाद, मुझे पता चला कि मेरा अपना सेटअप पहले ही फेल हो चुका था, और इसे ठीक करने में git, सिमलिंक, launchd, और एक शर्मिंदा कर देने वाली दोपहर लगी।
एक हफ़्ता पहले, मैंने वर्कस्पेस आर्किटेक्चर पर एक आर्टिकल पब्लिश किया था।
थीसिस भरोसे से भरी थी:
अगर आज मेरी मशीन मर जाए, तो मुझे एक ही डायरेक्टरी से सब कुछ दोबारा बना पाना चाहिए।
~/Developer/ के तहत सब कुछ। रिपॉज़िटरीज़। इंफ्रास्ट्रक्चर। डेटा। एक डायरेक्टरी, पूरी तरह पोर्टेबल।
मैं इसे लेकर काफ़ी अच्छा महसूस कर रहा था।
फिर, कुछ दिन बाद, मैंने अपने AI असिस्टेंट से कहा कि मेरे Claude Code स्किल्स फ़ोल्डर को नए स्ट्रक्चर में बैकअप कर दे।
जवाब में कुछ ऐसा आया जिसकी मुझे उम्मीद नहीं थी:
ls: /Users/paurushrai/.claude/skills/: No such file or directoryवह फ़ोल्डर था ही नहीं।
लेकिन मेरे पास स्किल्स थीं। एक कस्टम स्किल जिसका नाम graphify था, मैंने इसे बनाया था, रोज़ इस्तेमाल करता था, और मेरा ग्लोबल कॉन्फ़िग अब भी इसे रेफर करता था। तो यह गई कहां?
/Users/paurushrai/Downloads/mac-restore/dot-claude/skills/graphify/वहीं थी। मेरे Downloads फ़ोल्डर में। mac-restore नाम की एक डायरेक्टरी के अंदर।
जिस दिन से मैंने मशीन बदली थी, वहीं पड़ी थी। कभी रीस्टोर नहीं हुई। भुला दी गई।

मैंने ठीक इसी फेलियर मोड पर एक पूरा आर्टिकल लिखा था, और मेरी अपनी मशीन चुपचाप इसे पूरे वक़्त जी रही थी।
🔍 वह ब्लाइंड स्पॉट
यहां वह है जो मुझसे छूट गया था।
मेरे आर्किटेक्चर ने सब कुछ ~/Developer/ के अंदर व्यवस्थित किया। लेकिन वह कॉन्फ़िगरेशन जो किसी मशीन को मेरी महसूस कराता है, वहां नहीं रहता:
- Git आइडेंटिटी और बर्ताव,
~/.gitconfig - शेल सेटअप,
~/.zshrc,~/.zprofile - ग्लोबल इग्नोर्स,
~/.gitignore_global - AI कोडिंग नियम,
~/.claude/CLAUDE.md - कस्टम AI स्किल्स,
~/.claude/skills/ - और सबसे अदृश्य वाली: इंस्टॉल किए गए पैकेजों की लिस्ट, जो कहीं नहीं रहती
यह सब $HOME में बिखरा हुआ। बिना वर्ज़न के। बिना बैकअप के। मेरे खूबसूरत डायरेक्टरी स्ट्रक्चर से अदृश्य।
जब मैंने नुकसान का जायज़ा लेने के लिए ~/.gitconfig चेक किया, तो हालत जितना सोचा था उससे भी बदतर थी:
cat ~/.gitconfig
# → no such fileफ़ाइल थी ही नहीं। पुरानी मशीन पर मैंने ईमेल रूटिंग सेट की थी, अपने प्रोजेक्ट्स के लिए पर्सनल ईमेल, कंपनी रिपॉज़ के लिए वर्क ईमेल। उसमें से कुछ भी नहीं बचा था। मैं महीनों से बिना नोटिस किए पर्सनल ईमेल से कमिट कर रहा था।
कोड माइग्रेट हो गया था। कॉन्फ़िगरेशन नहीं हुआ था।
🔗 Unix की सबसे पुरानी ट्रिक
मेरी पहली सोच वही जो हर किसी की होती: एक बैकअप फ़ोल्डर बनाओ, dotfiles उसमें कॉपी करो, GitHub पर पुश कर दो।
लेकिन कॉपियां भटक जाती हैं। जिस पल .gitconfig के दो वर्ज़न बन जाते हैं, उनमें से एक झूठा होता है, और आपको पता तब चलेगा जब कुछ टूट जाएगा।
फिक्स मुझसे भी पुराना है: सिमलिंक।
फ़ाइल सिर्फ एक है। वह रिपॉज़िटरी में रहती है। होम डायरेक्टरी को एक पॉइंटर मिलता है:
~/.gitconfig ──symlink──▶ ~/Developer/infra/dotfiles/.gitconfig
└── अकेली असली फ़ाइल
जब मैंने पूछा कि क्या इसका मतलब है कि मुझे बदलाव वापस सिंक करते रहना होगा, तो जो जवाब मिला वही इस पूरे डिज़ाइन को काम करने लायक बनाता है:
राइट्स सिमलिंक से होकर जाते हैं।
कोई सेटिंग बदलने के लिए git config --global चलाओ, git पॉइंटर को फ़ॉलो करता है और सीधे रिपॉज़ फ़ाइल में लिखता है। ~/.zshrc एडिट करो, आप रिपॉज़ फ़ाइल ही एडिट कर रहे हो। सिंक करने के लिए कुछ है ही नहीं, क्योंकि सिंक करने के लिए कुछ है ही नहीं। एक फ़ाइल, दो पते।
तो मैंने यही बनाया। अपनी इंफ्रास्ट्रक्चर लेयर के अंदर एक dotfiles रिपॉज़, जिसमें असली फ़ाइलें रहती हैं, साथ में एक इडेम्पोटेंट install.sh जो नई मशीन पर हर सिमलिंक दोबारा बना देती है, पहले जो भी मिले उसका बैकअप लेकर, दो बार चलाने पर भी सुरक्षित।
Git कॉन्फ़िग, शेल कॉन्फ़िग, ग्लोबल इग्नोर्स, मेरे AI नियम, बचाई गई graphify स्किल। कमिट। पुश। परमानेंट।
एक नियम, जो यह देखकर सीखा कि लगभग क्या रिपॉज़ में चला गया था: कभी सीक्रेट्स कमिट मत करो। मेरी .zshrc अब एक ~/.zshrc.local को सोर्स करती है जिसे git कभी नहीं देखता। रिपॉज़ को स्ट्रक्चर मिलता है; मशीन अपने सीक्रेट्स रखती है।

मुझे लगा मैं पूरा कर चुका हूं।
🍺 Homebrew कोई डायरी नहीं रखता
फिर मैंने एक सीधे सवाल का जवाब देने की कोशिश की: अगर यह मशीन मर जाए, तो मुझे क्या-क्या दोबारा इंस्टॉल करना होगा?
Git कॉन्फ़िग की एक खूबसूरत ख़ासियत है, टूल अपने खुद के कॉन्फ़िग फ़ाइल में लिखता है, तो रिपॉज़ को हमेशा सच पता होता है। Homebrew के ऐसे कोई तौर-तरीके नहीं हैं। brew install jq पैकेज इंस्टॉल करता है और फ़ैसले को कहीं भी रिकॉर्ड नहीं करता। पैकेज लिस्ट सिर्फ अप्रत्यक्ष रूप से मौजूद होती है, /opt/homebrew/Cellar में फ़ोल्डरों के रूप में।
Homebrew मांगने पर एक मैनिफ़ेस्ट डंप कर सकता है:
brew bundle dump --global --force
# → writes .Brewfile: brew "gh", brew "git", brew "tree"...तो मैंने बाकी सब की तरह एक .Brewfile को रिपॉज़ में सिमलिंक कर दिया। लेकिन एक पेच था, और वह खटकता रहा:
Brewfile सिर्फ तब अपडेट होती है जब आपको इसे डंप करना याद रहे।
मेरा पूरा डिज़ाइन इस बात पर बना था कि मुझे याद नहीं रहता। यही पूरी वजह है कि सिमलिंक बने। एक मैनिफ़ेस्ट जो मेरे अनुशासन पर निर्भर है, वह मैनिफ़ेस्ट है जो ज़रूरत पड़ने तक तीन महीने पुराना हो चुका होगा।
🧪 मेरी पहली कोशिश गलत थी
पहला समाधान चतुर लगा: brew कमांड को एक शेल फ़ंक्शन में लपेट दो। हर इंस्टॉल और अनइंस्टॉल को इंटरसेप्ट करो, मैनिफ़ेस्ट दोबारा डंप करो, ऑटो-कमिट, ऑटो-पुश।
मैंने इसे बनाया। पहली ही कोशिश में काम कर गया:
$ brew install jq
...
$ git log --oneline -1
df053cb chore(brew): install jqखूबसूरत। कमिट मैसेज में पैकेज का नाम तक है।
और फिर मैंने इसे दोबारा देखा और खामी नज़र आ गई।
एक शेल रैपर सिर्फ उन कमांड्स को पकड़ता है जो मेरे इंटरैक्टिव शेल में टाइप की जाती हैं। कोई सेटअप स्क्रिप्ट डिपेंडेंसीज़ इंस्टॉल कर रही है? अदृश्य। कोई AI एजेंट किसी सेशन में brew चला रहा है? अदृश्य। कोई भी कॉन्टेक्स्ट जो मेरा zsh मेरे कॉन्फ़िग के साथ लोड नहीं है? अदृश्य।
मैंने इंटरफ़ेस ऑटोमेट किया था जब मुझे इवेंट ऑटोमेट करने की ज़रूरत थी।
पैकेज इंस्टॉल खुद वह इवेंट है, और यह फ़ाइलसिस्टम में होता है, मेरे शेल में नहीं।
⚙️ OS के पास पहले से जवाब था
मैंने पूछा: क्या हम इसके लिए launchd इस्तेमाल कर सकते हैं?
launchd macOS का सर्विस मैनेजर है, वह चीज़ जो सिस्टम पर हर डेमन को स्टार्ट करती है, Apple का systemd का जवाब। हर Mac डेवलपर ने इसे देखा है। लगभग कोई भी इसका सबसे अच्छा फ़ीचर इस्तेमाल नहीं करता:
WatchPaths, launchd OS लेवल पर एक पाथ पर नज़र रखेगा और बदलते ही आपकी जॉब चला देगा।
<key>WatchPaths</key>
<array>
<string>/opt/homebrew/Cellar</string>
<string>/opt/homebrew/Caskroom</string>
</array>अब इससे फ़र्क़ नहीं पड़ता कि पैकेज कौन इंस्टॉल कर रहा है, मेरा शेल, कोई स्क्रिप्ट, कोई एजेंट, कुछ भी। Cellar बदलता है, OS नोटिस करता है, मेरी सिंक स्क्रिप्ट चलती है:
# sync.sh, the whole engine
brew bundle dump --global --force # snapshot packages
git add -A # stage any dotfile drift
git diff --cached --quiet && exit 0 # nothing changed? exit silently
git commit -q -m "chore: auto-sync ${CHANGED}"
git push -q # offline? commits locally, pushes next timeसाथ ही एक कैच-ऑल के तौर पर हर दिन दोपहर 12 बजे का रन, और एक 60-सेकंड का थ्रॉटल ताकि पांच पैकेज इंस्टॉल करने पर पांच कमिट की बजाय एक कमिट बने।
फिर टेस्ट। एक पैकेज इंस्टॉल करो, बाकी कुछ मत छुओ, देखो:
$ brew install jq
... 8 seconds later, zero shell involvement:
67ba27e chore: auto-sync .Brewfile
→ pushed to GitHub ✅आठ सेकंड। मशीन ने अपने बदलाव को खुद दस्तावेज़ किया, कमिट किया, और पुश किया जबकि मैं वहीं बैठा रहा।

मैंने उसी मिनट शेल रैपर डिलीट कर दिया। न कोई थर्ड-पार्टी टूल, न किसी टाइमर पर cron पोलिंग, न कोई इंस्टॉल करने वाला डेमन। OS के पास पूरे वक़्त जवाब मौजूद था।
पैकेजों के लिए काम करने के बाद, अगला कदम साफ़ था: dotfiles पर ही नज़र रखो। अब .zshrc या मेरे AI नियम एडिट करने पर एक मिनट के अंदर ऑटो-कमिट हो जाता है, कमिट मैसेज में फ़ाइल का नाम रहता है।
🔁 वह जाल जो मैं लगभग बना ही बैठा
एक फ़ैसला यहां किसी को भी काटेगा जो यह बनाएगा, तो कबूलनामा लिख देना बेहतर है।
आलसी वर्ज़न यह है कि रिपॉज़ की रूट डायरेक्टरी को वॉच किया जाए, एक ही वॉच एंट्री, सब कुछ कवर करती है।
लेकिन हर git कमिट .git/ को म्यूटेट करता है। रिपॉज़ रूट वॉच करो और आपने एक औरोबोरोस बना लिया: बदलाव एजेंट चलाता है → एजेंट कमिट करता है → कमिट .git/ बदलता है → जो एजेंट को फिर चलाता है → हमेशा के लिए।
तो एजेंट अलग-अलग फ़ाइलों को वॉच करता है, कभी उस डायरेक्टरी को नहीं जिसमें .git है। ट्रेड-ऑफ़: किसी गहरी सबडायरेक्टरी में एक बिल्कुल नई फ़ाइल तुरंत नहीं पकड़ी जाती, इसकी बजाय डेली रन उसे बटोर लेता है। यह README में दर्ज है, और यह एक वाजिब कीमत है।
ऐसा ऑटोमेशन जो खुद को ट्रिगर कर दे, वही बग है जो आपको रात 2 बजे मिलता है।
🔔 इसे बोलना सिखाना
एक समस्या बाकी थी, और वह भरोसे की थी।
एजेंट अब मेरी तरफ़ से, चुपचाप कमिट कर रहा था। एक ख़ामोश डेमन वह डेमन है जिस पर आप भरोसा करना बंद कर देते हैं, या इससे भी बुरा, भूल जाते हैं कि वह मौजूद है।
स्क्रिप्ट को macOS-नेटिव नोटिफ़िकेशन की दो लाइनें मिलीं:
osascript -e 'display notification "Committed & pushed: .Brewfile" with title "dotfiles sync"'पहला टेस्ट: मैंने इसे देखा ही नहीं। बैनर पांच सेकंड में गायब हो जाते हैं, और मैं कहीं और देख रहा था।
फिक्स कोड नहीं था, एक सेटिंग थी। macOS नोटिफ़िकेशन के दो स्टाइल होते हैं, और लगभग कोई भी डिफ़ॉल्ट नहीं बदलता: System Settings → Notifications → Script Editor → Alerts। अलर्ट-स्टाइल नोटिफ़िकेशन तब तक स्क्रीन पर रहते हैं जब तक आप खारिज न करें।
अब हर सिंक कोने में मेरे मंज़ूर करने का इंतज़ार करता है, मैसेज में बदली हुई फ़ाइलों के नाम के साथ। मेरी मशीन की कॉन्फ़िग हिस्ट्री, रसीदों की तरह पहुंचाई गई।
और यह सिर्फ़ तब बोलता है जब वाकई कुछ बदला हो, एक नो-ऑप सिंक ख़ामोश रहता है। हर मिनट मिलने वाला नोटिफ़िकेशन वह नोटिफ़िकेशन है जिसे नज़रअंदाज़ करना आप पहले ही सीख चुके हैं।
🎭 पैरानॉयड सवाल
जब सब कुछ काम कर रहा था, तभी मुझे वह सवाल सूझा जो ऐसे ज़्यादातर सेटअप को तोड़ देता है:
जब मैं अपने वर्क अकाउंट से GitHub में लॉगिन करता हूं, तब क्या होता है?
मैं खोजने निकला, और पाया कि "GitHub में लॉग इन होना" असल में तीन अलग-अलग लेयर हैं जिन्हें लगभग किसी ने कभी अलग करके नहीं देखा:
लेयर 1, कमिट किसने लिखा? पूरी तरह git कॉन्फ़िग से तय होता है। एक कमिट लोकल फ़ाइल राइट है; GitHub से संपर्क तक नहीं होता। मेरा includeIf नियम कंपनी रिपॉज़ को मेरे वर्क ईमेल पर भेजता है, बाकी सब पर्सनल पर। CLI लॉगिन इसे छू भी नहीं सकते।
लेयर 2, पुश करने की इजाज़त किसे है? macOS keychain में मौजूद क्रेडेंशियल से तय होता है। और यहीं नाज़ुक हिस्सा है: keychain क्रेडेंशियल प्रति-होस्ट स्टोर करता है। दो अकाउंट, एक github.com, दूसरा लॉगिन चुपचाप पहले को ओवरराइट कर सकता है, और अचानक मेरा सिंक एजेंट गलत आइडेंटिटी से पुश कर रहा है और 403 इकट्ठा कर रहा है।
लेयर 3, gh CLI किसकी तरफ़ से काम करता है? जो भी अकाउंट एक्टिव है। मेरी सिंक के लिए बेमतलब, जो सिर्फ़ शुद्ध git बोलती है।

लेयर 2 असली ख़तरा था, और फिक्स एक लाइन है:
git remote set-url origin https://paurushrai@github.com/paurushrai/dotfiles.gitवह paurushrai@ कोई ईमेल नहीं है, यह URL सिंटैक्स है, वही user@host जो ssh में होता है। URL में यूज़रनेम होने पर, keychain क्रेडेंशियल को होस्ट + यूज़रनेम के तहत फ़ाइल करता है। अलग अकाउंट, अलग दराज़ें, ओवरराइट की कोई गुंजाइश नहीं।
यह वही सिद्धांत है जो ईमेल रूटिंग के पीछे है, और इसे इस तरह कहना बेहतर है:
आइडेंटिटी रिपॉज़ की एक प्रॉपर्टी होनी चाहिए, किसी ग्लोबल म्यूटेबल स्टेट की नहीं।
🚀 दस-मिनट वाली मशीन
यहां कहानी का अंत है।
अगली बार जब मैं कोई नया Mac खोलूंगा, यह पूरी प्रक्रिया होगी:
# 1. Homebrew, then the two bootstrap tools
brew install gh git
gh auth login# 2. Clone the config, link everything, reinstall every package
gh repo clone paurushrai/dotfiles ~/Developer/infra/dotfiles
~/Developer/infra/dotfiles/install.sh
brew bundle --global# 3. Re-authenticate, credentials never migrate, by design
claude loginऔर जो हिस्सा मुझे वाकई संतोष देता है: install.sh सिर्फ़ कॉन्फ़िग रीस्टोर नहीं करता। यह launchd एजेंट को भी बूटस्ट्रैप करता है।
नई मशीन को मेरे सेटअप का एक स्नैपशॉट नहीं मिलता। इसे वह सिस्टम मिलता है जो मेरा सेटअप बनाए रखता है, और पहले मिनट से खुद को बनाए रखना शुरू कर देता है।
जो मैंने जानबूझकर छोड़ा, वह भी उतना ही मायने रखता है:
- क्रेडेंशियल्स, किसी सीक्रेट के लिए बैकअप है दोबारा लॉगिन करना। दस सेकंड, जानबूझकर।
- कैश, सेशन स्टेट, बिल्ड आर्टिफ़ैक्ट्स, अपने आप दोबारा बन जाते हैं। इन्हें बैकअप करना जमाखोरी है।
- डेटा लेयर, Postgres और Redis वॉल्यूम बड़े, लाइव, बदलते हुए बाइनरी हैं। Git ग़लत टूल है, और चलती हुई डेटाबेस को कॉपी करना करप्ट कचरा पैदा करता है। डेटा को असली स्नैपशॉट बैकअप चाहिए, वह अगला आर्टिकल है।
🎯 मैंने असल में क्या सीखा
पिछले आर्टिकल का सबक था: आपका वर्कस्टेशन एक सिस्टम है, इसे आर्किटेक्ट करो।
इस हफ़्ते ने मुझे उसका corollary सिखाया: अनुशासन से चलने वाला सिस्टम बिगड़ जाता है। खुद को बनाए रखने वाला सिस्टम बढ़ता है।
इस कहानी के हर फिक्स की शक्ल देखिए:
- मैं कॉन्फ़िग फ़ाइलें कॉपी करना भूल जाता → तो अब सिर्फ़ एक फ़ाइल है, सिमलिंक की हुई, कोई कॉपी नहीं
- मैं पैकेज मैनिफ़ेस्ट अपडेट करना भूल जाता → तो अब OS फ़ाइलसिस्टम पर नज़र रखता है
- मैं कमिट करना भूल जाता → तो अब एजेंट कमिट करता है
- मुझसे ख़ामोश फेलियर छूट जाते → तो अब नोटिफ़िकेशन मेरे खारिज करने तक रुकते हैं
- मैं अगली बार यह सब दोबारा सेट करना भूल जाता → तो अब इंस्टॉलर खुद ऑटोमेशन को दोबारा इंस्टॉल करता है
इनमें से हर एक ने "याद रखना" को "यह अपने आप हो जाता है" से बदल दिया।
वह स्किल फ़ाइल महीनों तक मेरे Downloads फ़ोल्डर में मरी पड़ी रही क्योंकि उसे रीस्टोर करना इस बात पर निर्भर था कि मुझे याद रहे कि वह मौजूद है। अब मेरी मशीन पर कुछ भी मेरी याददाश्त पर निर्भर नहीं है।
और ईमानदारी से, यह सबसे अच्छा फ़ीचर है जो मैंने कभी खुद को शिप किया है।
आपकी कॉन्फ़िग लेयर कैसी दिखती है?
Dotfiles रिपॉज़ कोई नया आइडिया नहीं हैं। लेकिन ज़्यादातर "रिपॉज़ में फ़ाइलें और एक इंस्टॉल स्क्रिप्ट" पर रुक जाते हैं, वह हिस्सा जो पहले दिन के बाद रिपॉज़ को सच बनाए रखता है, वहीं दिलचस्पी शुरू होती है।
टूलिंग बदलती रहेगी। आर्किटेक्चर को इससे बचना चाहिए।


