README.html (11897B)
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> 3 <head> 4 <meta charset="utf-8" /> 5 <meta name="generator" content="pandoc" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> 7 <title>README</title> 8 <style> 9 html { 10 color: #1a1a1a; 11 background-color: #fdfdfd; 12 } 13 body { 14 margin: 0 auto; 15 max-width: 36em; 16 padding-left: 50px; 17 padding-right: 50px; 18 padding-top: 50px; 19 padding-bottom: 50px; 20 hyphens: auto; 21 overflow-wrap: break-word; 22 text-rendering: optimizeLegibility; 23 font-kerning: normal; 24 } 25 @media (max-width: 600px) { 26 body { 27 font-size: 0.9em; 28 padding: 12px; 29 } 30 h1 { 31 font-size: 1.8em; 32 } 33 } 34 @media print { 35 html { 36 background-color: white; 37 } 38 body { 39 background-color: transparent; 40 color: black; 41 font-size: 12pt; 42 } 43 p, h2, h3 { 44 orphans: 3; 45 widows: 3; 46 } 47 h2, h3, h4 { 48 page-break-after: avoid; 49 } 50 } 51 p { 52 margin: 1em 0; 53 } 54 a { 55 color: #1a1a1a; 56 } 57 a:visited { 58 color: #1a1a1a; 59 } 60 img { 61 max-width: 100%; 62 } 63 svg { 64 height: auto; 65 max-width: 100%; 66 } 67 h1, h2, h3, h4, h5, h6 { 68 margin-top: 1.4em; 69 } 70 h5, h6 { 71 font-size: 1em; 72 font-style: italic; 73 } 74 h6 { 75 font-weight: normal; 76 } 77 ol, ul { 78 padding-left: 1.7em; 79 margin-top: 1em; 80 } 81 li > ol, li > ul { 82 margin-top: 0; 83 } 84 blockquote { 85 margin: 1em 0 1em 1.7em; 86 padding-left: 1em; 87 border-left: 2px solid #e6e6e6; 88 color: #606060; 89 } 90 code { 91 font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace; 92 font-size: 85%; 93 margin: 0; 94 hyphens: manual; 95 } 96 pre { 97 margin: 1em 0; 98 overflow: auto; 99 } 100 pre code { 101 padding: 0; 102 overflow: visible; 103 overflow-wrap: normal; 104 } 105 .sourceCode { 106 background-color: transparent; 107 overflow: visible; 108 } 109 hr { 110 background-color: #1a1a1a; 111 border: none; 112 height: 1px; 113 margin: 1em 0; 114 } 115 table { 116 margin: 1em 0; 117 border-collapse: collapse; 118 width: 100%; 119 overflow-x: auto; 120 display: block; 121 font-variant-numeric: lining-nums tabular-nums; 122 } 123 table caption { 124 margin-bottom: 0.75em; 125 } 126 tbody { 127 margin-top: 0.5em; 128 border-top: 1px solid #1a1a1a; 129 border-bottom: 1px solid #1a1a1a; 130 } 131 th { 132 border-top: 1px solid #1a1a1a; 133 padding: 0.25em 0.5em 0.25em 0.5em; 134 } 135 td { 136 padding: 0.125em 0.5em 0.25em 0.5em; 137 } 138 header { 139 margin-bottom: 4em; 140 text-align: center; 141 } 142 #TOC li { 143 list-style: none; 144 } 145 #TOC ul { 146 padding-left: 1.3em; 147 } 148 #TOC > ul { 149 padding-left: 0; 150 } 151 #TOC a:not(:hover) { 152 text-decoration: none; 153 } 154 code{white-space: pre-wrap;} 155 span.smallcaps{font-variant: small-caps;} 156 div.columns{display: flex; gap: min(4vw, 1.5em);} 157 div.column{flex: auto; overflow-x: auto;} 158 div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} 159 /* The extra [class] is a hack that increases specificity enough to 160 override a similar rule in reveal.js */ 161 ul.task-list[class]{list-style: none;} 162 ul.task-list li input[type="checkbox"] { 163 font-size: inherit; 164 width: 0.8em; 165 margin: 0 0.8em 0.2em -1.6em; 166 vertical-align: middle; 167 } 168 .display.math{display: block; text-align: center; margin: 0.5rem auto;} 169 /* CSS for syntax highlighting */ 170 html { -webkit-text-size-adjust: 100%; } 171 pre > code.sourceCode { white-space: pre; position: relative; } 172 pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } 173 pre > code.sourceCode > span:empty { height: 1.2em; } 174 .sourceCode { overflow: visible; } 175 code.sourceCode > span { color: inherit; text-decoration: inherit; } 176 div.sourceCode { margin: 1em 0; } 177 pre.sourceCode { margin: 0; } 178 @media screen { 179 div.sourceCode { overflow: auto; } 180 } 181 @media print { 182 pre > code.sourceCode { white-space: pre-wrap; } 183 pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; } 184 } 185 pre.numberSource code 186 { counter-reset: source-line 0; } 187 pre.numberSource code > span 188 { position: relative; left: -4em; counter-increment: source-line; } 189 pre.numberSource code > span > a:first-child::before 190 { content: counter(source-line); 191 position: relative; left: -1em; text-align: right; vertical-align: baseline; 192 border: none; display: inline-block; 193 -webkit-touch-callout: none; -webkit-user-select: none; 194 -khtml-user-select: none; -moz-user-select: none; 195 -ms-user-select: none; user-select: none; 196 padding: 0 4px; width: 4em; 197 color: #aaaaaa; 198 } 199 pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; } 200 div.sourceCode 201 { } 202 @media screen { 203 pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; } 204 } 205 code span.al { color: #ff0000; font-weight: bold; } /* Alert */ 206 code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */ 207 code span.at { color: #7d9029; } /* Attribute */ 208 code span.bn { color: #40a070; } /* BaseN */ 209 code span.bu { color: #008000; } /* BuiltIn */ 210 code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */ 211 code span.ch { color: #4070a0; } /* Char */ 212 code span.cn { color: #880000; } /* Constant */ 213 code span.co { color: #60a0b0; font-style: italic; } /* Comment */ 214 code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */ 215 code span.do { color: #ba2121; font-style: italic; } /* Documentation */ 216 code span.dt { color: #902000; } /* DataType */ 217 code span.dv { color: #40a070; } /* DecVal */ 218 code span.er { color: #ff0000; font-weight: bold; } /* Error */ 219 code span.ex { } /* Extension */ 220 code span.fl { color: #40a070; } /* Float */ 221 code span.fu { color: #06287e; } /* Function */ 222 code span.im { color: #008000; font-weight: bold; } /* Import */ 223 code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */ 224 code span.kw { color: #007020; font-weight: bold; } /* Keyword */ 225 code span.op { color: #666666; } /* Operator */ 226 code span.ot { color: #007020; } /* Other */ 227 code span.pp { color: #bc7a00; } /* Preprocessor */ 228 code span.sc { color: #4070a0; } /* SpecialChar */ 229 code span.ss { color: #bb6688; } /* SpecialString */ 230 code span.st { color: #4070a0; } /* String */ 231 code span.va { color: #19177c; } /* Variable */ 232 code span.vs { color: #4070a0; } /* VerbatimString */ 233 code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */ 234 </style> 235 </head> 236 <body> 237 <h1 id="cachy-workstation">Cachy-workstation</h1> 238 <p>Infrastructure-as-Code for a CachyOS Linux workstation, managed with 239 Ansible.</p> 240 <h2 id="goals">Goals</h2> 241 <ul> 242 <li>Describe and reproduce the workstation configuration 243 declaratively</li> 244 <li>Learn Ansible patterns: site-based playbooks, roles, and tags</li> 245 </ul> 246 <h2 id="design">Design</h2> 247 <ul> 248 <li><strong>Tool:</strong> Ansible</li> 249 <li><strong>Target:</strong> <code>localhost</code> only</li> 250 <li><strong>Entry point:</strong> <code>site.yml</code></li> 251 <li><strong>Structure:</strong> Roles with tags for selective 252 execution</li> 253 </ul> 254 <h2 id="new-machine-bootstrap">New Machine Bootstrap</h2> 255 <p>On a fresh machine, run <code>bootstrap.yml</code> first. It handles 256 the prerequisites that <code>site.yml</code> depends on:</p> 257 <ol type="1"> 258 <li>Installs and authenticates Tailscale</li> 259 <li>Imports GPG keys from the local git server</li> 260 <li>Clones the pass password store</li> 261 </ol> 262 <p>Prerequisites before running bootstrap:</p> 263 <div class="sourceCode" id="cb1"><pre 264 class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> pacman <span class="at">-S</span> ansible git</span></code></pre></div> 265 <p>Run bootstrap:</p> 266 <div class="sourceCode" id="cb2"><pre 267 class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">ansible-playbook</span> <span class="at">-K</span> <span class="at">-i</span> inventory/hosts.yml bootstrap.yml</span></code></pre></div> 268 <p>Then run the full site playbook:</p> 269 <div class="sourceCode" id="cb3"><pre 270 class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">ansible-playbook</span> <span class="at">-K</span> <span class="at">-i</span> inventory/hosts.yml site.yml</span></code></pre></div> 271 <h2 id="usage">Usage</h2> 272 <p>Run the full site playbook:</p> 273 <div class="sourceCode" id="cb4"><pre 274 class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">ansible-playbook</span> <span class="at">-K</span> <span class="at">-i</span> inventory/hosts.yml site.yml</span></code></pre></div> 275 <p>Run only tasks matching a specific tag:</p> 276 <div class="sourceCode" id="cb5"><pre 277 class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">ansible-playbook</span> <span class="at">-K</span> <span class="at">-i</span> inventory/hosts.yml site.yml <span class="at">--tags</span> <span class="op"><</span>tagname<span class="op">></span></span></code></pre></div> 278 <p>Skip tasks matching a tag:</p> 279 <div class="sourceCode" id="cb6"><pre 280 class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">ansible-playbook</span> <span class="at">-K</span> <span class="at">-i</span> inventory/hosts.yml site.yml <span class="at">--skip-tags</span> <span class="op"><</span>tagname<span class="op">></span></span></code></pre></div> 281 <h2 id="roles">Roles</h2> 282 <table> 283 <colgroup> 284 <col style="width: 24%" /> 285 <col style="width: 24%" /> 286 <col style="width: 52%" /> 287 </colgroup> 288 <thead> 289 <tr> 290 <th>Role</th> 291 <th>Play</th> 292 <th>Description</th> 293 </tr> 294 </thead> 295 <tbody> 296 <tr> 297 <td>firewall</td> 298 <td>system</td> 299 <td>ufw, default deny incoming, allow port 22</td> 300 </tr> 301 <tr> 302 <td>packages</td> 303 <td>system</td> 304 <td>General packages via pacman</td> 305 </tr> 306 <tr> 307 <td>docker</td> 308 <td>system</td> 309 <td>Docker, enables service, adds user to docker group</td> 310 </tr> 311 <tr> 312 <td>virtualization</td> 313 <td>system</td> 314 <td>qemu-full, virt-manager, libvirtd</td> 315 </tr> 316 <tr> 317 <td>web</td> 318 <td>system</td> 319 <td>hcloud, hugo, tea</td> 320 </tr> 321 <tr> 322 <td>flatpak</td> 323 <td>system</td> 324 <td>Flatpak and apps, MakeMKV device permissions</td> 325 </tr> 326 <tr> 327 <td>gpg</td> 328 <td>user</td> 329 <td>Import GPG keys from private git repo</td> 330 </tr> 331 <tr> 332 <td>ssh</td> 333 <td>user</td> 334 <td>Deploy SSH keys and config from pass</td> 335 </tr> 336 <tr> 337 <td>pass</td> 338 <td>user</td> 339 <td>Clone pass password store</td> 340 </tr> 341 <tr> 342 <td>dotfiles</td> 343 <td>user</td> 344 <td>Clone dotfiles and nvim repos, symlink configs</td> 345 </tr> 346 <tr> 347 <td>scripts</td> 348 <td>user</td> 349 <td>Clone scripts repo, add to PATH, deploy Pushover credentials</td> 350 </tr> 351 <tr> 352 <td>hotkeys</td> 353 <td>user</td> 354 <td>Deploy .desktop files, set Plasma 6 global shortcuts</td> 355 </tr> 356 </tbody> 357 </table> 358 <h2 id="structure">Structure</h2> 359 <pre><code>. 360 ├── bootstrap.yml # New machine bootstrap 361 ├── site.yml # Main entry point 362 ├── inventory/ # Inventory files 363 └── roles/ # Ansible roles</code></pre> 364 </body> 365 </html>