[{"data":1,"prerenderedAt":4863},["ShallowReactive",2],{"page-\u002Flinux\u002F19-exercises-and-projects":3},{"id":4,"title":5,"body":6,"description":16,"extension":4857,"meta":4858,"navigation":73,"path":4859,"seo":4860,"stem":4861,"__hash__":4862},"content\u002Flinux\u002F19-exercises-and-projects.md","19 — Exercises & Projects",{"type":7,"value":8,"toc":4843},"minimark",[9,13,17,22,25,32,447,453,484,487,491,494,499,916,920,953,955,959,962,1211,1215,1242,1244,1248,1251,1723,1727,1763,1765,1769,1772,2224,2406,2410,2446,2448,2452,2455,3055,3165,3169,3197,3199,3203,3206,3554,3558,3594,3596,3600,3603,4087,4154,4158,4203,4205,4209,4212,4216,4303,4308,4447,4451,4532,4536,4636,4640,4643,4779,4796,4839],[10,11,5],"h1",{"id":12},"_19-exercises-projects",[14,15,16],"p",{},"Apply everything from chapters 1–18 in real-world projects. These exercises progress from focused drills to a full capstone. Each project lists requirements, hints, and a verification checklist.",[18,19,21],"h2",{"id":20},"project-1-system-inventory-script","Project 1 — System Inventory Script",[14,23,24],{},"Write a script that produces a system inventory report. Covers chapters 02–05 (shell, text processing, files, permissions).",[14,26,27,31],{},[28,29,30],"strong",{},"Requirements",": OS, kernel version, architecture, CPU model and core count, total and available memory, disk usage per filesystem, top 5 processes by CPU and memory, listening TCP ports.",[33,34,36],"code-wrapper",{"language":35},"bash",[37,38,42],"pre",{"className":39,"code":40,"language":35,"meta":41,"style":41},"language-bash shiki shiki-themes github-light github-dark","#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\necho \"=== System Inventory ===\"\necho \"Date: $(date)\"\necho \"Host: $(hostname)\"\necho \"OS: $(grep PRETTY_NAME \u002Fetc\u002Fos-release | cut -d= -f2 | tr -d '\"')\"\necho \"Kernel: $(uname -r)\"\necho \"Arch: $(uname -m)\"\necho\n\necho \"=== CPU ===\"\necho \"Model: $(grep -m1 'model name' \u002Fproc\u002Fcpuinfo | cut -d: -f2 | xargs)\"\necho \"Cores: $(nproc)\"\necho\n\necho \"=== Memory ===\"\nfree -h | awk '\u002FMem\u002F {print \"Total:\", $2, \" Available:\", $7}'\necho\n\necho \"=== Disk Usage ===\"\ndf -h | awk 'NR==1 || \u002F^\\\u002Fdev\u002F' | column -t\necho\n\necho \"=== Top 5 by CPU ===\"\nps aux --sort=-%cpu | head -6 | awk '{printf \"%-10s %5s %5s %s\\n\", $1, $3, $4, $11}'\necho\n\necho \"=== Top 5 by Memory ===\"\nps aux --sort=-%mem | head -6 | awk '{printf \"%-10s %5s %5s %s\\n\", $1, $3, $4, $11}'\necho\n\necho \"=== Listening TCP Ports ===\"\nss -tlnp | awk 'NR>1 {print $4, $6}' | column -t\n","",[43,44,45,54,68,75,84,99,112,151,167,182,188,193,201,233,246,251,256,264,281,286,291,299,322,327,332,340,367,372,377,385,407,412,417,425],"code",{"__ignoreMap":41},[46,47,50],"span",{"class":48,"line":49},"line",1,[46,51,53],{"class":52},"sdCPZ","#!\u002Fusr\u002Fbin\u002Fenv bash\n",[46,55,57,61,64],{"class":48,"line":56},2,[46,58,60],{"class":59},"snvgF","set",[46,62,63],{"class":59}," -euo",[46,65,67],{"class":66},"sJ6F3"," pipefail\n",[46,69,71],{"class":48,"line":70},3,[46,72,74],{"emptyLinePlaceholder":73},true,"\n",[46,76,78,81],{"class":48,"line":77},4,[46,79,80],{"class":59},"echo",[46,82,83],{"class":66}," \"=== System Inventory ===\"\n",[46,85,87,89,92,96],{"class":48,"line":86},5,[46,88,80],{"class":59},[46,90,91],{"class":66}," \"Date: $(",[46,93,95],{"class":94},"sIsaT","date",[46,97,98],{"class":66},")\"\n",[46,100,102,104,107,110],{"class":48,"line":101},6,[46,103,80],{"class":59},[46,105,106],{"class":66}," \"Host: $(",[46,108,109],{"class":94},"hostname",[46,111,98],{"class":66},[46,113,115,117,120,123,126,130,133,136,139,142,145,148],{"class":48,"line":114},7,[46,116,80],{"class":59},[46,118,119],{"class":66}," \"OS: $(",[46,121,122],{"class":94},"grep",[46,124,125],{"class":66}," PRETTY_NAME \u002Fetc\u002Fos-release ",[46,127,129],{"class":128},"svdQ7","|",[46,131,132],{"class":94}," cut",[46,134,135],{"class":59}," -d=",[46,137,138],{"class":59}," -f2",[46,140,141],{"class":128}," |",[46,143,144],{"class":94}," tr",[46,146,147],{"class":59}," -d",[46,149,150],{"class":66}," '\"')\"\n",[46,152,154,156,159,162,165],{"class":48,"line":153},8,[46,155,80],{"class":59},[46,157,158],{"class":66}," \"Kernel: $(",[46,160,161],{"class":94},"uname",[46,163,164],{"class":59}," -r",[46,166,98],{"class":66},[46,168,170,172,175,177,180],{"class":48,"line":169},9,[46,171,80],{"class":59},[46,173,174],{"class":66}," \"Arch: $(",[46,176,161],{"class":94},[46,178,179],{"class":59}," -m",[46,181,98],{"class":66},[46,183,185],{"class":48,"line":184},10,[46,186,187],{"class":59},"echo\n",[46,189,191],{"class":48,"line":190},11,[46,192,74],{"emptyLinePlaceholder":73},[46,194,196,198],{"class":48,"line":195},12,[46,197,80],{"class":59},[46,199,200],{"class":66}," \"=== CPU ===\"\n",[46,202,204,206,209,211,214,217,219,221,224,226,228,231],{"class":48,"line":203},13,[46,205,80],{"class":59},[46,207,208],{"class":66}," \"Model: $(",[46,210,122],{"class":94},[46,212,213],{"class":59}," -m1",[46,215,216],{"class":66}," 'model name' \u002Fproc\u002Fcpuinfo ",[46,218,129],{"class":128},[46,220,132],{"class":94},[46,222,223],{"class":59}," -d:",[46,225,138],{"class":59},[46,227,141],{"class":128},[46,229,230],{"class":94}," xargs",[46,232,98],{"class":66},[46,234,236,238,241,244],{"class":48,"line":235},14,[46,237,80],{"class":59},[46,239,240],{"class":66}," \"Cores: $(",[46,242,243],{"class":94},"nproc",[46,245,98],{"class":66},[46,247,249],{"class":48,"line":248},15,[46,250,187],{"class":59},[46,252,254],{"class":48,"line":253},16,[46,255,74],{"emptyLinePlaceholder":73},[46,257,259,261],{"class":48,"line":258},17,[46,260,80],{"class":59},[46,262,263],{"class":66}," \"=== Memory ===\"\n",[46,265,267,270,273,275,278],{"class":48,"line":266},18,[46,268,269],{"class":94},"free",[46,271,272],{"class":59}," -h",[46,274,141],{"class":128},[46,276,277],{"class":94}," awk",[46,279,280],{"class":66}," '\u002FMem\u002F {print \"Total:\", $2, \" Available:\", $7}'\n",[46,282,284],{"class":48,"line":283},19,[46,285,187],{"class":59},[46,287,289],{"class":48,"line":288},20,[46,290,74],{"emptyLinePlaceholder":73},[46,292,294,296],{"class":48,"line":293},21,[46,295,80],{"class":59},[46,297,298],{"class":66}," \"=== Disk Usage ===\"\n",[46,300,302,305,307,309,311,314,316,319],{"class":48,"line":301},22,[46,303,304],{"class":94},"df",[46,306,272],{"class":59},[46,308,141],{"class":128},[46,310,277],{"class":94},[46,312,313],{"class":66}," 'NR==1 || \u002F^\\\u002Fdev\u002F'",[46,315,141],{"class":128},[46,317,318],{"class":94}," column",[46,320,321],{"class":59}," -t\n",[46,323,325],{"class":48,"line":324},23,[46,326,187],{"class":59},[46,328,330],{"class":48,"line":329},24,[46,331,74],{"emptyLinePlaceholder":73},[46,333,335,337],{"class":48,"line":334},25,[46,336,80],{"class":59},[46,338,339],{"class":66}," \"=== Top 5 by CPU ===\"\n",[46,341,343,346,349,352,354,357,360,362,364],{"class":48,"line":342},26,[46,344,345],{"class":94},"ps",[46,347,348],{"class":66}," aux",[46,350,351],{"class":59}," --sort=-%cpu",[46,353,141],{"class":128},[46,355,356],{"class":94}," head",[46,358,359],{"class":59}," -6",[46,361,141],{"class":128},[46,363,277],{"class":94},[46,365,366],{"class":66}," '{printf \"%-10s %5s %5s %s\\n\", $1, $3, $4, $11}'\n",[46,368,370],{"class":48,"line":369},27,[46,371,187],{"class":59},[46,373,375],{"class":48,"line":374},28,[46,376,74],{"emptyLinePlaceholder":73},[46,378,380,382],{"class":48,"line":379},29,[46,381,80],{"class":59},[46,383,384],{"class":66}," \"=== Top 5 by Memory ===\"\n",[46,386,388,390,392,395,397,399,401,403,405],{"class":48,"line":387},30,[46,389,345],{"class":94},[46,391,348],{"class":66},[46,393,394],{"class":59}," --sort=-%mem",[46,396,141],{"class":128},[46,398,356],{"class":94},[46,400,359],{"class":59},[46,402,141],{"class":128},[46,404,277],{"class":94},[46,406,366],{"class":66},[46,408,410],{"class":48,"line":409},31,[46,411,187],{"class":59},[46,413,415],{"class":48,"line":414},32,[46,416,74],{"emptyLinePlaceholder":73},[46,418,420,422],{"class":48,"line":419},33,[46,421,80],{"class":59},[46,423,424],{"class":66}," \"=== Listening TCP Ports ===\"\n",[46,426,428,431,434,436,438,441,443,445],{"class":48,"line":427},34,[46,429,430],{"class":94},"ss",[46,432,433],{"class":59}," -tlnp",[46,435,141],{"class":128},[46,437,277],{"class":94},[46,439,440],{"class":66}," 'NR>1 {print $4, $6}'",[46,442,141],{"class":128},[46,444,318],{"class":94},[46,446,321],{"class":59},[14,448,449,452],{},[28,450,451],{},"Verification",":",[454,455,458,468,478],"ul",{"className":456},[457],"contains-task-list",[459,460,463,467],"li",{"className":461},[462],"task-list-item",[464,465],"input",{"disabled":73,"type":466},"checkbox"," Output is formatted in readable tables.",[459,469,471,473,474,477],{"className":470},[462],[464,472],{"disabled":73,"type":466}," Script runs with ",[43,475,476],{},"set -euo pipefail"," without errors.",[459,479,481,483],{"className":480},[462],[464,482],{"disabled":73,"type":466}," Works on both Debian and RHEL (portable commands).",[485,486],"hr",{},[18,488,490],{"id":489},"project-2-log-analysis-pipeline","Project 2 — Log Analysis Pipeline",[14,492,493],{},"Build a pipeline to analyze web server logs. Covers chapters 05 (text processing) and 12 (logging).",[14,495,496,498],{},[28,497,30],{},": Top 10 requesting IPs, top 10 requested paths, top 10 HTTP status codes, count of 4xx and 5xx errors, total bandwidth.",[33,500,501],{"language":35},[37,502,504],{"className":39,"code":503,"language":35,"meta":41,"style":41},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\nLOG=\"${1:-\u002Fvar\u002Flog\u002Fnginx\u002Faccess.log}\"\n[[ -f \"$LOG\" ]] || { echo \"Log not found: $LOG\" >&2; exit 1; }\n\necho \"=== Log Analysis: $LOG ===\"\necho \"Total requests: $(wc -l \u003C \"$LOG\")\"\necho\n\necho \"=== Top 10 IPs ===\"\nawk '{print $1}' \"$LOG\" | sort | uniq -c | sort -rn | head -10 | awk '{printf \"%6d  %s\\n\", $1, $2}'\necho\n\necho \"=== Top 10 Paths ===\"\nawk -F'\"' '{print $2}' \"$LOG\" | awk '{print $2}' | sort | uniq -c | sort -rn | head -10 | awk '{printf \"%6d  %s\\n\", $1, $2}'\necho\n\necho \"=== Status Codes ===\"\nawk -F'\"' '{print $3}' \"$LOG\" | awk '{print $1}' | sort | uniq -c | sort -rn\necho\n\necho \"=== Error Summary ===\"\nawk -F'\"' '{print $3}' \"$LOG\" | awk '{print $1}' | awk '$1 >= 400 && $1 \u003C 500 {c4++} $1 >= 500 {c5++} END{print \"4xx:\", c4+0; print \"5xx:\", c5+0}'\necho\n\necho \"=== Bandwidth ===\"\nawk '{sum += $10} END{printf \"Total: %.2f MB\\n\", sum\u002F1024\u002F1024}' \"$LOG\"\n",[43,505,506,510,518,522,570,619,623,635,658,662,666,673,721,725,729,736,789,793,797,804,844,848,852,859,888,892,896,903],{"__ignoreMap":41},[46,507,508],{"class":48,"line":49},[46,509,53],{"class":52},[46,511,512,514,516],{"class":48,"line":56},[46,513,60],{"class":59},[46,515,63],{"class":59},[46,517,67],{"class":66},[46,519,520],{"class":48,"line":70},[46,521,74],{"emptyLinePlaceholder":73},[46,523,524,528,531,534,537,540,543,546,549,551,554,556,559,562,564,567],{"class":48,"line":77},[46,525,527],{"class":526},"ssxIu","LOG",[46,529,530],{"class":128},"=",[46,532,533],{"class":66},"\"",[46,535,536],{"class":59},"${1",[46,538,539],{"class":128},":-\u002F",[46,541,542],{"class":526},"var",[46,544,545],{"class":128},"\u002F",[46,547,548],{"class":526},"log",[46,550,545],{"class":128},[46,552,553],{"class":526},"nginx",[46,555,545],{"class":128},[46,557,558],{"class":526},"access",[46,560,561],{"class":66},".",[46,563,548],{"class":526},[46,565,566],{"class":59},"}",[46,568,569],{"class":66},"\"\n",[46,571,572,575,578,581,584,586,589,592,595,597,600,602,604,607,610,613,616],{"class":48,"line":86},[46,573,574],{"class":526},"[[ ",[46,576,577],{"class":128},"-f",[46,579,580],{"class":66}," \"",[46,582,583],{"class":526},"$LOG",[46,585,533],{"class":66},[46,587,588],{"class":526}," ]] ",[46,590,591],{"class":128},"||",[46,593,594],{"class":526}," { ",[46,596,80],{"class":59},[46,598,599],{"class":66}," \"Log not found: ",[46,601,583],{"class":526},[46,603,533],{"class":66},[46,605,606],{"class":128}," >&2",[46,608,609],{"class":526},"; ",[46,611,612],{"class":59},"exit",[46,614,615],{"class":59}," 1",[46,617,618],{"class":526},"; }\n",[46,620,621],{"class":48,"line":101},[46,622,74],{"emptyLinePlaceholder":73},[46,624,625,627,630,632],{"class":48,"line":114},[46,626,80],{"class":59},[46,628,629],{"class":66}," \"=== Log Analysis: ",[46,631,583],{"class":526},[46,633,634],{"class":66}," ===\"\n",[46,636,637,639,642,645,648,651,653,655],{"class":48,"line":153},[46,638,80],{"class":59},[46,640,641],{"class":66}," \"Total requests: $(",[46,643,644],{"class":94},"wc",[46,646,647],{"class":59}," -l",[46,649,650],{"class":128}," \u003C",[46,652,580],{"class":66},[46,654,583],{"class":526},[46,656,657],{"class":66},"\")\"\n",[46,659,660],{"class":48,"line":169},[46,661,187],{"class":59},[46,663,664],{"class":48,"line":184},[46,665,74],{"emptyLinePlaceholder":73},[46,667,668,670],{"class":48,"line":190},[46,669,80],{"class":59},[46,671,672],{"class":66}," \"=== Top 10 IPs ===\"\n",[46,674,675,678,681,683,685,687,689,692,694,697,700,702,704,707,709,711,714,716,718],{"class":48,"line":195},[46,676,677],{"class":94},"awk",[46,679,680],{"class":66}," '{print $1}'",[46,682,580],{"class":66},[46,684,583],{"class":526},[46,686,533],{"class":66},[46,688,141],{"class":128},[46,690,691],{"class":94}," sort",[46,693,141],{"class":128},[46,695,696],{"class":94}," uniq",[46,698,699],{"class":59}," -c",[46,701,141],{"class":128},[46,703,691],{"class":94},[46,705,706],{"class":59}," -rn",[46,708,141],{"class":128},[46,710,356],{"class":94},[46,712,713],{"class":59}," -10",[46,715,141],{"class":128},[46,717,277],{"class":94},[46,719,720],{"class":66}," '{printf \"%6d  %s\\n\", $1, $2}'\n",[46,722,723],{"class":48,"line":203},[46,724,187],{"class":59},[46,726,727],{"class":48,"line":235},[46,728,74],{"emptyLinePlaceholder":73},[46,730,731,733],{"class":48,"line":248},[46,732,80],{"class":59},[46,734,735],{"class":66}," \"=== Top 10 Paths ===\"\n",[46,737,738,740,743,746,749,751,753,755,757,759,761,763,765,767,769,771,773,775,777,779,781,783,785,787],{"class":48,"line":253},[46,739,677],{"class":94},[46,741,742],{"class":59}," -F",[46,744,745],{"class":66},"'\"'",[46,747,748],{"class":66}," '{print $2}'",[46,750,580],{"class":66},[46,752,583],{"class":526},[46,754,533],{"class":66},[46,756,141],{"class":128},[46,758,277],{"class":94},[46,760,748],{"class":66},[46,762,141],{"class":128},[46,764,691],{"class":94},[46,766,141],{"class":128},[46,768,696],{"class":94},[46,770,699],{"class":59},[46,772,141],{"class":128},[46,774,691],{"class":94},[46,776,706],{"class":59},[46,778,141],{"class":128},[46,780,356],{"class":94},[46,782,713],{"class":59},[46,784,141],{"class":128},[46,786,277],{"class":94},[46,788,720],{"class":66},[46,790,791],{"class":48,"line":258},[46,792,187],{"class":59},[46,794,795],{"class":48,"line":266},[46,796,74],{"emptyLinePlaceholder":73},[46,798,799,801],{"class":48,"line":283},[46,800,80],{"class":59},[46,802,803],{"class":66}," \"=== Status Codes ===\"\n",[46,805,806,808,810,812,815,817,819,821,823,825,827,829,831,833,835,837,839,841],{"class":48,"line":288},[46,807,677],{"class":94},[46,809,742],{"class":59},[46,811,745],{"class":66},[46,813,814],{"class":66}," '{print $3}'",[46,816,580],{"class":66},[46,818,583],{"class":526},[46,820,533],{"class":66},[46,822,141],{"class":128},[46,824,277],{"class":94},[46,826,680],{"class":66},[46,828,141],{"class":128},[46,830,691],{"class":94},[46,832,141],{"class":128},[46,834,696],{"class":94},[46,836,699],{"class":59},[46,838,141],{"class":128},[46,840,691],{"class":94},[46,842,843],{"class":59}," -rn\n",[46,845,846],{"class":48,"line":293},[46,847,187],{"class":59},[46,849,850],{"class":48,"line":301},[46,851,74],{"emptyLinePlaceholder":73},[46,853,854,856],{"class":48,"line":324},[46,855,80],{"class":59},[46,857,858],{"class":66}," \"=== Error Summary ===\"\n",[46,860,861,863,865,867,869,871,873,875,877,879,881,883,885],{"class":48,"line":329},[46,862,677],{"class":94},[46,864,742],{"class":59},[46,866,745],{"class":66},[46,868,814],{"class":66},[46,870,580],{"class":66},[46,872,583],{"class":526},[46,874,533],{"class":66},[46,876,141],{"class":128},[46,878,277],{"class":94},[46,880,680],{"class":66},[46,882,141],{"class":128},[46,884,277],{"class":94},[46,886,887],{"class":66}," '$1 >= 400 && $1 \u003C 500 {c4++} $1 >= 500 {c5++} END{print \"4xx:\", c4+0; print \"5xx:\", c5+0}'\n",[46,889,890],{"class":48,"line":334},[46,891,187],{"class":59},[46,893,894],{"class":48,"line":342},[46,895,74],{"emptyLinePlaceholder":73},[46,897,898,900],{"class":48,"line":369},[46,899,80],{"class":59},[46,901,902],{"class":66}," \"=== Bandwidth ===\"\n",[46,904,905,907,910,912,914],{"class":48,"line":374},[46,906,677],{"class":94},[46,908,909],{"class":66}," '{sum += $10} END{printf \"Total: %.2f MB\\n\", sum\u002F1024\u002F1024}'",[46,911,580],{"class":66},[46,913,583],{"class":526},[46,915,569],{"class":66},[14,917,918,452],{},[28,919,451],{},[454,921,923,933,947],{"className":922},[457],[459,924,926,928,929,932],{"className":925},[462],[464,927],{"disabled":73,"type":466}," Top IPs\u002Fpaths\u002Fstatus codes are correct (verify with manual ",[43,930,931],{},"grep | wc",").",[459,934,936,938,939,942,943,946],{"className":935},[462],[464,937],{"disabled":73,"type":466}," ",[43,940,941],{},"sort"," before ",[43,944,945],{},"uniq -c"," (common bug: forgetting to sort).",[459,948,950,952],{"className":949},[462],[464,951],{"disabled":73,"type":466}," Bandwidth is in MB (converted from bytes).",[485,954],{},[18,956,958],{"id":957},"project-3-user-group-audit","Project 3 — User & Group Audit",[14,960,961],{},"Audit user accounts and sudo access. Covers chapter 07.",[33,963,964],{"language":35},[37,965,967],{"className":39,"code":966,"language":35,"meta":41,"style":41},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\necho \"=== User Audit ===\"\necho\n\necho \"Regular users (UID ≥ 1000):\"\nawk -F: '$3 >= 1000 && $3 \u003C 65534 {printf \"  %-15s UID:%s  Shell:%s\\n\", $1, $3, $7}' \u002Fetc\u002Fpasswd\necho\n\necho \"Users with login shells:\"\nawk -F: '$7 !~ \u002Fnologin|false$\u002F {printf \"  %-15s %s\\n\", $1, $7}' \u002Fetc\u002Fpasswd\necho\n\necho \"Sudo users:\"\ngetent group sudo 2>\u002Fdev\u002Fnull || getent group wheel\necho\n\necho \"Password aging (regular users):\"\nfor user in $(awk -F: '$3 >= 1000 && $3 \u003C 65534 {print $1}' \u002Fetc\u002Fpasswd); do\n  echo \"  --- $user ---\"\n  sudo chage -l \"$user\" 2>\u002Fdev\u002Fnull | sed 's\u002F^\u002F    \u002F'\ndone\necho\n\necho \"Accounts with no password or locked:\"\nsudo awk -F: '($2 == \"!\" || $2 == \"*\" || $2 == \"!!\") {print \"  \" $1 \": \" $2}' \u002Fetc\u002Fshadow\n",[43,968,969,973,981,985,992,996,1000,1007,1020,1024,1028,1035,1046,1050,1054,1061,1089,1093,1097,1104,1134,1148,1176,1181,1185,1189,1196],{"__ignoreMap":41},[46,970,971],{"class":48,"line":49},[46,972,53],{"class":52},[46,974,975,977,979],{"class":48,"line":56},[46,976,60],{"class":59},[46,978,63],{"class":59},[46,980,67],{"class":66},[46,982,983],{"class":48,"line":70},[46,984,74],{"emptyLinePlaceholder":73},[46,986,987,989],{"class":48,"line":77},[46,988,80],{"class":59},[46,990,991],{"class":66}," \"=== User Audit ===\"\n",[46,993,994],{"class":48,"line":86},[46,995,187],{"class":59},[46,997,998],{"class":48,"line":101},[46,999,74],{"emptyLinePlaceholder":73},[46,1001,1002,1004],{"class":48,"line":114},[46,1003,80],{"class":59},[46,1005,1006],{"class":66}," \"Regular users (UID ≥ 1000):\"\n",[46,1008,1009,1011,1014,1017],{"class":48,"line":153},[46,1010,677],{"class":94},[46,1012,1013],{"class":59}," -F:",[46,1015,1016],{"class":66}," '$3 >= 1000 && $3 \u003C 65534 {printf \"  %-15s UID:%s  Shell:%s\\n\", $1, $3, $7}'",[46,1018,1019],{"class":66}," \u002Fetc\u002Fpasswd\n",[46,1021,1022],{"class":48,"line":169},[46,1023,187],{"class":59},[46,1025,1026],{"class":48,"line":184},[46,1027,74],{"emptyLinePlaceholder":73},[46,1029,1030,1032],{"class":48,"line":190},[46,1031,80],{"class":59},[46,1033,1034],{"class":66}," \"Users with login shells:\"\n",[46,1036,1037,1039,1041,1044],{"class":48,"line":195},[46,1038,677],{"class":94},[46,1040,1013],{"class":59},[46,1042,1043],{"class":66}," '$7 !~ \u002Fnologin|false$\u002F {printf \"  %-15s %s\\n\", $1, $7}'",[46,1045,1019],{"class":66},[46,1047,1048],{"class":48,"line":203},[46,1049,187],{"class":59},[46,1051,1052],{"class":48,"line":235},[46,1053,74],{"emptyLinePlaceholder":73},[46,1055,1056,1058],{"class":48,"line":248},[46,1057,80],{"class":59},[46,1059,1060],{"class":66}," \"Sudo users:\"\n",[46,1062,1063,1066,1069,1072,1075,1078,1081,1084,1086],{"class":48,"line":253},[46,1064,1065],{"class":94},"getent",[46,1067,1068],{"class":66}," group",[46,1070,1071],{"class":66}," sudo",[46,1073,1074],{"class":128}," 2>",[46,1076,1077],{"class":66},"\u002Fdev\u002Fnull",[46,1079,1080],{"class":128}," ||",[46,1082,1083],{"class":94}," getent",[46,1085,1068],{"class":66},[46,1087,1088],{"class":66}," wheel\n",[46,1090,1091],{"class":48,"line":258},[46,1092,187],{"class":59},[46,1094,1095],{"class":48,"line":266},[46,1096,74],{"emptyLinePlaceholder":73},[46,1098,1099,1101],{"class":48,"line":283},[46,1100,80],{"class":59},[46,1102,1103],{"class":66}," \"Password aging (regular users):\"\n",[46,1105,1106,1109,1112,1115,1118,1120,1122,1125,1128,1131],{"class":48,"line":288},[46,1107,1108],{"class":128},"for",[46,1110,1111],{"class":526}," user ",[46,1113,1114],{"class":128},"in",[46,1116,1117],{"class":526}," $(",[46,1119,677],{"class":94},[46,1121,1013],{"class":59},[46,1123,1124],{"class":66}," '$3 >= 1000 && $3 \u003C 65534 {print $1}'",[46,1126,1127],{"class":66}," \u002Fetc\u002Fpasswd",[46,1129,1130],{"class":526},"); ",[46,1132,1133],{"class":128},"do\n",[46,1135,1136,1139,1142,1145],{"class":48,"line":293},[46,1137,1138],{"class":59},"  echo",[46,1140,1141],{"class":66}," \"  --- ",[46,1143,1144],{"class":526},"$user",[46,1146,1147],{"class":66}," ---\"\n",[46,1149,1150,1153,1156,1158,1160,1162,1164,1166,1168,1170,1173],{"class":48,"line":301},[46,1151,1152],{"class":94},"  sudo",[46,1154,1155],{"class":66}," chage",[46,1157,647],{"class":59},[46,1159,580],{"class":66},[46,1161,1144],{"class":526},[46,1163,533],{"class":66},[46,1165,1074],{"class":128},[46,1167,1077],{"class":66},[46,1169,141],{"class":128},[46,1171,1172],{"class":94}," sed",[46,1174,1175],{"class":66}," 's\u002F^\u002F    \u002F'\n",[46,1177,1178],{"class":48,"line":324},[46,1179,1180],{"class":128},"done\n",[46,1182,1183],{"class":48,"line":329},[46,1184,187],{"class":59},[46,1186,1187],{"class":48,"line":334},[46,1188,74],{"emptyLinePlaceholder":73},[46,1190,1191,1193],{"class":48,"line":342},[46,1192,80],{"class":59},[46,1194,1195],{"class":66}," \"Accounts with no password or locked:\"\n",[46,1197,1198,1201,1203,1205,1208],{"class":48,"line":369},[46,1199,1200],{"class":94},"sudo",[46,1202,277],{"class":66},[46,1204,1013],{"class":59},[46,1206,1207],{"class":66}," '($2 == \"!\" || $2 == \"*\" || $2 == \"!!\") {print \"  \" $1 \": \" $2}'",[46,1209,1210],{"class":66}," \u002Fetc\u002Fshadow\n",[14,1212,1213,452],{},[28,1214,451],{},[454,1216,1218,1224,1233],{"className":1217},[457],[459,1219,1221,1223],{"className":1220},[462],[464,1222],{"disabled":73,"type":466}," All regular users listed.",[459,1225,1227,1229,1230,932],{"className":1226},[462],[464,1228],{"disabled":73,"type":466}," Sudo group membership correct (cross-check with ",[43,1231,1232],{},"id \u003Cuser>",[459,1234,1236,1238,1239,932],{"className":1235},[462],[464,1237],{"disabled":73,"type":466}," Locked accounts flagged correctly (",[43,1240,1241],{},"passwd -S",[485,1243],{},[18,1245,1247],{"id":1246},"project-4-hardened-web-server","Project 4 — Hardened Web Server",[14,1249,1250],{},"Set up an nginx web server with full hardening. Covers chapters 11, 14, 10.",[33,1252,1253],{"language":35},[37,1254,1256],{"className":39,"code":1255,"language":35,"meta":41,"style":41},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\n# 1. Install\nsudo apt update && sudo apt install -y nginx ufw fail2ban\n\n# 2. Firewall\nsudo ufw allow 22\u002Ftcp\nsudo ufw allow 80\u002Ftcp\nsudo ufw allow 443\u002Ftcp\nsudo ufw --force enable\n\n# 3. SSH hardening (verify key auth first!)\nsudo sed -i 's\u002F^#*PermitRootLogin.*\u002FPermitRootLogin no\u002F' \u002Fetc\u002Fssh\u002Fsshd_config\nsudo sed -i 's\u002F^#*PasswordAuthentication.*\u002FPasswordAuthentication no\u002F' \u002Fetc\u002Fssh\u002Fsshd_config\nsudo sshd -t && sudo systemctl reload ssh\n\n# 4. fail2ban\nsudo systemctl enable --now fail2ban\n\n# 5. TLS cert (self-signed — use certbot for real)\nsudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \\\n  -keyout \u002Fetc\u002Fssl\u002Fprivate\u002Fselfsigned.key \\\n  -out \u002Fetc\u002Fssl\u002Fcerts\u002Fselfsigned.crt \\\n  -subj \"\u002FCN=localhost\"\n\n# 6. nginx config with security headers\nsudo tee \u002Fetc\u002Fnginx\u002Fsites-available\u002Fhardened \u003C\u003C'EOF'\nserver {\n    listen 80;\n    return 301 https:\u002F\u002F$host$request_uri;\n}\nserver {\n    listen 443 ssl;\n    ssl_certificate \u002Fetc\u002Fssl\u002Fcerts\u002Fselfsigned.crt;\n    ssl_certificate_key \u002Fetc\u002Fssl\u002Fprivate\u002Fselfsigned.key;\n    add_header Strict-Transport-Security \"max-age=31536000\" always;\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    location \u002F {\n        root \u002Fvar\u002Fwww\u002Fhtml;\n        index index.html;\n    }\n}\nEOF\nsudo ln -sf \u002Fetc\u002Fnginx\u002Fsites-available\u002Fhardened \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fhardened\nsudo nginx -t && sudo systemctl reload nginx\n\n# 7. Verify\ncurl -kI https:\u002F\u002Flocalhost\u002F\nss -tlnp | grep -E ':80|:443'\nsudo ufw status\n",[43,1257,1258,1262,1270,1274,1279,1311,1315,1320,1332,1343,1354,1366,1370,1375,1390,1403,1426,1430,1435,1449,1453,1458,1489,1499,1509,1517,1521,1526,1542,1547,1552,1557,1562,1566,1571,1577,1583,1589,1595,1601,1607,1613,1619,1625,1630,1636,1652,1672,1677,1683,1695,1713],{"__ignoreMap":41},[46,1259,1260],{"class":48,"line":49},[46,1261,53],{"class":52},[46,1263,1264,1266,1268],{"class":48,"line":56},[46,1265,60],{"class":59},[46,1267,63],{"class":59},[46,1269,67],{"class":66},[46,1271,1272],{"class":48,"line":70},[46,1273,74],{"emptyLinePlaceholder":73},[46,1275,1276],{"class":48,"line":77},[46,1277,1278],{"class":52},"# 1. Install\n",[46,1280,1281,1283,1286,1289,1292,1294,1296,1299,1302,1305,1308],{"class":48,"line":86},[46,1282,1200],{"class":94},[46,1284,1285],{"class":66}," apt",[46,1287,1288],{"class":66}," update",[46,1290,1291],{"class":526}," && ",[46,1293,1200],{"class":94},[46,1295,1285],{"class":66},[46,1297,1298],{"class":66}," install",[46,1300,1301],{"class":59}," -y",[46,1303,1304],{"class":66}," nginx",[46,1306,1307],{"class":66}," ufw",[46,1309,1310],{"class":66}," fail2ban\n",[46,1312,1313],{"class":48,"line":101},[46,1314,74],{"emptyLinePlaceholder":73},[46,1316,1317],{"class":48,"line":114},[46,1318,1319],{"class":52},"# 2. Firewall\n",[46,1321,1322,1324,1326,1329],{"class":48,"line":153},[46,1323,1200],{"class":94},[46,1325,1307],{"class":66},[46,1327,1328],{"class":66}," allow",[46,1330,1331],{"class":66}," 22\u002Ftcp\n",[46,1333,1334,1336,1338,1340],{"class":48,"line":169},[46,1335,1200],{"class":94},[46,1337,1307],{"class":66},[46,1339,1328],{"class":66},[46,1341,1342],{"class":66}," 80\u002Ftcp\n",[46,1344,1345,1347,1349,1351],{"class":48,"line":184},[46,1346,1200],{"class":94},[46,1348,1307],{"class":66},[46,1350,1328],{"class":66},[46,1352,1353],{"class":66}," 443\u002Ftcp\n",[46,1355,1356,1358,1360,1363],{"class":48,"line":190},[46,1357,1200],{"class":94},[46,1359,1307],{"class":66},[46,1361,1362],{"class":59}," --force",[46,1364,1365],{"class":66}," enable\n",[46,1367,1368],{"class":48,"line":195},[46,1369,74],{"emptyLinePlaceholder":73},[46,1371,1372],{"class":48,"line":203},[46,1373,1374],{"class":52},"# 3. SSH hardening (verify key auth first!)\n",[46,1376,1377,1379,1381,1384,1387],{"class":48,"line":235},[46,1378,1200],{"class":94},[46,1380,1172],{"class":66},[46,1382,1383],{"class":59}," -i",[46,1385,1386],{"class":66}," 's\u002F^#*PermitRootLogin.*\u002FPermitRootLogin no\u002F'",[46,1388,1389],{"class":66}," \u002Fetc\u002Fssh\u002Fsshd_config\n",[46,1391,1392,1394,1396,1398,1401],{"class":48,"line":248},[46,1393,1200],{"class":94},[46,1395,1172],{"class":66},[46,1397,1383],{"class":59},[46,1399,1400],{"class":66}," 's\u002F^#*PasswordAuthentication.*\u002FPasswordAuthentication no\u002F'",[46,1402,1389],{"class":66},[46,1404,1405,1407,1410,1413,1415,1417,1420,1423],{"class":48,"line":253},[46,1406,1200],{"class":94},[46,1408,1409],{"class":66}," sshd",[46,1411,1412],{"class":59}," -t",[46,1414,1291],{"class":526},[46,1416,1200],{"class":94},[46,1418,1419],{"class":66}," systemctl",[46,1421,1422],{"class":66}," reload",[46,1424,1425],{"class":66}," ssh\n",[46,1427,1428],{"class":48,"line":258},[46,1429,74],{"emptyLinePlaceholder":73},[46,1431,1432],{"class":48,"line":266},[46,1433,1434],{"class":52},"# 4. fail2ban\n",[46,1436,1437,1439,1441,1444,1447],{"class":48,"line":283},[46,1438,1200],{"class":94},[46,1440,1419],{"class":66},[46,1442,1443],{"class":66}," enable",[46,1445,1446],{"class":59}," --now",[46,1448,1310],{"class":66},[46,1450,1451],{"class":48,"line":288},[46,1452,74],{"emptyLinePlaceholder":73},[46,1454,1455],{"class":48,"line":293},[46,1456,1457],{"class":52},"# 5. TLS cert (self-signed — use certbot for real)\n",[46,1459,1460,1462,1465,1468,1471,1474,1477,1480,1483,1486],{"class":48,"line":301},[46,1461,1200],{"class":94},[46,1463,1464],{"class":66}," openssl",[46,1466,1467],{"class":66}," req",[46,1469,1470],{"class":59}," -x509",[46,1472,1473],{"class":59}," -nodes",[46,1475,1476],{"class":59}," -days",[46,1478,1479],{"class":59}," 365",[46,1481,1482],{"class":59}," -newkey",[46,1484,1485],{"class":66}," rsa:2048",[46,1487,1488],{"class":59}," \\\n",[46,1490,1491,1494,1497],{"class":48,"line":324},[46,1492,1493],{"class":59},"  -keyout",[46,1495,1496],{"class":66}," \u002Fetc\u002Fssl\u002Fprivate\u002Fselfsigned.key",[46,1498,1488],{"class":59},[46,1500,1501,1504,1507],{"class":48,"line":329},[46,1502,1503],{"class":59},"  -out",[46,1505,1506],{"class":66}," \u002Fetc\u002Fssl\u002Fcerts\u002Fselfsigned.crt",[46,1508,1488],{"class":59},[46,1510,1511,1514],{"class":48,"line":334},[46,1512,1513],{"class":59},"  -subj",[46,1515,1516],{"class":66}," \"\u002FCN=localhost\"\n",[46,1518,1519],{"class":48,"line":342},[46,1520,74],{"emptyLinePlaceholder":73},[46,1522,1523],{"class":48,"line":369},[46,1524,1525],{"class":52},"# 6. nginx config with security headers\n",[46,1527,1528,1530,1533,1536,1539],{"class":48,"line":374},[46,1529,1200],{"class":94},[46,1531,1532],{"class":66}," tee",[46,1534,1535],{"class":66}," \u002Fetc\u002Fnginx\u002Fsites-available\u002Fhardened",[46,1537,1538],{"class":128}," \u003C\u003C",[46,1540,1541],{"class":66},"'EOF'\n",[46,1543,1544],{"class":48,"line":379},[46,1545,1546],{"class":66},"server {\n",[46,1548,1549],{"class":48,"line":387},[46,1550,1551],{"class":66},"    listen 80;\n",[46,1553,1554],{"class":48,"line":409},[46,1555,1556],{"class":66},"    return 301 https:\u002F\u002F$host$request_uri;\n",[46,1558,1559],{"class":48,"line":414},[46,1560,1561],{"class":66},"}\n",[46,1563,1564],{"class":48,"line":419},[46,1565,1546],{"class":66},[46,1567,1568],{"class":48,"line":427},[46,1569,1570],{"class":66},"    listen 443 ssl;\n",[46,1572,1574],{"class":48,"line":1573},35,[46,1575,1576],{"class":66},"    ssl_certificate \u002Fetc\u002Fssl\u002Fcerts\u002Fselfsigned.crt;\n",[46,1578,1580],{"class":48,"line":1579},36,[46,1581,1582],{"class":66},"    ssl_certificate_key \u002Fetc\u002Fssl\u002Fprivate\u002Fselfsigned.key;\n",[46,1584,1586],{"class":48,"line":1585},37,[46,1587,1588],{"class":66},"    add_header Strict-Transport-Security \"max-age=31536000\" always;\n",[46,1590,1592],{"class":48,"line":1591},38,[46,1593,1594],{"class":66},"    add_header X-Frame-Options \"SAMEORIGIN\" always;\n",[46,1596,1598],{"class":48,"line":1597},39,[46,1599,1600],{"class":66},"    add_header X-Content-Type-Options \"nosniff\" always;\n",[46,1602,1604],{"class":48,"line":1603},40,[46,1605,1606],{"class":66},"    location \u002F {\n",[46,1608,1610],{"class":48,"line":1609},41,[46,1611,1612],{"class":66},"        root \u002Fvar\u002Fwww\u002Fhtml;\n",[46,1614,1616],{"class":48,"line":1615},42,[46,1617,1618],{"class":66},"        index index.html;\n",[46,1620,1622],{"class":48,"line":1621},43,[46,1623,1624],{"class":66},"    }\n",[46,1626,1628],{"class":48,"line":1627},44,[46,1629,1561],{"class":66},[46,1631,1633],{"class":48,"line":1632},45,[46,1634,1635],{"class":66},"EOF\n",[46,1637,1639,1641,1644,1647,1649],{"class":48,"line":1638},46,[46,1640,1200],{"class":94},[46,1642,1643],{"class":66}," ln",[46,1645,1646],{"class":59}," -sf",[46,1648,1535],{"class":66},[46,1650,1651],{"class":66}," \u002Fetc\u002Fnginx\u002Fsites-enabled\u002Fhardened\n",[46,1653,1655,1657,1659,1661,1663,1665,1667,1669],{"class":48,"line":1654},47,[46,1656,1200],{"class":94},[46,1658,1304],{"class":66},[46,1660,1412],{"class":59},[46,1662,1291],{"class":526},[46,1664,1200],{"class":94},[46,1666,1419],{"class":66},[46,1668,1422],{"class":66},[46,1670,1671],{"class":66}," nginx\n",[46,1673,1675],{"class":48,"line":1674},48,[46,1676,74],{"emptyLinePlaceholder":73},[46,1678,1680],{"class":48,"line":1679},49,[46,1681,1682],{"class":52},"# 7. Verify\n",[46,1684,1686,1689,1692],{"class":48,"line":1685},50,[46,1687,1688],{"class":94},"curl",[46,1690,1691],{"class":59}," -kI",[46,1693,1694],{"class":66}," https:\u002F\u002Flocalhost\u002F\n",[46,1696,1698,1700,1702,1704,1707,1710],{"class":48,"line":1697},51,[46,1699,430],{"class":94},[46,1701,433],{"class":59},[46,1703,141],{"class":128},[46,1705,1706],{"class":94}," grep",[46,1708,1709],{"class":59}," -E",[46,1711,1712],{"class":66}," ':80|:443'\n",[46,1714,1716,1718,1720],{"class":48,"line":1715},52,[46,1717,1200],{"class":94},[46,1719,1307],{"class":66},[46,1721,1722],{"class":66}," status\n",[14,1724,1725,452],{},[28,1726,451],{},[454,1728,1730,1739,1748,1757],{"className":1729},[457],[459,1731,1733,938,1735,1738],{"className":1732},[462],[464,1734],{"disabled":73,"type":466},[43,1736,1737],{},"curl -kI https:\u002F\u002Flocalhost\u002F"," returns 200.",[459,1740,1742,938,1744,1747],{"className":1741},[462],[464,1743],{"disabled":73,"type":466},[43,1745,1746],{},"ufw status"," shows only 22\u002F80\u002F443.",[459,1749,1751,938,1753,1756],{"className":1750},[462],[464,1752],{"disabled":73,"type":466},[43,1754,1755],{},"fail2ban-client status sshd"," shows the jail active.",[459,1758,1760,1762],{"className":1759},[462],[464,1761],{"disabled":73,"type":466}," HTTP redirects to HTTPS.",[485,1764],{},[18,1766,1768],{"id":1767},"project-5-automated-backup-with-systemd-timers","Project 5 — Automated Backup with systemd Timers",[14,1770,1771],{},"Create a backup system using systemd timers (replacing cron). Covers chapters 09, 11.",[33,1773,1774],{"language":35},[37,1775,1777],{"className":39,"code":1776,"language":35,"meta":41,"style":41},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# \u002Fopt\u002Fbackup\u002Fbackup.sh\nset -euo pipefail\n\nSRC=\"${1:-\u002Fhome}\"\nDST=\"${2:-\u002Fbackup}\"\nKEEP=7\n\nmkdir -p \"$DST\"\ntimestamp=$(date +%Y%m%d_%H%M%S)\narchive=\"$DST\u002Fbackup_${timestamp}.tar.gz\"\n\necho \"Backing up $SRC to $archive\"\ntar -czf \"$archive\" -C \"$(dirname \"$SRC\")\" \"$(basename \"$SRC\")\"\n\n# Verify integrity\nif ! gzip -t \"$archive\"; then\n  echo \"ERROR: Backup $archive is corrupt\" >&2\n  exit 1\nfi\n\n# Rotate old backups\nmapfile -t backups \u003C \u003C(ls -1 \"$DST\"\u002Fbackup_*.tar.gz 2>\u002Fdev\u002Fnull | sort -r)\nif (( ${#backups[@]} > KEEP )); then\n  for old in \"${backups[@]:KEEP}\"; do\n    echo \"Removing old backup: $old\"\n    rm -f \"$old\"\n  done\nfi\n\necho \"Backup complete: $archive ($(du -h \"$archive\" | cut -f1))\"\n",[43,1778,1779,1783,1788,1796,1800,1820,1841,1851,1855,1870,1888,1907,1911,1929,1970,1974,1979,2003,2018,2026,2031,2035,2040,2088,2115,2150,2163,2177,2182,2186,2190],{"__ignoreMap":41},[46,1780,1781],{"class":48,"line":49},[46,1782,53],{"class":52},[46,1784,1785],{"class":48,"line":56},[46,1786,1787],{"class":52},"# \u002Fopt\u002Fbackup\u002Fbackup.sh\n",[46,1789,1790,1792,1794],{"class":48,"line":70},[46,1791,60],{"class":59},[46,1793,63],{"class":59},[46,1795,67],{"class":66},[46,1797,1798],{"class":48,"line":77},[46,1799,74],{"emptyLinePlaceholder":73},[46,1801,1802,1805,1807,1809,1811,1813,1816,1818],{"class":48,"line":86},[46,1803,1804],{"class":526},"SRC",[46,1806,530],{"class":128},[46,1808,533],{"class":66},[46,1810,536],{"class":59},[46,1812,539],{"class":128},[46,1814,1815],{"class":526},"home",[46,1817,566],{"class":59},[46,1819,569],{"class":66},[46,1821,1822,1825,1827,1829,1832,1834,1837,1839],{"class":48,"line":101},[46,1823,1824],{"class":526},"DST",[46,1826,530],{"class":128},[46,1828,533],{"class":66},[46,1830,1831],{"class":59},"${2",[46,1833,539],{"class":128},[46,1835,1836],{"class":526},"backup",[46,1838,566],{"class":59},[46,1840,569],{"class":66},[46,1842,1843,1846,1848],{"class":48,"line":114},[46,1844,1845],{"class":526},"KEEP",[46,1847,530],{"class":128},[46,1849,1850],{"class":66},"7\n",[46,1852,1853],{"class":48,"line":153},[46,1854,74],{"emptyLinePlaceholder":73},[46,1856,1857,1860,1863,1865,1868],{"class":48,"line":169},[46,1858,1859],{"class":94},"mkdir",[46,1861,1862],{"class":59}," -p",[46,1864,580],{"class":66},[46,1866,1867],{"class":526},"$DST",[46,1869,569],{"class":66},[46,1871,1872,1875,1877,1880,1882,1885],{"class":48,"line":184},[46,1873,1874],{"class":526},"timestamp",[46,1876,530],{"class":128},[46,1878,1879],{"class":526},"$(",[46,1881,95],{"class":94},[46,1883,1884],{"class":66}," +%Y%m%d_%H%M%S",[46,1886,1887],{"class":526},")\n",[46,1889,1890,1893,1895,1897,1899,1902,1904],{"class":48,"line":190},[46,1891,1892],{"class":526},"archive",[46,1894,530],{"class":128},[46,1896,533],{"class":66},[46,1898,1867],{"class":526},[46,1900,1901],{"class":66},"\u002Fbackup_${",[46,1903,1874],{"class":526},[46,1905,1906],{"class":66},"}.tar.gz\"\n",[46,1908,1909],{"class":48,"line":195},[46,1910,74],{"emptyLinePlaceholder":73},[46,1912,1913,1915,1918,1921,1924,1927],{"class":48,"line":203},[46,1914,80],{"class":59},[46,1916,1917],{"class":66}," \"Backing up ",[46,1919,1920],{"class":526},"$SRC",[46,1922,1923],{"class":66}," to ",[46,1925,1926],{"class":526},"$archive",[46,1928,569],{"class":66},[46,1930,1931,1934,1937,1939,1941,1943,1946,1949,1952,1954,1956,1959,1961,1964,1966,1968],{"class":48,"line":235},[46,1932,1933],{"class":94},"tar",[46,1935,1936],{"class":59}," -czf",[46,1938,580],{"class":66},[46,1940,1926],{"class":526},[46,1942,533],{"class":66},[46,1944,1945],{"class":59}," -C",[46,1947,1948],{"class":66}," \"$(",[46,1950,1951],{"class":94},"dirname",[46,1953,580],{"class":66},[46,1955,1920],{"class":526},[46,1957,1958],{"class":66},"\")\"",[46,1960,1948],{"class":66},[46,1962,1963],{"class":94},"basename",[46,1965,580],{"class":66},[46,1967,1920],{"class":526},[46,1969,657],{"class":66},[46,1971,1972],{"class":48,"line":248},[46,1973,74],{"emptyLinePlaceholder":73},[46,1975,1976],{"class":48,"line":253},[46,1977,1978],{"class":52},"# Verify integrity\n",[46,1980,1981,1984,1987,1990,1992,1994,1996,1998,2000],{"class":48,"line":258},[46,1982,1983],{"class":128},"if",[46,1985,1986],{"class":128}," !",[46,1988,1989],{"class":94}," gzip",[46,1991,1412],{"class":59},[46,1993,580],{"class":66},[46,1995,1926],{"class":526},[46,1997,533],{"class":66},[46,1999,609],{"class":526},[46,2001,2002],{"class":128},"then\n",[46,2004,2005,2007,2010,2012,2015],{"class":48,"line":266},[46,2006,1138],{"class":59},[46,2008,2009],{"class":66}," \"ERROR: Backup ",[46,2011,1926],{"class":526},[46,2013,2014],{"class":66}," is corrupt\"",[46,2016,2017],{"class":128}," >&2\n",[46,2019,2020,2023],{"class":48,"line":283},[46,2021,2022],{"class":59},"  exit",[46,2024,2025],{"class":59}," 1\n",[46,2027,2028],{"class":48,"line":288},[46,2029,2030],{"class":128},"fi\n",[46,2032,2033],{"class":48,"line":293},[46,2034,74],{"emptyLinePlaceholder":73},[46,2036,2037],{"class":48,"line":301},[46,2038,2039],{"class":52},"# Rotate old backups\n",[46,2041,2042,2045,2047,2050,2052,2055,2058,2061,2063,2065,2068,2071,2074,2077,2080,2082,2084,2086],{"class":48,"line":324},[46,2043,2044],{"class":94},"mapfile",[46,2046,1412],{"class":59},[46,2048,2049],{"class":66}," backups",[46,2051,650],{"class":128},[46,2053,2054],{"class":66}," \u003C(",[46,2056,2057],{"class":94},"ls",[46,2059,2060],{"class":59}," -1",[46,2062,580],{"class":66},[46,2064,1867],{"class":526},[46,2066,2067],{"class":66},"\"\u002Fbackup_",[46,2069,2070],{"class":59},"*",[46,2072,2073],{"class":66},".tar.gz ",[46,2075,2076],{"class":128},"2>",[46,2078,2079],{"class":66},"\u002Fdev\u002Fnull ",[46,2081,129],{"class":128},[46,2083,691],{"class":94},[46,2085,164],{"class":59},[46,2087,1887],{"class":66},[46,2089,2090,2092,2095,2098,2101,2104,2107,2110,2113],{"class":48,"line":329},[46,2091,1983],{"class":128},[46,2093,2094],{"class":526}," (( ${",[46,2096,2097],{"class":128},"#",[46,2099,2100],{"class":526},"backups[",[46,2102,2103],{"class":128},"@",[46,2105,2106],{"class":526},"]} ",[46,2108,2109],{"class":128},">",[46,2111,2112],{"class":526}," KEEP )); ",[46,2114,2002],{"class":128},[46,2116,2117,2120,2123,2125,2128,2131,2134,2136,2139,2141,2143,2146,2148],{"class":48,"line":334},[46,2118,2119],{"class":128},"  for",[46,2121,2122],{"class":526}," old ",[46,2124,1114],{"class":128},[46,2126,2127],{"class":66}," \"${",[46,2129,2130],{"class":526},"backups",[46,2132,2133],{"class":66},"[",[46,2135,2103],{"class":128},[46,2137,2138],{"class":66},"]",[46,2140,452],{"class":128},[46,2142,1845],{"class":526},[46,2144,2145],{"class":66},"}\"",[46,2147,609],{"class":526},[46,2149,1133],{"class":128},[46,2151,2152,2155,2158,2161],{"class":48,"line":342},[46,2153,2154],{"class":59},"    echo",[46,2156,2157],{"class":66}," \"Removing old backup: ",[46,2159,2160],{"class":526},"$old",[46,2162,569],{"class":66},[46,2164,2165,2168,2171,2173,2175],{"class":48,"line":369},[46,2166,2167],{"class":94},"    rm",[46,2169,2170],{"class":59}," -f",[46,2172,580],{"class":66},[46,2174,2160],{"class":526},[46,2176,569],{"class":66},[46,2178,2179],{"class":48,"line":374},[46,2180,2181],{"class":128},"  done\n",[46,2183,2184],{"class":48,"line":379},[46,2185,2030],{"class":128},[46,2187,2188],{"class":48,"line":387},[46,2189,74],{"emptyLinePlaceholder":73},[46,2191,2192,2194,2197,2199,2202,2205,2207,2209,2211,2214,2216,2218,2221],{"class":48,"line":409},[46,2193,80],{"class":59},[46,2195,2196],{"class":66}," \"Backup complete: ",[46,2198,1926],{"class":526},[46,2200,2201],{"class":66}," ($(",[46,2203,2204],{"class":94},"du",[46,2206,272],{"class":59},[46,2208,580],{"class":66},[46,2210,1926],{"class":526},[46,2212,2213],{"class":66},"\" ",[46,2215,129],{"class":128},[46,2217,132],{"class":94},[46,2219,2220],{"class":59}," -f1",[46,2222,2223],{"class":66},"))\"\n",[33,2225,2226],{"language":35},[37,2227,2229],{"className":39,"code":2228,"language":35,"meta":41,"style":41},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service\nsudo tee \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service \u003C\u003C'EOF'\n[Unit]\nDescription=Daily backup\nAfter=network.target\n\n[Service]\nType=oneshot\nExecStart=\u002Fopt\u002Fbackup\u002Fbackup.sh \u002Fhome \u002Fbackup\nNice=19\nIOSchedulingClass=idle\nEOF\n\n# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer\nsudo tee \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer \u003C\u003C'EOF'\n[Unit]\nDescription=Daily backup at 2 AM\n\n[Timer]\nOnCalendar=*-*-* 02:00:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target\nEOF\n\nsudo systemctl daemon-reload\nsudo systemctl enable --now backup.timer\nsystemctl list-timers | grep backup\n",[43,2230,2231,2236,2249,2254,2259,2264,2268,2273,2278,2283,2288,2293,2297,2301,2306,2319,2323,2328,2332,2337,2342,2347,2351,2356,2361,2365,2369,2378,2391],{"__ignoreMap":41},[46,2232,2233],{"class":48,"line":49},[46,2234,2235],{"class":52},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service\n",[46,2237,2238,2240,2242,2245,2247],{"class":48,"line":56},[46,2239,1200],{"class":94},[46,2241,1532],{"class":66},[46,2243,2244],{"class":66}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service",[46,2246,1538],{"class":128},[46,2248,1541],{"class":66},[46,2250,2251],{"class":48,"line":70},[46,2252,2253],{"class":66},"[Unit]\n",[46,2255,2256],{"class":48,"line":77},[46,2257,2258],{"class":66},"Description=Daily backup\n",[46,2260,2261],{"class":48,"line":86},[46,2262,2263],{"class":66},"After=network.target\n",[46,2265,2266],{"class":48,"line":101},[46,2267,74],{"emptyLinePlaceholder":73},[46,2269,2270],{"class":48,"line":114},[46,2271,2272],{"class":66},"[Service]\n",[46,2274,2275],{"class":48,"line":153},[46,2276,2277],{"class":66},"Type=oneshot\n",[46,2279,2280],{"class":48,"line":169},[46,2281,2282],{"class":66},"ExecStart=\u002Fopt\u002Fbackup\u002Fbackup.sh \u002Fhome \u002Fbackup\n",[46,2284,2285],{"class":48,"line":184},[46,2286,2287],{"class":66},"Nice=19\n",[46,2289,2290],{"class":48,"line":190},[46,2291,2292],{"class":66},"IOSchedulingClass=idle\n",[46,2294,2295],{"class":48,"line":195},[46,2296,1635],{"class":66},[46,2298,2299],{"class":48,"line":203},[46,2300,74],{"emptyLinePlaceholder":73},[46,2302,2303],{"class":48,"line":235},[46,2304,2305],{"class":52},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer\n",[46,2307,2308,2310,2312,2315,2317],{"class":48,"line":248},[46,2309,1200],{"class":94},[46,2311,1532],{"class":66},[46,2313,2314],{"class":66}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer",[46,2316,1538],{"class":128},[46,2318,1541],{"class":66},[46,2320,2321],{"class":48,"line":253},[46,2322,2253],{"class":66},[46,2324,2325],{"class":48,"line":258},[46,2326,2327],{"class":66},"Description=Daily backup at 2 AM\n",[46,2329,2330],{"class":48,"line":266},[46,2331,74],{"emptyLinePlaceholder":73},[46,2333,2334],{"class":48,"line":283},[46,2335,2336],{"class":66},"[Timer]\n",[46,2338,2339],{"class":48,"line":288},[46,2340,2341],{"class":66},"OnCalendar=*-*-* 02:00:00\n",[46,2343,2344],{"class":48,"line":293},[46,2345,2346],{"class":66},"Persistent=true\n",[46,2348,2349],{"class":48,"line":301},[46,2350,74],{"emptyLinePlaceholder":73},[46,2352,2353],{"class":48,"line":324},[46,2354,2355],{"class":66},"[Install]\n",[46,2357,2358],{"class":48,"line":329},[46,2359,2360],{"class":66},"WantedBy=timers.target\n",[46,2362,2363],{"class":48,"line":334},[46,2364,1635],{"class":66},[46,2366,2367],{"class":48,"line":342},[46,2368,74],{"emptyLinePlaceholder":73},[46,2370,2371,2373,2375],{"class":48,"line":369},[46,2372,1200],{"class":94},[46,2374,1419],{"class":66},[46,2376,2377],{"class":66}," daemon-reload\n",[46,2379,2380,2382,2384,2386,2388],{"class":48,"line":374},[46,2381,1200],{"class":94},[46,2383,1419],{"class":66},[46,2385,1443],{"class":66},[46,2387,1446],{"class":59},[46,2389,2390],{"class":66}," backup.timer\n",[46,2392,2393,2396,2399,2401,2403],{"class":48,"line":379},[46,2394,2395],{"class":94},"systemctl",[46,2397,2398],{"class":66}," list-timers",[46,2400,141],{"class":128},[46,2402,1706],{"class":94},[46,2404,2405],{"class":66}," backup\n",[14,2407,2408,452],{},[28,2409,451],{},[454,2411,2413,2422,2431,2437],{"className":2412},[457],[459,2414,2416,938,2418,2421],{"className":2415},[462],[464,2417],{"disabled":73,"type":466},[43,2419,2420],{},"systemctl start backup.service"," creates a backup.",[459,2423,2425,938,2427,2430],{"className":2424},[462],[464,2426],{"disabled":73,"type":466},[43,2428,2429],{},"journalctl -u backup.service"," shows log output.",[459,2432,2434,2436],{"className":2433},[462],[464,2435],{"disabled":73,"type":466}," Old backups are rotated (keep 7).",[459,2438,2440,2442,2443,932],{"className":2439},[462],[464,2441],{"disabled":73,"type":466}," Corrupt backup detection works (test with ",[43,2444,2445],{},"gzip -t",[485,2447],{},[18,2449,2451],{"id":2450},"project-6-process-resource-monitor","Project 6 — Process & Resource Monitor",[14,2453,2454],{},"Build a monitoring script that alerts on resource thresholds. Covers chapters 06, 15, 12.",[33,2456,2457],{"language":35},[37,2458,2460],{"className":39,"code":2459,"language":35,"meta":41,"style":41},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# \u002Fopt\u002Fmonitor\u002Fmonitor.sh\nset -euo pipefail\n\nCPU_THRESHOLD=80\nDISK_THRESHOLD=90\nMEM_THRESHOLD=90\nCRITICAL_PROCS=(\"sshd\" \"nginx\")\n\nwhile true; do\n  # CPU (load avg \u002F cores * 100)\n  load=$(awk '{print $1}' \u002Fproc\u002Floadavg)\n  cores=$(nproc)\n  cpu_pct=$(awk \"BEGIN{printf \\\"%.0f\\\", ($load \u002F $cores) * 100}\")\n  if (( cpu_pct > CPU_THRESHOLD )); then\n    logger -t monitor -p user.warn \"CPU load high: ${cpu_pct}% (load $load, $cores cores)\"\n  fi\n\n  # Disk\n  while read -r line; do\n    usage=$(echo \"$line\" | awk '{print $5}' | tr -d '%')\n    part=$(echo \"$line\" | awk '{print $6}')\n    if (( usage > DISK_THRESHOLD )); then\n      logger -t monitor -p user.warn \"Disk $part at ${usage}%\"\n    fi\n  done \u003C \u003C(df -h | awk 'NR>1 && \u002F^\\\u002Fdev\u002F')\n\n  # Memory\n  mem_avail=$(awk '\u002FMemAvailable\u002F {print $2}' \u002Fproc\u002Fmeminfo)\n  mem_total=$(awk '\u002FMemTotal\u002F {print $2}' \u002Fproc\u002Fmeminfo)\n  mem_pct=$(awk \"BEGIN{printf \\\"%.0f\\\", (1 - $mem_avail\u002F$mem_total) * 100}\")\n  if (( mem_pct > MEM_THRESHOLD )); then\n    logger -t monitor -p user.warn \"Memory usage high: ${mem_pct}%\"\n  fi\n\n  # Critical processes\n  for proc in \"${CRITICAL_PROCS[@]}\"; do\n    if ! pgrep -x \"$proc\" >\u002Fdev\u002Fnull; then\n      logger -t monitor -p user.err \"Critical process $proc is NOT running!\"\n    fi\n  done\n\n  sleep 60\ndone\n",[43,2461,2462,2466,2471,2479,2483,2493,2503,2512,2530,2534,2546,2551,2569,2582,2621,2636,2670,2675,2679,2684,2701,2737,2763,2778,2806,2811,2831,2835,2840,2859,2877,2911,2925,2945,2949,2953,2958,2982,3010,3031,3035,3039,3043,3051],{"__ignoreMap":41},[46,2463,2464],{"class":48,"line":49},[46,2465,53],{"class":52},[46,2467,2468],{"class":48,"line":56},[46,2469,2470],{"class":52},"# \u002Fopt\u002Fmonitor\u002Fmonitor.sh\n",[46,2472,2473,2475,2477],{"class":48,"line":70},[46,2474,60],{"class":59},[46,2476,63],{"class":59},[46,2478,67],{"class":66},[46,2480,2481],{"class":48,"line":77},[46,2482,74],{"emptyLinePlaceholder":73},[46,2484,2485,2488,2490],{"class":48,"line":86},[46,2486,2487],{"class":526},"CPU_THRESHOLD",[46,2489,530],{"class":128},[46,2491,2492],{"class":66},"80\n",[46,2494,2495,2498,2500],{"class":48,"line":101},[46,2496,2497],{"class":526},"DISK_THRESHOLD",[46,2499,530],{"class":128},[46,2501,2502],{"class":66},"90\n",[46,2504,2505,2508,2510],{"class":48,"line":114},[46,2506,2507],{"class":526},"MEM_THRESHOLD",[46,2509,530],{"class":128},[46,2511,2502],{"class":66},[46,2513,2514,2517,2519,2522,2525,2528],{"class":48,"line":153},[46,2515,2516],{"class":526},"CRITICAL_PROCS",[46,2518,530],{"class":128},[46,2520,2521],{"class":526},"(",[46,2523,2524],{"class":66},"\"sshd\"",[46,2526,2527],{"class":66}," \"nginx\"",[46,2529,1887],{"class":526},[46,2531,2532],{"class":48,"line":169},[46,2533,74],{"emptyLinePlaceholder":73},[46,2535,2536,2539,2542,2544],{"class":48,"line":184},[46,2537,2538],{"class":128},"while",[46,2540,2541],{"class":59}," true",[46,2543,609],{"class":526},[46,2545,1133],{"class":128},[46,2547,2548],{"class":48,"line":190},[46,2549,2550],{"class":52},"  # CPU (load avg \u002F cores * 100)\n",[46,2552,2553,2556,2558,2560,2562,2564,2567],{"class":48,"line":195},[46,2554,2555],{"class":526},"  load",[46,2557,530],{"class":128},[46,2559,1879],{"class":526},[46,2561,677],{"class":94},[46,2563,680],{"class":66},[46,2565,2566],{"class":66}," \u002Fproc\u002Floadavg",[46,2568,1887],{"class":526},[46,2570,2571,2574,2576,2578,2580],{"class":48,"line":203},[46,2572,2573],{"class":526},"  cores",[46,2575,530],{"class":128},[46,2577,1879],{"class":526},[46,2579,243],{"class":94},[46,2581,1887],{"class":526},[46,2583,2584,2587,2589,2591,2593,2596,2599,2602,2604,2607,2610,2613,2616,2619],{"class":48,"line":235},[46,2585,2586],{"class":526},"  cpu_pct",[46,2588,530],{"class":128},[46,2590,1879],{"class":526},[46,2592,677],{"class":94},[46,2594,2595],{"class":66}," \"BEGIN{printf ",[46,2597,2598],{"class":59},"\\\"",[46,2600,2601],{"class":66},"%.0f",[46,2603,2598],{"class":59},[46,2605,2606],{"class":66},", (",[46,2608,2609],{"class":526},"$load",[46,2611,2612],{"class":66}," \u002F ",[46,2614,2615],{"class":526},"$cores",[46,2617,2618],{"class":66},") * 100}\"",[46,2620,1887],{"class":526},[46,2622,2623,2626,2629,2631,2634],{"class":48,"line":248},[46,2624,2625],{"class":128},"  if",[46,2627,2628],{"class":526}," (( cpu_pct ",[46,2630,2109],{"class":128},[46,2632,2633],{"class":526}," CPU_THRESHOLD )); ",[46,2635,2002],{"class":128},[46,2637,2638,2641,2643,2646,2648,2651,2654,2657,2660,2662,2665,2667],{"class":48,"line":253},[46,2639,2640],{"class":94},"    logger",[46,2642,1412],{"class":59},[46,2644,2645],{"class":66}," monitor",[46,2647,1862],{"class":59},[46,2649,2650],{"class":66}," user.warn",[46,2652,2653],{"class":66}," \"CPU load high: ${",[46,2655,2656],{"class":526},"cpu_pct",[46,2658,2659],{"class":66},"}% (load ",[46,2661,2609],{"class":526},[46,2663,2664],{"class":66},", ",[46,2666,2615],{"class":526},[46,2668,2669],{"class":66}," cores)\"\n",[46,2671,2672],{"class":48,"line":258},[46,2673,2674],{"class":128},"  fi\n",[46,2676,2677],{"class":48,"line":266},[46,2678,74],{"emptyLinePlaceholder":73},[46,2680,2681],{"class":48,"line":283},[46,2682,2683],{"class":52},"  # Disk\n",[46,2685,2686,2689,2692,2694,2697,2699],{"class":48,"line":288},[46,2687,2688],{"class":128},"  while",[46,2690,2691],{"class":59}," read",[46,2693,164],{"class":59},[46,2695,2696],{"class":66}," line",[46,2698,609],{"class":526},[46,2700,1133],{"class":128},[46,2702,2703,2706,2708,2710,2712,2714,2717,2719,2721,2723,2726,2728,2730,2732,2735],{"class":48,"line":293},[46,2704,2705],{"class":526},"    usage",[46,2707,530],{"class":128},[46,2709,1879],{"class":526},[46,2711,80],{"class":59},[46,2713,580],{"class":66},[46,2715,2716],{"class":526},"$line",[46,2718,533],{"class":66},[46,2720,141],{"class":128},[46,2722,277],{"class":94},[46,2724,2725],{"class":66}," '{print $5}'",[46,2727,141],{"class":128},[46,2729,144],{"class":94},[46,2731,147],{"class":59},[46,2733,2734],{"class":66}," '%'",[46,2736,1887],{"class":526},[46,2738,2739,2742,2744,2746,2748,2750,2752,2754,2756,2758,2761],{"class":48,"line":301},[46,2740,2741],{"class":526},"    part",[46,2743,530],{"class":128},[46,2745,1879],{"class":526},[46,2747,80],{"class":59},[46,2749,580],{"class":66},[46,2751,2716],{"class":526},[46,2753,533],{"class":66},[46,2755,141],{"class":128},[46,2757,277],{"class":94},[46,2759,2760],{"class":66}," '{print $6}'",[46,2762,1887],{"class":526},[46,2764,2765,2768,2771,2773,2776],{"class":48,"line":324},[46,2766,2767],{"class":128},"    if",[46,2769,2770],{"class":526}," (( usage ",[46,2772,2109],{"class":128},[46,2774,2775],{"class":526}," DISK_THRESHOLD )); ",[46,2777,2002],{"class":128},[46,2779,2780,2783,2785,2787,2789,2791,2794,2797,2800,2803],{"class":48,"line":329},[46,2781,2782],{"class":94},"      logger",[46,2784,1412],{"class":59},[46,2786,2645],{"class":66},[46,2788,1862],{"class":59},[46,2790,2650],{"class":66},[46,2792,2793],{"class":66}," \"Disk ",[46,2795,2796],{"class":526},"$part",[46,2798,2799],{"class":66}," at ${",[46,2801,2802],{"class":526},"usage",[46,2804,2805],{"class":66},"}%\"\n",[46,2807,2808],{"class":48,"line":334},[46,2809,2810],{"class":128},"    fi\n",[46,2812,2813,2816,2818,2820,2822,2824,2826,2828],{"class":48,"line":342},[46,2814,2815],{"class":128},"  done",[46,2817,650],{"class":128},[46,2819,2054],{"class":66},[46,2821,304],{"class":94},[46,2823,272],{"class":59},[46,2825,141],{"class":128},[46,2827,277],{"class":94},[46,2829,2830],{"class":66}," 'NR>1 && \u002F^\\\u002Fdev\u002F')\n",[46,2832,2833],{"class":48,"line":369},[46,2834,74],{"emptyLinePlaceholder":73},[46,2836,2837],{"class":48,"line":374},[46,2838,2839],{"class":52},"  # Memory\n",[46,2841,2842,2845,2847,2849,2851,2854,2857],{"class":48,"line":379},[46,2843,2844],{"class":526},"  mem_avail",[46,2846,530],{"class":128},[46,2848,1879],{"class":526},[46,2850,677],{"class":94},[46,2852,2853],{"class":66}," '\u002FMemAvailable\u002F {print $2}'",[46,2855,2856],{"class":66}," \u002Fproc\u002Fmeminfo",[46,2858,1887],{"class":526},[46,2860,2861,2864,2866,2868,2870,2873,2875],{"class":48,"line":387},[46,2862,2863],{"class":526},"  mem_total",[46,2865,530],{"class":128},[46,2867,1879],{"class":526},[46,2869,677],{"class":94},[46,2871,2872],{"class":66}," '\u002FMemTotal\u002F {print $2}'",[46,2874,2856],{"class":66},[46,2876,1887],{"class":526},[46,2878,2879,2882,2884,2886,2888,2890,2892,2894,2896,2899,2902,2904,2907,2909],{"class":48,"line":409},[46,2880,2881],{"class":526},"  mem_pct",[46,2883,530],{"class":128},[46,2885,1879],{"class":526},[46,2887,677],{"class":94},[46,2889,2595],{"class":66},[46,2891,2598],{"class":59},[46,2893,2601],{"class":66},[46,2895,2598],{"class":59},[46,2897,2898],{"class":66},", (1 - ",[46,2900,2901],{"class":526},"$mem_avail",[46,2903,545],{"class":66},[46,2905,2906],{"class":526},"$mem_total",[46,2908,2618],{"class":66},[46,2910,1887],{"class":526},[46,2912,2913,2915,2918,2920,2923],{"class":48,"line":414},[46,2914,2625],{"class":128},[46,2916,2917],{"class":526}," (( mem_pct ",[46,2919,2109],{"class":128},[46,2921,2922],{"class":526}," MEM_THRESHOLD )); ",[46,2924,2002],{"class":128},[46,2926,2927,2929,2931,2933,2935,2937,2940,2943],{"class":48,"line":419},[46,2928,2640],{"class":94},[46,2930,1412],{"class":59},[46,2932,2645],{"class":66},[46,2934,1862],{"class":59},[46,2936,2650],{"class":66},[46,2938,2939],{"class":66}," \"Memory usage high: ${",[46,2941,2942],{"class":526},"mem_pct",[46,2944,2805],{"class":66},[46,2946,2947],{"class":48,"line":427},[46,2948,2674],{"class":128},[46,2950,2951],{"class":48,"line":1573},[46,2952,74],{"emptyLinePlaceholder":73},[46,2954,2955],{"class":48,"line":1579},[46,2956,2957],{"class":52},"  # Critical processes\n",[46,2959,2960,2962,2965,2967,2969,2971,2973,2975,2978,2980],{"class":48,"line":1585},[46,2961,2119],{"class":128},[46,2963,2964],{"class":526}," proc ",[46,2966,1114],{"class":128},[46,2968,2127],{"class":66},[46,2970,2516],{"class":526},[46,2972,2133],{"class":66},[46,2974,2103],{"class":128},[46,2976,2977],{"class":66},"]}\"",[46,2979,609],{"class":526},[46,2981,1133],{"class":128},[46,2983,2984,2986,2988,2991,2994,2996,2999,3001,3004,3006,3008],{"class":48,"line":1591},[46,2985,2767],{"class":128},[46,2987,1986],{"class":128},[46,2989,2990],{"class":94}," pgrep",[46,2992,2993],{"class":59}," -x",[46,2995,580],{"class":66},[46,2997,2998],{"class":526},"$proc",[46,3000,533],{"class":66},[46,3002,3003],{"class":128}," >",[46,3005,1077],{"class":66},[46,3007,609],{"class":526},[46,3009,2002],{"class":128},[46,3011,3012,3014,3016,3018,3020,3023,3026,3028],{"class":48,"line":1597},[46,3013,2782],{"class":94},[46,3015,1412],{"class":59},[46,3017,2645],{"class":66},[46,3019,1862],{"class":59},[46,3021,3022],{"class":66}," user.err",[46,3024,3025],{"class":66}," \"Critical process ",[46,3027,2998],{"class":526},[46,3029,3030],{"class":66}," is NOT running!\"\n",[46,3032,3033],{"class":48,"line":1603},[46,3034,2810],{"class":128},[46,3036,3037],{"class":48,"line":1609},[46,3038,2181],{"class":128},[46,3040,3041],{"class":48,"line":1615},[46,3042,74],{"emptyLinePlaceholder":73},[46,3044,3045,3048],{"class":48,"line":1621},[46,3046,3047],{"class":94},"  sleep",[46,3049,3050],{"class":59}," 60\n",[46,3052,3053],{"class":48,"line":1627},[46,3054,1180],{"class":128},[33,3056,3057],{"language":35},[37,3058,3060],{"className":39,"code":3059,"language":35,"meta":41,"style":41},"sudo tee \u002Fetc\u002Fsystemd\u002Fsystem\u002Fmonitor.service \u003C\u003C'EOF'\n[Unit]\nDescription=Resource Monitor\nAfter=network.target\n\n[Service]\nExecStart=\u002Fopt\u002Fmonitor\u002Fmonitor.sh\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsudo systemctl daemon-reload\nsudo systemctl enable --now monitor\njournalctl -t monitor -f\n",[43,3061,3062,3075,3079,3084,3088,3092,3096,3101,3106,3111,3115,3119,3124,3128,3132,3140,3153],{"__ignoreMap":41},[46,3063,3064,3066,3068,3071,3073],{"class":48,"line":49},[46,3065,1200],{"class":94},[46,3067,1532],{"class":66},[46,3069,3070],{"class":66}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fmonitor.service",[46,3072,1538],{"class":128},[46,3074,1541],{"class":66},[46,3076,3077],{"class":48,"line":56},[46,3078,2253],{"class":66},[46,3080,3081],{"class":48,"line":70},[46,3082,3083],{"class":66},"Description=Resource Monitor\n",[46,3085,3086],{"class":48,"line":77},[46,3087,2263],{"class":66},[46,3089,3090],{"class":48,"line":86},[46,3091,74],{"emptyLinePlaceholder":73},[46,3093,3094],{"class":48,"line":101},[46,3095,2272],{"class":66},[46,3097,3098],{"class":48,"line":114},[46,3099,3100],{"class":66},"ExecStart=\u002Fopt\u002Fmonitor\u002Fmonitor.sh\n",[46,3102,3103],{"class":48,"line":153},[46,3104,3105],{"class":66},"Restart=always\n",[46,3107,3108],{"class":48,"line":169},[46,3109,3110],{"class":66},"RestartSec=10\n",[46,3112,3113],{"class":48,"line":184},[46,3114,74],{"emptyLinePlaceholder":73},[46,3116,3117],{"class":48,"line":190},[46,3118,2355],{"class":66},[46,3120,3121],{"class":48,"line":195},[46,3122,3123],{"class":66},"WantedBy=multi-user.target\n",[46,3125,3126],{"class":48,"line":203},[46,3127,1635],{"class":66},[46,3129,3130],{"class":48,"line":235},[46,3131,74],{"emptyLinePlaceholder":73},[46,3133,3134,3136,3138],{"class":48,"line":248},[46,3135,1200],{"class":94},[46,3137,1419],{"class":66},[46,3139,2377],{"class":66},[46,3141,3142,3144,3146,3148,3150],{"class":48,"line":253},[46,3143,1200],{"class":94},[46,3145,1419],{"class":66},[46,3147,1443],{"class":66},[46,3149,1446],{"class":59},[46,3151,3152],{"class":66}," monitor\n",[46,3154,3155,3158,3160,3162],{"class":48,"line":258},[46,3156,3157],{"class":94},"journalctl",[46,3159,1412],{"class":59},[46,3161,2645],{"class":66},[46,3163,3164],{"class":59}," -f\n",[14,3166,3167,452],{},[28,3168,451],{},[454,3170,3172,3182,3188],{"className":3171},[457],[459,3173,3175,3177,3178,3181],{"className":3174},[462],[464,3176],{"disabled":73,"type":466}," High load (use ",[43,3179,3180],{},"stress --cpu $(nproc)",") triggers a log entry.",[459,3183,3185,3187],{"className":3184},[462],[464,3186],{"disabled":73,"type":466}," Stopping nginx triggers \"not running\" alert.",[459,3189,3191,938,3193,3196],{"className":3190},[462],[464,3192],{"disabled":73,"type":466},[43,3194,3195],{},"journalctl -t monitor"," shows all alerts.",[485,3198],{},[18,3200,3202],{"id":3201},"project-7-lvm-raid-storage-setup","Project 7 — LVM + RAID Storage Setup",[14,3204,3205],{},"Configure a resilient storage setup. Covers chapter 09.",[33,3207,3208],{"language":35},[37,3209,3211],{"className":39,"code":3210,"language":35,"meta":41,"style":41},"#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail\n\n# 1. Create RAID 1\nsudo mdadm --create \u002Fdev\u002Fmd0 --level=1 --raid-devices=2 \u002Fdev\u002Fsdb \u002Fdev\u002Fsdc\ncat \u002Fproc\u002Fmdstat\n\n# 2. LVM on the RAID\nsudo pvcreate \u002Fdev\u002Fmd0\nsudo vgcreate vg_data \u002Fdev\u002Fmd0\nsudo lvcreate -L 50G -n lv_data vg_data\n\n# 3. Filesystem + mount\nsudo mkfs.ext4 \u002Fdev\u002Fvg_data\u002Flv_data\nsudo mkdir -p \u002Fmnt\u002Fdata\necho \"\u002Fdev\u002Fvg_data\u002Flv_data \u002Fmnt\u002Fdata ext4 defaults,noatime 0 2\" | sudo tee -a \u002Fetc\u002Ffstab\nsudo mount -a\n\n# 4. Snapshot + backup\nsudo lvcreate -L 5G -s -n lv_data_snap \u002Fdev\u002Fvg_data\u002Flv_data\nsudo mount -o ro \u002Fdev\u002Fvg_data\u002Flv_data_snap \u002Fmnt\u002Fsnap\ntar -czf \u002Ftmp\u002Fdata-backup.tar.gz -C \u002Fmnt\u002Fsnap .\nsudo umount \u002Fmnt\u002Fsnap\nsudo lvremove -f \u002Fdev\u002Fvg_data\u002Flv_data_snap\n\n# 5. Simulate disk failure\nsudo mdadm --fail \u002Fdev\u002Fmd0 \u002Fdev\u002Fsdb\nsudo mdadm --remove \u002Fdev\u002Fmd0 \u002Fdev\u002Fsdb\ncat \u002Fproc\u002Fmdstat                    # shows degraded\n\n# Save RAID config\nsudo mdadm --detail --scan | sudo tee -a \u002Fetc\u002Fmdadm\u002Fmdadm.conf\nsudo update-initramfs -u\n",[43,3212,3213,3217,3225,3229,3234,3259,3267,3271,3276,3286,3298,3320,3324,3329,3339,3351,3370,3380,3384,3389,3410,3428,3445,3454,3466,3470,3475,3489,3502,3512,3516,3521,3544],{"__ignoreMap":41},[46,3214,3215],{"class":48,"line":49},[46,3216,53],{"class":52},[46,3218,3219,3221,3223],{"class":48,"line":56},[46,3220,60],{"class":59},[46,3222,63],{"class":59},[46,3224,67],{"class":66},[46,3226,3227],{"class":48,"line":70},[46,3228,74],{"emptyLinePlaceholder":73},[46,3230,3231],{"class":48,"line":77},[46,3232,3233],{"class":52},"# 1. Create RAID 1\n",[46,3235,3236,3238,3241,3244,3247,3250,3253,3256],{"class":48,"line":86},[46,3237,1200],{"class":94},[46,3239,3240],{"class":66}," mdadm",[46,3242,3243],{"class":59}," --create",[46,3245,3246],{"class":66}," \u002Fdev\u002Fmd0",[46,3248,3249],{"class":59}," --level=1",[46,3251,3252],{"class":59}," --raid-devices=2",[46,3254,3255],{"class":66}," \u002Fdev\u002Fsdb",[46,3257,3258],{"class":66}," \u002Fdev\u002Fsdc\n",[46,3260,3261,3264],{"class":48,"line":101},[46,3262,3263],{"class":94},"cat",[46,3265,3266],{"class":66}," \u002Fproc\u002Fmdstat\n",[46,3268,3269],{"class":48,"line":114},[46,3270,74],{"emptyLinePlaceholder":73},[46,3272,3273],{"class":48,"line":153},[46,3274,3275],{"class":52},"# 2. LVM on the RAID\n",[46,3277,3278,3280,3283],{"class":48,"line":169},[46,3279,1200],{"class":94},[46,3281,3282],{"class":66}," pvcreate",[46,3284,3285],{"class":66}," \u002Fdev\u002Fmd0\n",[46,3287,3288,3290,3293,3296],{"class":48,"line":184},[46,3289,1200],{"class":94},[46,3291,3292],{"class":66}," vgcreate",[46,3294,3295],{"class":66}," vg_data",[46,3297,3285],{"class":66},[46,3299,3300,3302,3305,3308,3311,3314,3317],{"class":48,"line":190},[46,3301,1200],{"class":94},[46,3303,3304],{"class":66}," lvcreate",[46,3306,3307],{"class":59}," -L",[46,3309,3310],{"class":66}," 50G",[46,3312,3313],{"class":59}," -n",[46,3315,3316],{"class":66}," lv_data",[46,3318,3319],{"class":66}," vg_data\n",[46,3321,3322],{"class":48,"line":195},[46,3323,74],{"emptyLinePlaceholder":73},[46,3325,3326],{"class":48,"line":203},[46,3327,3328],{"class":52},"# 3. Filesystem + mount\n",[46,3330,3331,3333,3336],{"class":48,"line":235},[46,3332,1200],{"class":94},[46,3334,3335],{"class":66}," mkfs.ext4",[46,3337,3338],{"class":66}," \u002Fdev\u002Fvg_data\u002Flv_data\n",[46,3340,3341,3343,3346,3348],{"class":48,"line":248},[46,3342,1200],{"class":94},[46,3344,3345],{"class":66}," mkdir",[46,3347,1862],{"class":59},[46,3349,3350],{"class":66}," \u002Fmnt\u002Fdata\n",[46,3352,3353,3355,3358,3360,3362,3364,3367],{"class":48,"line":253},[46,3354,80],{"class":59},[46,3356,3357],{"class":66}," \"\u002Fdev\u002Fvg_data\u002Flv_data \u002Fmnt\u002Fdata ext4 defaults,noatime 0 2\"",[46,3359,141],{"class":128},[46,3361,1071],{"class":94},[46,3363,1532],{"class":66},[46,3365,3366],{"class":59}," -a",[46,3368,3369],{"class":66}," \u002Fetc\u002Ffstab\n",[46,3371,3372,3374,3377],{"class":48,"line":258},[46,3373,1200],{"class":94},[46,3375,3376],{"class":66}," mount",[46,3378,3379],{"class":59}," -a\n",[46,3381,3382],{"class":48,"line":266},[46,3383,74],{"emptyLinePlaceholder":73},[46,3385,3386],{"class":48,"line":283},[46,3387,3388],{"class":52},"# 4. Snapshot + backup\n",[46,3390,3391,3393,3395,3397,3400,3403,3405,3408],{"class":48,"line":288},[46,3392,1200],{"class":94},[46,3394,3304],{"class":66},[46,3396,3307],{"class":59},[46,3398,3399],{"class":66}," 5G",[46,3401,3402],{"class":59}," -s",[46,3404,3313],{"class":59},[46,3406,3407],{"class":66}," lv_data_snap",[46,3409,3338],{"class":66},[46,3411,3412,3414,3416,3419,3422,3425],{"class":48,"line":293},[46,3413,1200],{"class":94},[46,3415,3376],{"class":66},[46,3417,3418],{"class":59}," -o",[46,3420,3421],{"class":66}," ro",[46,3423,3424],{"class":66}," \u002Fdev\u002Fvg_data\u002Flv_data_snap",[46,3426,3427],{"class":66}," \u002Fmnt\u002Fsnap\n",[46,3429,3430,3432,3434,3437,3439,3442],{"class":48,"line":301},[46,3431,1933],{"class":94},[46,3433,1936],{"class":59},[46,3435,3436],{"class":66}," \u002Ftmp\u002Fdata-backup.tar.gz",[46,3438,1945],{"class":59},[46,3440,3441],{"class":66}," \u002Fmnt\u002Fsnap",[46,3443,3444],{"class":66}," .\n",[46,3446,3447,3449,3452],{"class":48,"line":324},[46,3448,1200],{"class":94},[46,3450,3451],{"class":66}," umount",[46,3453,3427],{"class":66},[46,3455,3456,3458,3461,3463],{"class":48,"line":329},[46,3457,1200],{"class":94},[46,3459,3460],{"class":66}," lvremove",[46,3462,2170],{"class":59},[46,3464,3465],{"class":66}," \u002Fdev\u002Fvg_data\u002Flv_data_snap\n",[46,3467,3468],{"class":48,"line":334},[46,3469,74],{"emptyLinePlaceholder":73},[46,3471,3472],{"class":48,"line":342},[46,3473,3474],{"class":52},"# 5. Simulate disk failure\n",[46,3476,3477,3479,3481,3484,3486],{"class":48,"line":369},[46,3478,1200],{"class":94},[46,3480,3240],{"class":66},[46,3482,3483],{"class":59}," --fail",[46,3485,3246],{"class":66},[46,3487,3488],{"class":66}," \u002Fdev\u002Fsdb\n",[46,3490,3491,3493,3495,3498,3500],{"class":48,"line":374},[46,3492,1200],{"class":94},[46,3494,3240],{"class":66},[46,3496,3497],{"class":59}," --remove",[46,3499,3246],{"class":66},[46,3501,3488],{"class":66},[46,3503,3504,3506,3509],{"class":48,"line":379},[46,3505,3263],{"class":94},[46,3507,3508],{"class":66}," \u002Fproc\u002Fmdstat",[46,3510,3511],{"class":52},"                    # shows degraded\n",[46,3513,3514],{"class":48,"line":387},[46,3515,74],{"emptyLinePlaceholder":73},[46,3517,3518],{"class":48,"line":409},[46,3519,3520],{"class":52},"# Save RAID config\n",[46,3522,3523,3525,3527,3530,3533,3535,3537,3539,3541],{"class":48,"line":414},[46,3524,1200],{"class":94},[46,3526,3240],{"class":66},[46,3528,3529],{"class":59}," --detail",[46,3531,3532],{"class":59}," --scan",[46,3534,141],{"class":128},[46,3536,1071],{"class":94},[46,3538,1532],{"class":66},[46,3540,3366],{"class":59},[46,3542,3543],{"class":66}," \u002Fetc\u002Fmdadm\u002Fmdadm.conf\n",[46,3545,3546,3548,3551],{"class":48,"line":419},[46,3547,1200],{"class":94},[46,3549,3550],{"class":66}," update-initramfs",[46,3552,3553],{"class":59}," -u\n",[14,3555,3556,452],{},[28,3557,451],{},[454,3559,3561,3570,3579,3585],{"className":3560},[457],[459,3562,3564,938,3566,3569],{"className":3563},[462],[464,3565],{"disabled":73,"type":466},[43,3567,3568],{},"lsblk"," shows the RAID + LVM stack.",[459,3571,3573,938,3575,3578],{"className":3572},[462],[464,3574],{"disabled":73,"type":466},[43,3576,3577],{},"cat \u002Fproc\u002Fmdstat"," shows the RAID status.",[459,3580,3582,3584],{"className":3581},[462],[464,3583],{"disabled":73,"type":466}," Snapshot was created and removed cleanly.",[459,3586,3588,938,3590,3593],{"className":3587},[462],[464,3589],{"disabled":73,"type":466},[43,3591,3592],{},"mdadm --fail"," showed the array going degraded.",[485,3595],{},[18,3597,3599],{"id":3598},"project-8-containerized-app-stack","Project 8 — Containerized App Stack",[14,3601,3602],{},"Deploy a multi-container app stack. Covers chapter 17.",[33,3604,3607],{"language":3605,"filename":3606},"yaml","docker-compose.yml",[37,3608,3611],{"className":3609,"code":3610,"language":3605,"meta":41,"style":41},"language-yaml shiki shiki-themes github-light github-dark","version: \"3.8\"\n\nservices:\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_DB: myapp\n      POSTGRES_USER: appuser\n      POSTGRES_PASSWORD: secret\n    volumes:\n      - dbdata:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n    networks:\n      - appnet\n    healthcheck:\n      test: [\"CMD\", \"pg_isready\", \"-U\", \"appuser\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    deploy:\n      resources:\n        limits:\n          memory: 512M\n          cpus: \"1.0\"\n    restart: unless-stopped\n\n  backend:\n    build: .\u002Fbackend\n    environment:\n      DATABASE_URL: postgres:\u002F\u002Fappuser:secret@db:5432\u002Fmyapp\n    depends_on:\n      db:\n        condition: service_healthy\n    networks:\n      - appnet\n    deploy:\n      resources:\n        limits:\n          memory: 256M\n          cpus: \"0.5\"\n    restart: unless-stopped\n    user: \"1000:1000\"\n\n  web:\n    image: nginx:1.27\n    ports:\n      - \"8080:80\"\n    volumes:\n      - .\u002Fnginx.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n    depends_on:\n      - backend\n    networks:\n      - appnet\n    restart: unless-stopped\n\nvolumes:\n  dbdata:\n\nnetworks:\n  appnet:\n",[43,3612,3613,3625,3629,3637,3644,3654,3661,3671,3681,3691,3698,3706,3713,3720,3727,3756,3766,3776,3786,3793,3800,3807,3817,3827,3837,3841,3848,3858,3864,3874,3881,3888,3898,3904,3910,3916,3922,3928,3937,3946,3954,3964,3968,3975,3984,3991,3998,4004,4011,4017,4024,4030,4036,4045,4050,4058,4066,4071,4079],{"__ignoreMap":41},[46,3614,3615,3619,3622],{"class":48,"line":49},[46,3616,3618],{"class":3617},"sk71V","version",[46,3620,3621],{"class":526},": ",[46,3623,3624],{"class":66},"\"3.8\"\n",[46,3626,3627],{"class":48,"line":56},[46,3628,74],{"emptyLinePlaceholder":73},[46,3630,3631,3634],{"class":48,"line":70},[46,3632,3633],{"class":3617},"services",[46,3635,3636],{"class":526},":\n",[46,3638,3639,3642],{"class":48,"line":77},[46,3640,3641],{"class":3617},"  db",[46,3643,3636],{"class":526},[46,3645,3646,3649,3651],{"class":48,"line":86},[46,3647,3648],{"class":3617},"    image",[46,3650,3621],{"class":526},[46,3652,3653],{"class":66},"postgres:16\n",[46,3655,3656,3659],{"class":48,"line":101},[46,3657,3658],{"class":3617},"    environment",[46,3660,3636],{"class":526},[46,3662,3663,3666,3668],{"class":48,"line":114},[46,3664,3665],{"class":3617},"      POSTGRES_DB",[46,3667,3621],{"class":526},[46,3669,3670],{"class":66},"myapp\n",[46,3672,3673,3676,3678],{"class":48,"line":153},[46,3674,3675],{"class":3617},"      POSTGRES_USER",[46,3677,3621],{"class":526},[46,3679,3680],{"class":66},"appuser\n",[46,3682,3683,3686,3688],{"class":48,"line":169},[46,3684,3685],{"class":3617},"      POSTGRES_PASSWORD",[46,3687,3621],{"class":526},[46,3689,3690],{"class":66},"secret\n",[46,3692,3693,3696],{"class":48,"line":184},[46,3694,3695],{"class":3617},"    volumes",[46,3697,3636],{"class":526},[46,3699,3700,3703],{"class":48,"line":190},[46,3701,3702],{"class":526},"      - ",[46,3704,3705],{"class":66},"dbdata:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[46,3707,3708,3711],{"class":48,"line":195},[46,3709,3710],{"class":3617},"    networks",[46,3712,3636],{"class":526},[46,3714,3715,3717],{"class":48,"line":203},[46,3716,3702],{"class":526},[46,3718,3719],{"class":66},"appnet\n",[46,3721,3722,3725],{"class":48,"line":235},[46,3723,3724],{"class":3617},"    healthcheck",[46,3726,3636],{"class":526},[46,3728,3729,3732,3735,3738,3740,3743,3745,3748,3750,3753],{"class":48,"line":248},[46,3730,3731],{"class":3617},"      test",[46,3733,3734],{"class":526},": [",[46,3736,3737],{"class":66},"\"CMD\"",[46,3739,2664],{"class":526},[46,3741,3742],{"class":66},"\"pg_isready\"",[46,3744,2664],{"class":526},[46,3746,3747],{"class":66},"\"-U\"",[46,3749,2664],{"class":526},[46,3751,3752],{"class":66},"\"appuser\"",[46,3754,3755],{"class":526},"]\n",[46,3757,3758,3761,3763],{"class":48,"line":253},[46,3759,3760],{"class":3617},"      interval",[46,3762,3621],{"class":526},[46,3764,3765],{"class":66},"10s\n",[46,3767,3768,3771,3773],{"class":48,"line":258},[46,3769,3770],{"class":3617},"      timeout",[46,3772,3621],{"class":526},[46,3774,3775],{"class":66},"5s\n",[46,3777,3778,3781,3783],{"class":48,"line":266},[46,3779,3780],{"class":3617},"      retries",[46,3782,3621],{"class":526},[46,3784,3785],{"class":59},"5\n",[46,3787,3788,3791],{"class":48,"line":283},[46,3789,3790],{"class":3617},"    deploy",[46,3792,3636],{"class":526},[46,3794,3795,3798],{"class":48,"line":288},[46,3796,3797],{"class":3617},"      resources",[46,3799,3636],{"class":526},[46,3801,3802,3805],{"class":48,"line":293},[46,3803,3804],{"class":3617},"        limits",[46,3806,3636],{"class":526},[46,3808,3809,3812,3814],{"class":48,"line":301},[46,3810,3811],{"class":3617},"          memory",[46,3813,3621],{"class":526},[46,3815,3816],{"class":66},"512M\n",[46,3818,3819,3822,3824],{"class":48,"line":324},[46,3820,3821],{"class":3617},"          cpus",[46,3823,3621],{"class":526},[46,3825,3826],{"class":66},"\"1.0\"\n",[46,3828,3829,3832,3834],{"class":48,"line":329},[46,3830,3831],{"class":3617},"    restart",[46,3833,3621],{"class":526},[46,3835,3836],{"class":66},"unless-stopped\n",[46,3838,3839],{"class":48,"line":334},[46,3840,74],{"emptyLinePlaceholder":73},[46,3842,3843,3846],{"class":48,"line":342},[46,3844,3845],{"class":3617},"  backend",[46,3847,3636],{"class":526},[46,3849,3850,3853,3855],{"class":48,"line":369},[46,3851,3852],{"class":3617},"    build",[46,3854,3621],{"class":526},[46,3856,3857],{"class":66},".\u002Fbackend\n",[46,3859,3860,3862],{"class":48,"line":374},[46,3861,3658],{"class":3617},[46,3863,3636],{"class":526},[46,3865,3866,3869,3871],{"class":48,"line":379},[46,3867,3868],{"class":3617},"      DATABASE_URL",[46,3870,3621],{"class":526},[46,3872,3873],{"class":66},"postgres:\u002F\u002Fappuser:secret@db:5432\u002Fmyapp\n",[46,3875,3876,3879],{"class":48,"line":387},[46,3877,3878],{"class":3617},"    depends_on",[46,3880,3636],{"class":526},[46,3882,3883,3886],{"class":48,"line":409},[46,3884,3885],{"class":3617},"      db",[46,3887,3636],{"class":526},[46,3889,3890,3893,3895],{"class":48,"line":414},[46,3891,3892],{"class":3617},"        condition",[46,3894,3621],{"class":526},[46,3896,3897],{"class":66},"service_healthy\n",[46,3899,3900,3902],{"class":48,"line":419},[46,3901,3710],{"class":3617},[46,3903,3636],{"class":526},[46,3905,3906,3908],{"class":48,"line":427},[46,3907,3702],{"class":526},[46,3909,3719],{"class":66},[46,3911,3912,3914],{"class":48,"line":1573},[46,3913,3790],{"class":3617},[46,3915,3636],{"class":526},[46,3917,3918,3920],{"class":48,"line":1579},[46,3919,3797],{"class":3617},[46,3921,3636],{"class":526},[46,3923,3924,3926],{"class":48,"line":1585},[46,3925,3804],{"class":3617},[46,3927,3636],{"class":526},[46,3929,3930,3932,3934],{"class":48,"line":1591},[46,3931,3811],{"class":3617},[46,3933,3621],{"class":526},[46,3935,3936],{"class":66},"256M\n",[46,3938,3939,3941,3943],{"class":48,"line":1597},[46,3940,3821],{"class":3617},[46,3942,3621],{"class":526},[46,3944,3945],{"class":66},"\"0.5\"\n",[46,3947,3948,3950,3952],{"class":48,"line":1603},[46,3949,3831],{"class":3617},[46,3951,3621],{"class":526},[46,3953,3836],{"class":66},[46,3955,3956,3959,3961],{"class":48,"line":1609},[46,3957,3958],{"class":3617},"    user",[46,3960,3621],{"class":526},[46,3962,3963],{"class":66},"\"1000:1000\"\n",[46,3965,3966],{"class":48,"line":1615},[46,3967,74],{"emptyLinePlaceholder":73},[46,3969,3970,3973],{"class":48,"line":1621},[46,3971,3972],{"class":3617},"  web",[46,3974,3636],{"class":526},[46,3976,3977,3979,3981],{"class":48,"line":1627},[46,3978,3648],{"class":3617},[46,3980,3621],{"class":526},[46,3982,3983],{"class":66},"nginx:1.27\n",[46,3985,3986,3989],{"class":48,"line":1632},[46,3987,3988],{"class":3617},"    ports",[46,3990,3636],{"class":526},[46,3992,3993,3995],{"class":48,"line":1638},[46,3994,3702],{"class":526},[46,3996,3997],{"class":66},"\"8080:80\"\n",[46,3999,4000,4002],{"class":48,"line":1654},[46,4001,3695],{"class":3617},[46,4003,3636],{"class":526},[46,4005,4006,4008],{"class":48,"line":1674},[46,4007,3702],{"class":526},[46,4009,4010],{"class":66},".\u002Fnginx.conf:\u002Fetc\u002Fnginx\u002Fconf.d\u002Fdefault.conf:ro\n",[46,4012,4013,4015],{"class":48,"line":1679},[46,4014,3878],{"class":3617},[46,4016,3636],{"class":526},[46,4018,4019,4021],{"class":48,"line":1685},[46,4020,3702],{"class":526},[46,4022,4023],{"class":66},"backend\n",[46,4025,4026,4028],{"class":48,"line":1697},[46,4027,3710],{"class":3617},[46,4029,3636],{"class":526},[46,4031,4032,4034],{"class":48,"line":1715},[46,4033,3702],{"class":526},[46,4035,3719],{"class":66},[46,4037,4039,4041,4043],{"class":48,"line":4038},53,[46,4040,3831],{"class":3617},[46,4042,3621],{"class":526},[46,4044,3836],{"class":66},[46,4046,4048],{"class":48,"line":4047},54,[46,4049,74],{"emptyLinePlaceholder":73},[46,4051,4053,4056],{"class":48,"line":4052},55,[46,4054,4055],{"class":3617},"volumes",[46,4057,3636],{"class":526},[46,4059,4061,4064],{"class":48,"line":4060},56,[46,4062,4063],{"class":3617},"  dbdata",[46,4065,3636],{"class":526},[46,4067,4069],{"class":48,"line":4068},57,[46,4070,74],{"emptyLinePlaceholder":73},[46,4072,4074,4077],{"class":48,"line":4073},58,[46,4075,4076],{"class":3617},"networks",[46,4078,3636],{"class":526},[46,4080,4082,4085],{"class":48,"line":4081},59,[46,4083,4084],{"class":3617},"  appnet",[46,4086,3636],{"class":526},[33,4088,4089],{"language":35},[37,4090,4092],{"className":39,"code":4091,"language":35,"meta":41,"style":41},"docker compose up -d\ndocker compose ps\ndocker compose logs -f\ndocker compose down           # stop + remove\ndocker compose down -v        # also remove volumes (data!)\n",[43,4093,4094,4108,4117,4128,4140],{"__ignoreMap":41},[46,4095,4096,4099,4102,4105],{"class":48,"line":49},[46,4097,4098],{"class":94},"docker",[46,4100,4101],{"class":66}," compose",[46,4103,4104],{"class":66}," up",[46,4106,4107],{"class":59}," -d\n",[46,4109,4110,4112,4114],{"class":48,"line":56},[46,4111,4098],{"class":94},[46,4113,4101],{"class":66},[46,4115,4116],{"class":66}," ps\n",[46,4118,4119,4121,4123,4126],{"class":48,"line":70},[46,4120,4098],{"class":94},[46,4122,4101],{"class":66},[46,4124,4125],{"class":66}," logs",[46,4127,3164],{"class":59},[46,4129,4130,4132,4134,4137],{"class":48,"line":77},[46,4131,4098],{"class":94},[46,4133,4101],{"class":66},[46,4135,4136],{"class":66}," down",[46,4138,4139],{"class":52},"           # stop + remove\n",[46,4141,4142,4144,4146,4148,4151],{"class":48,"line":86},[46,4143,4098],{"class":94},[46,4145,4101],{"class":66},[46,4147,4136],{"class":66},[46,4149,4150],{"class":59}," -v",[46,4152,4153],{"class":52},"        # also remove volumes (data!)\n",[14,4155,4156,452],{},[28,4157,451],{},[454,4159,4161,4170,4179,4189],{"className":4160},[457],[459,4162,4164,938,4166,4169],{"className":4163},[462],[464,4165],{"disabled":73,"type":466},[43,4167,4168],{},"docker compose ps"," shows all services healthy.",[459,4171,4173,938,4175,4178],{"className":4172},[462],[464,4174],{"disabled":73,"type":466},[43,4176,4177],{},"curl http:\u002F\u002Flocalhost:8080"," returns the app.",[459,4180,4182,4184,4185,4188],{"className":4181},[462],[464,4183],{"disabled":73,"type":466}," Backend can reach ",[43,4186,4187],{},"db:5432"," (DNS resolution works).",[459,4190,4192,4194,4195,4198,4199,4202],{"className":4191},[462],[464,4193],{"disabled":73,"type":466}," Data persists across ",[43,4196,4197],{},"docker compose down"," + ",[43,4200,4201],{},"up"," (volume).",[485,4204],{},[18,4206,4208],{"id":4207},"capstone-build-a-production-linux-server","Capstone — Build a Production Linux Server",[14,4210,4211],{},"Combine everything into a fully configured production server.",[14,4213,4214,452],{},[28,4215,30],{},[454,4217,4218,4224,4230,4236,4242,4255,4261,4267,4273,4291,4297],{},[459,4219,4220,4223],{},[28,4221,4222],{},"Base system",": fresh install, fully updated.",[459,4225,4226,4229],{},[28,4227,4228],{},"Users",": one admin user with sudo (key-only SSH), one service account.",[459,4231,4232,4235],{},[28,4233,4234],{},"Hardening",": UFW (22\u002F80\u002F443), SSH hardened, fail2ban, SELinux\u002FAppArmor enforcing.",[459,4237,4238,4241],{},[28,4239,4240],{},"Services"," (systemd): nginx (web), PostgreSQL (database), a custom app.",[459,4243,4244,4247,4248,4251,4252,561],{},[28,4245,4246],{},"Storage",": LVM for ",[43,4249,4250],{},"\u002Fdata",", ext4 with ",[43,4253,4254],{},"noatime",[459,4256,4257,4260],{},[28,4258,4259],{},"Backups",": systemd timer daily, 7-day retention, integrity verified.",[459,4262,4263,4266],{},[28,4264,4265],{},"Monitoring",": resource monitor (Project 6), logs to journald + forwarded to a central server.",[459,4268,4269,4272],{},[28,4270,4271],{},"Logging",": journald persistent, logrotate configured.",[459,4274,4275,3621,4278,2664,4281,4283,4284,545,4287,4290],{},[28,4276,4277],{},"Performance",[43,4279,4280],{},"swappiness=10",[43,4282,4254],{}," mounts, ",[43,4285,4286],{},"LimitNOFILE",[43,4288,4289],{},"MemoryMax"," on the app.",[459,4292,4293,4296],{},[28,4294,4295],{},"Containers",": the custom app runs in a container (rootless via Podman), managed by systemd.",[459,4298,4299,4302],{},[28,4300,4301],{},"Documentation",": a README documenting the setup, recovery procedures, and credential locations.",[14,4304,4305,452],{},[28,4306,4307],{},"Verification (full checklist)",[454,4309,4311,4323,4330,4342,4350,4363,4372,4381,4390,4403,4411,4420,4429,4438],{"className":4310},[457],[459,4312,4314,938,4316,2612,4319,4322],{"className":4313},[462],[464,4315],{"disabled":73,"type":466},[43,4317,4318],{},"apt upgrade",[43,4320,4321],{},"dnf upgrade"," shows nothing to update.",[459,4324,4326,938,4328,1747],{"className":4325},[462],[464,4327],{"disabled":73,"type":466},[43,4329,1746],{},[459,4331,4333,938,4335,4338,4339,561],{"className":4332},[462],[464,4334],{"disabled":73,"type":466},[43,4336,4337],{},"sshd -T | grep permitrootlogin"," shows ",[43,4340,4341],{},"no",[459,4343,4345,938,4347,4349],{"className":4344},[462],[464,4346],{"disabled":73,"type":466},[43,4348,1755],{}," is active.",[459,4351,4353,938,4355,4358,4359,4362],{"className":4352},[462],[464,4354],{"disabled":73,"type":466},[43,4356,4357],{},"getenforce"," (RHEL) shows ",[43,4360,4361],{},"Enforcing"," or AppArmor profiles active.",[459,4364,4366,938,4368,4371],{"className":4365},[462],[464,4367],{"disabled":73,"type":466},[43,4369,4370],{},"systemctl list-units --state=failed"," shows nothing.",[459,4373,4375,938,4377,4380],{"className":4374},[462],[464,4376],{"disabled":73,"type":466},[43,4378,4379],{},"journalctl --disk-usage"," is within limits.",[459,4382,4384,938,4386,4389],{"className":4383},[462],[464,4385],{"disabled":73,"type":466},[43,4387,4388],{},"df -h"," shows adequate free space.",[459,4391,4393,938,4395,4398,4399,4402],{"className":4392},[462],[464,4394],{"disabled":73,"type":466},[43,4396,4397],{},"docker ps"," (or ",[43,4400,4401],{},"podman ps",") shows the app container running.",[459,4404,4406,938,4408,1738],{"className":4405},[462],[464,4407],{"disabled":73,"type":466},[43,4409,4410],{},"curl -I https:\u002F\u002Flocalhost",[459,4412,4414,4416,4417,932],{"className":4413},[462],[464,4415],{"disabled":73,"type":466}," Backup timer is active (",[43,4418,4419],{},"systemctl list-timers | grep backup",[459,4421,4423,4425,4426,932],{"className":4422},[462],[464,4424],{"disabled":73,"type":466}," Monitor is running (",[43,4427,4428],{},"systemctl status monitor",[459,4430,4432,4434,4435,932],{"className":4431},[462],[464,4433],{"disabled":73,"type":466}," Logs are persistent (",[43,4436,4437],{},"ls \u002Fvar\u002Flog\u002Fjournal\u002F",[459,4439,4441,938,4443,4446],{"className":4440},[462],[464,4442],{"disabled":73,"type":466},[43,4444,4445],{},"sysctl vm.swappiness"," shows 10.",[18,4448,4450],{"id":4449},"tips-tricks","💡 Tips & Tricks",[454,4452,4453,4467,4484,4503,4513],{},[459,4454,4455,4458,4459,2664,4461,4463,4464,4466],{},[28,4456,4457],{},"Idiom",": every project's verification checklist doubles as a runbook — when Project 4's hardened server goes down at 3am, ",[43,4460,1737],{},[43,4462,1746],{},", and ",[43,4465,1755],{}," are the fastest first three checks, in that order, because they narrow \"network,\" \"firewall,\" and \"auth\" independently.",[459,4468,4469,3621,4472,4474,4475,4478,4479,4483],{},[28,4470,4471],{},"Debug",[43,4473,4370],{}," (used in the Capstone) is the single fastest way to see everything currently broken on a box — run it before diving into individual ",[43,4476,4477],{},"journalctl -u"," calls, since it tells you ",[4480,4481,4482],"em",{},"what"," to investigate instead of guessing.",[459,4485,4486,4488,4489,4492,4493,545,4496,4498,4499,4502],{},[28,4487,4277],{},": Project 6's monitor script computes CPU from ",[43,4490,4491],{},"\u002Fproc\u002Floadavg"," rather than parsing ",[43,4494,4495],{},"top",[43,4497,345],{}," output — reading ",[43,4500,4501],{},"\u002Fproc"," directly avoids spawning a subprocess per sample, which matters when the check loop runs every 60 seconds indefinitely as a long-lived systemd service.",[459,4504,4505,4508,4509,4512],{},[28,4506,4507],{},"Safety",": Project 5's ",[43,4510,4511],{},"gzip -t \"$archive\""," integrity check after every backup is the difference between \"we have backups\" and \"we have backups that restore\" — untested backups are a liability that looks identical to a working backup strategy until the day you need it.",[459,4514,4515,4517,4518,4521,4522,4524,4525,4524,4528,4531],{},[28,4516,4457],{},": Project 7's LVM snapshot-then-backup-then-remove pattern (",[43,4519,4520],{},"lvcreate -s"," → mount read-only → ",[43,4523,1933],{}," → ",[43,4526,4527],{},"umount",[43,4529,4530],{},"lvremove",") lets you back up a live, mounted filesystem in a crash-consistent state without stopping the service using it — the snapshot freezes a point-in-time view via copy-on-write.",[18,4533,4535],{"id":4534},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[454,4537,4538,4562,4576,4588,4604],{},[459,4539,4540,3621,4546,4549,4550,545,4552,4554,4555,4558,4559,4561],{},[28,4541,4542,4545],{},[43,4543,4544],{},"awk \"BEGIN{printf ...}\""," in Project 6 embeds shell variables directly into the AWK program string",[43,4547,4548],{},"cpu_pct=$(awk \"BEGIN{printf ... ($load \u002F $cores) * 100}\")"," interpolates ",[43,4551,2609],{},[43,4553,2615],{}," via bash ",[4480,4556,4557],{},"before"," AWK ever sees the program — if ",[43,4560,4491],{}," ever produced a non-numeric or empty value (e.g., during a container's very first tick before the file is populated), the resulting AWK program is syntactically broken and errors instead of failing gracefully with a sentinel value.",[459,4563,4564,4571,4572,4575],{},[28,4565,4566,4567,4570],{},"Project 4 disables ",[43,4568,4569],{},"PasswordAuthentication"," before confirming key-based login actually works",": the script comments \"verify key auth first!\" for a reason — running the ",[43,4573,4574],{},"sed"," hardening steps over an SSH session that itself relies on password auth, without a second confirmed key-based session open, is how you lock yourself out of a remote box with no console access.",[459,4577,4578,4584,4585,4587],{},[28,4579,4580,4583],{},[43,4581,4582],{},"ss -tlnp"," (Projects 1 and 4) requires root to show the owning process",": run unprivileged, the ",[43,4586,14],{}," (process) column comes back empty rather than erroring — a report generated by a non-root cron job will silently omit the process-per-port mapping without any visible failure.",[459,4589,4590,4599,4600,4603],{},[28,4591,4592,4593,2612,4595,4598],{},"Project 7's RAID failure simulation (",[43,4594,3592],{},[43,4596,4597],{},"--remove",") is destructive and irreversible on that disk",": this is meant for a lab\u002Ftest VM only — running it against ",[43,4601,4602],{},"\u002Fdev\u002Fsdb"," on a system where that device also holds unrelated data (common when experimenting on a box that isn't dedicated to the exercise) destroys that data with no confirmation prompt.",[459,4605,4606,3621,4617,4620,4621,4624,4625,4627,4628,4631,4632,4635],{},[28,4607,4608,4609,4612,4613,4616],{},"Docker Compose's ",[43,4610,4611],{},"depends_on: condition: service_healthy"," (Project 8) only gates container ",[4480,4614,4615],{},"start order",", not application readiness inside the dependent container",[43,4618,4619],{},"backend"," won't start until ",[43,4622,4623],{},"db","'s healthcheck passes, but if ",[43,4626,4619],{}," itself opens its DB connection before its own app server is ready to accept traffic, ",[43,4629,4630],{},"web","'s ",[43,4633,4634],{},"depends_on: - backend"," (a plain dependency, no healthcheck) can still route traffic to a backend that isn't actually serving yet.",[18,4637,4639],{"id":4638},"quick-quiz","🧠 Quick Quiz",[14,4641,4642],{},"Project 6's monitor loop logs a disk-usage warning like this:",[33,4644,4645],{"language":35},[37,4646,4648],{"className":39,"code":4647,"language":35,"meta":41,"style":41},"while read -r line; do\n  usage=$(echo \"$line\" | awk '{print $5}' | tr -d '%')\n  part=$(echo \"$line\" | awk '{print $6}')\n  if (( usage > DISK_THRESHOLD )); then\n    logger -t monitor -p user.warn \"Disk $part at ${usage}%\"\n  fi\ndone \u003C \u003C(df -h | awk 'NR>1 && \u002F^\\\u002Fdev\u002F')\n",[43,4649,4650,4664,4697,4722,4734,4756,4760],{"__ignoreMap":41},[46,4651,4652,4654,4656,4658,4660,4662],{"class":48,"line":49},[46,4653,2538],{"class":128},[46,4655,2691],{"class":59},[46,4657,164],{"class":59},[46,4659,2696],{"class":66},[46,4661,609],{"class":526},[46,4663,1133],{"class":128},[46,4665,4666,4669,4671,4673,4675,4677,4679,4681,4683,4685,4687,4689,4691,4693,4695],{"class":48,"line":56},[46,4667,4668],{"class":526},"  usage",[46,4670,530],{"class":128},[46,4672,1879],{"class":526},[46,4674,80],{"class":59},[46,4676,580],{"class":66},[46,4678,2716],{"class":526},[46,4680,533],{"class":66},[46,4682,141],{"class":128},[46,4684,277],{"class":94},[46,4686,2725],{"class":66},[46,4688,141],{"class":128},[46,4690,144],{"class":94},[46,4692,147],{"class":59},[46,4694,2734],{"class":66},[46,4696,1887],{"class":526},[46,4698,4699,4702,4704,4706,4708,4710,4712,4714,4716,4718,4720],{"class":48,"line":70},[46,4700,4701],{"class":526},"  part",[46,4703,530],{"class":128},[46,4705,1879],{"class":526},[46,4707,80],{"class":59},[46,4709,580],{"class":66},[46,4711,2716],{"class":526},[46,4713,533],{"class":66},[46,4715,141],{"class":128},[46,4717,277],{"class":94},[46,4719,2760],{"class":66},[46,4721,1887],{"class":526},[46,4723,4724,4726,4728,4730,4732],{"class":48,"line":77},[46,4725,2625],{"class":128},[46,4727,2770],{"class":526},[46,4729,2109],{"class":128},[46,4731,2775],{"class":526},[46,4733,2002],{"class":128},[46,4735,4736,4738,4740,4742,4744,4746,4748,4750,4752,4754],{"class":48,"line":86},[46,4737,2640],{"class":94},[46,4739,1412],{"class":59},[46,4741,2645],{"class":66},[46,4743,1862],{"class":59},[46,4745,2650],{"class":66},[46,4747,2793],{"class":66},[46,4749,2796],{"class":526},[46,4751,2799],{"class":66},[46,4753,2802],{"class":526},[46,4755,2805],{"class":66},[46,4757,4758],{"class":48,"line":101},[46,4759,2674],{"class":128},[46,4761,4762,4765,4767,4769,4771,4773,4775,4777],{"class":48,"line":114},[46,4763,4764],{"class":128},"done",[46,4766,650],{"class":128},[46,4768,2054],{"class":66},[46,4770,304],{"class":94},[46,4772,272],{"class":59},[46,4774,141],{"class":128},[46,4776,277],{"class":94},[46,4778,2830],{"class":66},[14,4780,4781,4782,4785,4786,2664,4788,4791,4792,4795],{},"A teammate suggests simplifying it to ",[43,4783,4784],{},"df -h | awk 'NR>1 && \u002F^\\\u002Fdev\u002F' | while read -r line; do ... done"," (a plain pipe instead of process substitution) to make it \"more readable.\" What breaks if any of the loop body's variables (",[43,4787,2802],{},[43,4789,4790],{},"part",") were meant to be visible ",[4480,4793,4794],{},"after"," the loop ends, and why does the process-substitution version avoid that problem?",[4797,4798,4799,4803],"details",{},[4800,4801,4802],"summary",{},"Answer",[14,4804,4805,4806,4809,4810,4812,4813,4815,4816,4818,4819,4822,4823,4825,4826,4829,4830,545,4832,4834,4835,4838],{},"A plain pipe (",[43,4807,4808],{},"cmd | while read ...",") runs the ",[43,4811,2538],{}," loop in a subshell, because every command in a pipeline gets its own subshell in bash — any variables set inside the loop (including ",[43,4814,2802],{}," and ",[43,4817,4790],{},") vanish when the loop exits, since they only existed in the child subshell's memory. The process-substitution form (",[43,4820,4821],{},"done \u003C \u003C(df -h | ...)",") keeps the ",[43,4824,2538],{}," loop in the ",[4480,4827,4828],{},"current"," shell — only the ",[43,4831,304],{},[43,4833,677],{}," producer runs in a subshell (via ",[43,4836,4837],{},"\u003C(...)","), so variables assigned inside the loop persist afterward. Project 6 doesn't rely on this (each iteration is self-contained), but it's the exact reason this idiom — not a plain pipe — appears throughout this course whenever a loop needs to accumulate state (a running total, an array built line-by-line) across iterations.",[4840,4841,4842],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":41,"searchDepth":56,"depth":56,"links":4844},[4845,4846,4847,4848,4849,4850,4851,4852,4853,4854,4855,4856],{"id":20,"depth":56,"text":21},{"id":489,"depth":56,"text":490},{"id":957,"depth":56,"text":958},{"id":1246,"depth":56,"text":1247},{"id":1767,"depth":56,"text":1768},{"id":2450,"depth":56,"text":2451},{"id":3201,"depth":56,"text":3202},{"id":3598,"depth":56,"text":3599},{"id":4207,"depth":56,"text":4208},{"id":4449,"depth":56,"text":4450},{"id":4534,"depth":56,"text":4535},{"id":4638,"depth":56,"text":4639},"md",{},"\u002Flinux\u002F19-exercises-and-projects",{"title":5,"description":16},"linux\u002F19-exercises-and-projects","9FjEMEijOP4OU7ONDCQCzJ-itQv1Du2dbqE3oOp6_ZQ",1789924650167]