[{"data":1,"prerenderedAt":8917},["ShallowReactive",2],{"i-lucide:sun-moon":3,"i-lucide:menu":8,"blog-all-for-tags":10,"i-lucide:arrow-left":8915},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 2v2m2.837 12.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715M16 12a4 4 0 0 0-4-4m7-3l-1.256 1.256M20 12h2\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 5h16M4 12h16M4 19h16\"\u002F>",[11,590,977,1790,1984,2408,2656,2803,2944,3136,3359,3928,4650,5074,5492,6286,6667,7187,7542,7939,8512],{"id":12,"title":13,"authors":14,"body":17,"date":576,"description":23,"extension":577,"meta":578,"navigation":480,"path":581,"seo":582,"stem":583,"tags":584,"toc":6,"__hash__":589},"blog\u002Fblog\u002F2026-01-16-installing-debian-trixie-wsl-docker.md","Installing Debian Trixie in WSL Using Docker",[15,16],"gpt","pfarmer",{"type":18,"value":19,"toc":564},"minimark",[20,24,29,41,45,48,58,62,65,128,131,135,138,157,164,167,181,184,188,191,274,281,285,288,312,315,319,322,330,333,374,377,399,403,406,437,444,448,451,538,542,545,560],[21,22,23],"p",{},"Windows Subsystem for Linux (WSL) provides an excellent way to run Linux distributions on Windows, but it has a limitation: you can only install one instance of each distribution from the Microsoft Store. If you're already running a WSL distribution and want a fresh, separate Debian environment — perhaps for testing the upcoming Trixie release — you'll need a workaround. The solution is to use Docker to export a clean Debian rootfs and import it as a new WSL instance.",[25,26,28],"h2",{"id":27},"why-this-approach","Why This Approach?",[21,30,31,32,36,37,40],{},"While you could clone your existing distro with ",[33,34,35],"code",{},"wsl --export"," and ",[33,38,39],{},"--import",", that gives you a copy rather than a fresh start. The Docker method provides a minimal, clean Debian installation that's completely independent of your existing setup.",[25,42,44],{"id":43},"prerequisites","Prerequisites",[21,46,47],{},"Before you begin, ensure you have the following:",[49,50,51,55],"ul",{},[52,53,54],"li",{},"Windows 10\u002F11 with WSL2 enabled",[52,56,57],{},"Docker Desktop installed and running",[25,59,61],{"id":60},"creating-the-rootfs-using-docker","Creating the Rootfs Using Docker",[21,63,64],{},"Open PowerShell and run the following commands to create a minimal Debian Trixie filesystem:",[66,67,72],"pre",{"className":68,"code":69,"language":70,"meta":71,"style":71},"language-powershell shiki shiki-themes github-light github-dark-default","docker run --name debian-temp debian:trixie true\ndocker export debian-temp -o debian-trixie.tar\ndocker rm debian-temp\n","powershell","",[33,73,74,96,117],{"__ignoreMap":71},[75,76,79,83,87,90,93],"span",{"class":77,"line":78},"line",1,[75,80,82],{"class":81},"szmH1","docker run ",[75,84,86],{"class":85},"sDgYj","--",[75,88,89],{"class":81},"name debian",[75,91,92],{"class":85},"-",[75,94,95],{"class":81},"temp debian:trixie true\n",[75,97,99,102,104,107,109,112,114],{"class":77,"line":98},2,[75,100,101],{"class":81},"docker export debian",[75,103,92],{"class":85},[75,105,106],{"class":81},"temp ",[75,108,92],{"class":85},[75,110,111],{"class":81},"o debian",[75,113,92],{"class":85},[75,115,116],{"class":81},"trixie.tar\n",[75,118,120,123,125],{"class":77,"line":119},3,[75,121,122],{"class":81},"docker rm debian",[75,124,92],{"class":85},[75,126,127],{"class":81},"temp\n",[21,129,130],{},"This creates a container from the official Debian Trixie image, exports its filesystem to a tar file, and cleans up the temporary container.",[25,132,134],{"id":133},"importing-into-wsl","Importing into WSL",[21,136,137],{},"Now import the tar file as a new WSL distribution:",[66,139,141],{"className":68,"code":140,"language":70,"meta":71,"style":71},"wsl --import Trixie C:\\WSL\\Trixie debian-trixie.tar\n",[33,142,143],{"__ignoreMap":71},[75,144,145,148,150,153,155],{"class":77,"line":78},[75,146,147],{"class":81},"wsl ",[75,149,86],{"class":85},[75,151,152],{"class":81},"import Trixie C:\\WSL\\Trixie debian",[75,154,92],{"class":85},[75,156,116],{"class":81},[21,158,159,160,163],{},"The second argument is where WSL will store the virtual disk for this distribution. Choose a location that makes sense for your setup — ",[33,161,162],{},"C:\\WSL\\"," keeps things organised, but you could use any path.",[21,165,166],{},"Test that it works:",[66,168,170],{"className":68,"code":169,"language":70,"meta":71,"style":71},"wsl -d Trixie\n",[33,171,172],{"__ignoreMap":71},[75,173,174,176,178],{"class":77,"line":78},[75,175,147],{"class":81},[75,177,92],{"class":85},[75,179,180],{"class":81},"d Trixie\n",[21,182,183],{},"You should be dropped into a root shell.",[25,185,187],{"id":186},"creating-a-user-account","Creating a User Account",[21,189,190],{},"The imported image only has a root user. To create a regular user account:",[192,193,194,236],"ol",{},[52,195,196,197],{},"Update the package lists and install sudo:",[66,198,202],{"className":199,"code":200,"language":201,"meta":71,"style":71},"language-bash shiki shiki-themes github-light github-dark-default","apt update && apt upgrade -y\napt install sudo\n","bash",[33,203,204,226],{"__ignoreMap":71},[75,205,206,210,214,217,219,222],{"class":77,"line":78},[75,207,209],{"class":208},"sYRkA","apt",[75,211,213],{"class":212},"sNbzA"," update",[75,215,216],{"class":81}," && ",[75,218,209],{"class":208},[75,220,221],{"class":212}," upgrade",[75,223,225],{"class":224},"sPgVT"," -y\n",[75,227,228,230,233],{"class":77,"line":98},[75,229,209],{"class":208},[75,231,232],{"class":212}," install",[75,234,235],{"class":212}," sudo\n",[52,237,238,239],{},"Create a new user with sudo privileges:",[66,240,242],{"className":199,"code":241,"language":201,"meta":71,"style":71},"useradd -m -G sudo -s \u002Fbin\u002Fbash yourusername\npasswd yourusername\n",[33,243,244,267],{"__ignoreMap":71},[75,245,246,249,252,255,258,261,264],{"class":77,"line":78},[75,247,248],{"class":208},"useradd",[75,250,251],{"class":224}," -m",[75,253,254],{"class":224}," -G",[75,256,257],{"class":212}," sudo",[75,259,260],{"class":224}," -s",[75,262,263],{"class":212}," \u002Fbin\u002Fbash",[75,265,266],{"class":212}," yourusername\n",[75,268,269,272],{"class":77,"line":98},[75,270,271],{"class":208},"passwd",[75,273,266],{"class":212},[21,275,276,277,280],{},"Replace ",[33,278,279],{},"yourusername"," with your preferred username.",[25,282,284],{"id":283},"setting-the-default-user","Setting the Default User",[21,286,287],{},"Exit the WSL session and run this from PowerShell:",[66,289,291],{"className":68,"code":290,"language":70,"meta":71,"style":71},"wsl --manage Trixie --set-default-user yourusername\n",[33,292,293],{"__ignoreMap":71},[75,294,295,297,299,302,304,307,309],{"class":77,"line":78},[75,296,147],{"class":81},[75,298,86],{"class":85},[75,300,301],{"class":81},"manage Trixie ",[75,303,86],{"class":85},[75,305,306],{"class":224},"set-default",[75,308,92],{"class":85},[75,310,311],{"class":81},"user yourusername\n",[21,313,314],{},"Now when you start Trixie, you'll log in as your user rather than root.",[25,316,318],{"id":317},"fixing-locale-warnings","Fixing Locale Warnings",[21,320,321],{},"The minimal Docker image doesn't include generated locales, so you'll likely see warnings like:",[66,323,328],{"className":324,"code":326,"language":327},[325],"language-text","bash: warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF-8)\n","text",[33,329,326],{"__ignoreMap":71},[21,331,332],{},"Fix this by installing and configuring locales:",[66,334,336],{"className":199,"code":335,"language":201,"meta":71,"style":71},"sudo apt install locales\nsudo sed -i '\u002Fen_US.UTF-8\u002Fs\u002F^# \u002F\u002Fg' \u002Fetc\u002Flocale.gen\nsudo locale-gen\n",[33,337,338,351,367],{"__ignoreMap":71},[75,339,340,343,346,348],{"class":77,"line":78},[75,341,342],{"class":208},"sudo",[75,344,345],{"class":212}," apt",[75,347,232],{"class":212},[75,349,350],{"class":212}," locales\n",[75,352,353,355,358,361,364],{"class":77,"line":98},[75,354,342],{"class":208},[75,356,357],{"class":212}," sed",[75,359,360],{"class":224}," -i",[75,362,363],{"class":212}," '\u002Fen_US.UTF-8\u002Fs\u002F^# \u002F\u002Fg'",[75,365,366],{"class":212}," \u002Fetc\u002Flocale.gen\n",[75,368,369,371],{"class":77,"line":119},[75,370,342],{"class":208},[75,372,373],{"class":212}," locale-gen\n",[21,375,376],{},"Restart the distro for the changes to take effect:",[66,378,380],{"className":68,"code":379,"language":70,"meta":71,"style":71},"wsl --terminate Trixie\nwsl -d Trixie\n",[33,381,382,391],{"__ignoreMap":71},[75,383,384,386,388],{"class":77,"line":78},[75,385,147],{"class":81},[75,387,86],{"class":85},[75,389,390],{"class":81},"terminate Trixie\n",[75,392,393,395,397],{"class":77,"line":98},[75,394,147],{"class":81},[75,396,92],{"class":85},[75,398,180],{"class":81},[25,400,402],{"id":401},"enabling-docker-desktop-integration-optional","Enabling Docker Desktop Integration (Optional)",[21,404,405],{},"If you want to use Docker commands inside your new Trixie instance without installing Docker separately:",[192,407,408,411,425,431],{},[52,409,410],{},"Open Docker Desktop",[52,412,413,414,418,419,418,422],{},"Go to ",[415,416,417],"strong",{},"Settings"," → ",[415,420,421],{},"Resources",[415,423,424],{},"WSL Integration",[52,426,427,428],{},"Enable the toggle for ",[415,429,430],{},"Trixie",[52,432,433,434],{},"Click ",[415,435,436],{},"Apply & Restart",[21,438,439,440,443],{},"The ",[33,441,442],{},"docker"," command will now be available inside Trixie.",[25,445,447],{"id":446},"managing-your-new-instance","Managing Your New Instance",[21,449,450],{},"Here are some useful commands for working with your new distribution:",[66,452,454],{"className":68,"code":453,"language":70,"meta":71,"style":71},"# List all distributions\nwsl --list --verbose\n\n# Start Trixie\nwsl -d Trixie\n\n# Shut down Trixie\nwsl --terminate Trixie\n\n# Remove Trixie completely\nwsl --unregister Trixie\n",[33,455,456,462,476,482,488,497,502,508,517,522,528],{"__ignoreMap":71},[75,457,458],{"class":77,"line":78},[75,459,461],{"class":460},"sPyeA","# List all distributions\n",[75,463,464,466,468,471,473],{"class":77,"line":98},[75,465,147],{"class":81},[75,467,86],{"class":85},[75,469,470],{"class":81},"list ",[75,472,86],{"class":85},[75,474,475],{"class":81},"verbose\n",[75,477,478],{"class":77,"line":119},[75,479,481],{"emptyLinePlaceholder":480},true,"\n",[75,483,485],{"class":77,"line":484},4,[75,486,487],{"class":460},"# Start Trixie\n",[75,489,491,493,495],{"class":77,"line":490},5,[75,492,147],{"class":81},[75,494,92],{"class":85},[75,496,180],{"class":81},[75,498,500],{"class":77,"line":499},6,[75,501,481],{"emptyLinePlaceholder":480},[75,503,505],{"class":77,"line":504},7,[75,506,507],{"class":460},"# Shut down Trixie\n",[75,509,511,513,515],{"class":77,"line":510},8,[75,512,147],{"class":81},[75,514,86],{"class":85},[75,516,390],{"class":81},[75,518,520],{"class":77,"line":519},9,[75,521,481],{"emptyLinePlaceholder":480},[75,523,525],{"class":77,"line":524},10,[75,526,527],{"class":460},"# Remove Trixie completely\n",[75,529,531,533,535],{"class":77,"line":530},11,[75,532,147],{"class":81},[75,534,86],{"class":85},[75,536,537],{"class":81},"unregister Trixie\n",[25,539,541],{"id":540},"conclusion","Conclusion",[21,543,544],{},"This method gives you a clean, minimal Debian Trixie installation running alongside your existing WSL distributions. It's perfect for testing the upcoming Debian release, isolating development environments, or simply having a fresh playground without affecting your main setup.",[21,546,547,548,551,552,555,556,559],{},"The same technique works for any Debian release — just change ",[33,549,550],{},"debian:trixie"," to ",[33,553,554],{},"debian:bookworm",", ",[33,557,558],{},"debian:sid",", or whichever version you need.",[561,562,563],"style",{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .sPyeA, html code.shiki .sPyeA{--shiki-default:#6A737D;--shiki-dark:#8B949E}",{"title":71,"searchDepth":119,"depth":119,"links":565},[566,567,568,569,570,571,572,573,574,575],{"id":27,"depth":98,"text":28},{"id":43,"depth":98,"text":44},{"id":60,"depth":98,"text":61},{"id":133,"depth":98,"text":134},{"id":186,"depth":98,"text":187},{"id":283,"depth":98,"text":284},{"id":317,"depth":98,"text":318},{"id":401,"depth":98,"text":402},{"id":446,"depth":98,"text":447},{"id":540,"depth":98,"text":541},"2026-01-16","md",{"type":579,"layout":580},"post","blog_page","\u002Fblog\u002F2026-01-16-installing-debian-trixie-wsl-docker",{"title":13,"description":23},"blog\u002F2026-01-16-installing-debian-trixie-wsl-docker",[585,586,587,588],"devops","linux","sysadmin","wsl","Zy1LeidX9TIp5GTJVByt6ALBSQ87IWpqZpxNqF6NVWY",{"id":591,"title":592,"authors":593,"body":594,"date":969,"description":598,"extension":577,"meta":970,"navigation":480,"path":971,"seo":972,"stem":973,"tags":974,"toc":6,"__hash__":976},"blog\u002Fblog\u002F2025-04-23-signing-git-commits-with-ssh-keys.md","Signing git commits with SSH keys",[15,16],{"type":18,"value":595,"toc":958},[596,599,603,606,622,625,627,639,649,671,675,682,695,698,702,705,740,746,750,753,772,775,790,794,797,840,853,857,860,875,878,926,930,948,952,955],[21,597,598],{},"Git commit signing is a great way to prove the authenticity of your commits. While GPG has long been the standard, SSH signing is a newer and simpler alternative that uses the SSH keys you may already use for authentication. In this guide, we’ll walk through setting up SSH signing for Git on your local machine.",[25,600,602],{"id":601},"why-sign-git-commits","Why Sign Git Commits?",[21,604,605],{},"Signed commits ensure that:",[49,607,608,616,619],{},[52,609,610,611,615],{},"The commit was made by ",[612,613,614],"em",{},"you",".",[52,617,618],{},"The commit hasn’t been tampered with.",[52,620,621],{},"Other collaborators can verify your identity.",[21,623,624],{},"This is especially important in open-source projects or collaborative teams where trust and traceability matter.",[25,626,44],{"id":43},[49,628,629,636],{},[52,630,631,632,635],{},"Git 2.34+ (run ",[33,633,634],{},"git --version"," to check)",[52,637,638],{},"An existing SSH key pair (or create one)",[21,640,641,645,646],{},[642,643],"binding",{"value":644},"\u003C warning >","If you don’t already have an SSH key, you can generate one:",[642,647],{"value":648},"\u003C \u002Fwarning >",[66,650,652],{"className":199,"code":651,"language":201,"meta":71,"style":71},"ssh-keygen -t ed25519 -C \"your_email@example.com\"\n",[33,653,654],{"__ignoreMap":71},[75,655,656,659,662,665,668],{"class":77,"line":78},[75,657,658],{"class":208},"ssh-keygen",[75,660,661],{"class":224}," -t",[75,663,664],{"class":212}," ed25519",[75,666,667],{"class":224}," -C",[75,669,670],{"class":212}," \"your_email@example.com\"\n",[25,672,674],{"id":673},"_1-find-your-ssh-public-key","1. Find Your SSH Public Key",[21,676,677,678,681],{},"First, locate your SSH public key (usually in ",[33,679,680],{},"~\u002F.ssh","):",[66,683,685],{"className":199,"code":684,"language":201,"meta":71,"style":71},"cat ~\u002F.ssh\u002Fid_ed25519.pub\n",[33,686,687],{"__ignoreMap":71},[75,688,689,692],{"class":77,"line":78},[75,690,691],{"class":208},"cat",[75,693,694],{"class":212}," ~\u002F.ssh\u002Fid_ed25519.pub\n",[21,696,697],{},"You'll need this for the next step.",[25,699,701],{"id":700},"_2-add-the-ssh-key-to-git-as-a-signing-key","2. Add the SSH Key to Git as a Signing Key",[21,703,704],{},"Tell Git to use your SSH key for signing:",[66,706,708],{"className":199,"code":707,"language":201,"meta":71,"style":71},"git config --global gpg.format ssh\ngit config --global user.signingkey ~\u002F.ssh\u002Fid_ed25519.pub\n",[33,709,710,727],{"__ignoreMap":71},[75,711,712,715,718,721,724],{"class":77,"line":78},[75,713,714],{"class":208},"git",[75,716,717],{"class":212}," config",[75,719,720],{"class":224}," --global",[75,722,723],{"class":212}," gpg.format",[75,725,726],{"class":212}," ssh\n",[75,728,729,731,733,735,738],{"class":77,"line":98},[75,730,714],{"class":208},[75,732,717],{"class":212},[75,734,720],{"class":224},[75,736,737],{"class":212}," user.signingkey",[75,739,694],{"class":212},[21,741,276,742,745],{},[33,743,744],{},"~\u002F.ssh\u002Fid_ed25519.pub"," with the path to your actual SSH public key if different.",[25,747,749],{"id":748},"_3-enable-commit-signing","3. Enable Commit Signing",[21,751,752],{},"You can configure Git to sign all commits by default:",[66,754,756],{"className":199,"code":755,"language":201,"meta":71,"style":71},"git config --global commit.gpgsign true\n",[33,757,758],{"__ignoreMap":71},[75,759,760,762,764,766,769],{"class":77,"line":78},[75,761,714],{"class":208},[75,763,717],{"class":212},[75,765,720],{"class":224},[75,767,768],{"class":212}," commit.gpgsign",[75,770,771],{"class":224}," true\n",[21,773,774],{},"Or enable it per repository:",[66,776,778],{"className":199,"code":777,"language":201,"meta":71,"style":71},"git config commit.gpgsign true\n",[33,779,780],{"__ignoreMap":71},[75,781,782,784,786,788],{"class":77,"line":78},[75,783,714],{"class":208},[75,785,717],{"class":212},[75,787,768],{"class":212},[75,789,771],{"class":224},[25,791,793],{"id":792},"_4-make-a-signed-commit","4. Make a Signed Commit",[21,795,796],{},"Create a test commit to verify everything works:",[66,798,800],{"className":199,"code":799,"language":201,"meta":71,"style":71},"echo \"test\" > test.txt\ngit add test.txt\ngit commit -S -m \"Test commit with SSH signing\"\n",[33,801,802,816,825],{"__ignoreMap":71},[75,803,804,807,810,813],{"class":77,"line":78},[75,805,806],{"class":224},"echo",[75,808,809],{"class":212}," \"test\"",[75,811,812],{"class":85}," >",[75,814,815],{"class":212}," test.txt\n",[75,817,818,820,823],{"class":77,"line":98},[75,819,714],{"class":208},[75,821,822],{"class":212}," add",[75,824,815],{"class":212},[75,826,827,829,832,835,837],{"class":77,"line":119},[75,828,714],{"class":208},[75,830,831],{"class":212}," commit",[75,833,834],{"class":224}," -S",[75,836,251],{"class":224},[75,838,839],{"class":212}," \"Test commit with SSH signing\"\n",[21,841,842,845,846,849,850],{},[642,843],{"value":844},"\u003C info >"," The ",[33,847,848],{},"-S"," flag tells Git to sign the commit.",[642,851],{"value":852},"\u003C \u002Finfo >",[25,854,856],{"id":855},"_5-verify-the-signature","5. Verify the Signature",[21,858,859],{},"You can verify a commit with:",[66,861,863],{"className":199,"code":862,"language":201,"meta":71,"style":71},"git log --show-signature\n",[33,864,865],{"__ignoreMap":71},[75,866,867,869,872],{"class":77,"line":78},[75,868,714],{"class":208},[75,870,871],{"class":212}," log",[75,873,874],{"class":224}," --show-signature\n",[21,876,877],{},"You should see something like:",[66,879,881],{"className":199,"code":880,"language":201,"meta":71,"style":71},"gpg: Signature made ...\ngpg:                using EDDSA key ...\nGood signature from \"Your Name \u003Cyour_email@example.com>\"\n",[33,882,883,897,912],{"__ignoreMap":71},[75,884,885,888,891,894],{"class":77,"line":78},[75,886,887],{"class":208},"gpg:",[75,889,890],{"class":212}," Signature",[75,892,893],{"class":212}," made",[75,895,896],{"class":212}," ...\n",[75,898,899,901,904,907,910],{"class":77,"line":98},[75,900,887],{"class":208},[75,902,903],{"class":212},"                using",[75,905,906],{"class":212}," EDDSA",[75,908,909],{"class":212}," key",[75,911,896],{"class":212},[75,913,914,917,920,923],{"class":77,"line":119},[75,915,916],{"class":208},"Good",[75,918,919],{"class":212}," signature",[75,921,922],{"class":212}," from",[75,924,925],{"class":212}," \"Your Name \u003Cyour_email@example.com>\"\n",[25,927,929],{"id":928},"troubleshooting-tips","Troubleshooting Tips",[49,931,932,935,938,945],{},[52,933,934],{},"If signing fails, double-check the key path.",[52,936,937],{},"Ensure your Git version supports SSH signing (2.34+).",[52,939,940,941,944],{},"Make sure your SSH key has the correct permissions (",[33,942,943],{},"chmod 600 ~\u002F.ssh\u002Fid_ed25519",").",[52,946,947],{},"GitHub will only show the \"Verified\" badge if your signing key is added to your profile.",[25,949,951],{"id":950},"youre-signed-and-secure","You're Signed and Secure!",[21,953,954],{},"That’s it! Your commits are now cryptographically signed using your SSH key, offering both security and simplicity.",[561,956,957],{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}",{"title":71,"searchDepth":119,"depth":119,"links":959},[960,961,962,963,964,965,966,967,968],{"id":601,"depth":98,"text":602},{"id":43,"depth":98,"text":44},{"id":673,"depth":98,"text":674},{"id":700,"depth":98,"text":701},{"id":748,"depth":98,"text":749},{"id":792,"depth":98,"text":793},{"id":855,"depth":98,"text":856},{"id":928,"depth":98,"text":929},{"id":950,"depth":98,"text":951},"2025-04-23",{"type":579,"layout":580},"\u002Fblog\u002F2025-04-23-signing-git-commits-with-ssh-keys",{"title":592,"description":598},"blog\u002F2025-04-23-signing-git-commits-with-ssh-keys",[585,714,975],"ssh","9-kVaN6hitEDcM-zD5YkDvT_i_ZlUjQLXwRsDD3qIjs",{"id":978,"title":979,"authors":980,"body":981,"date":1781,"description":989,"extension":577,"meta":1782,"navigation":480,"path":1783,"seo":1784,"stem":1785,"tags":1786,"toc":6,"__hash__":1789},"blog\u002Fblog\u002F2025-11-26-using-git-tags.md","Using Git Tags: A Practical Guide",[],{"type":18,"value":982,"toc":1757},[983,987,990,994,997,1003,1009,1013,1018,1021,1036,1040,1043,1065,1078,1082,1085,1108,1114,1118,1121,1133,1136,1153,1156,1170,1173,1187,1191,1198,1202,1219,1223,1226,1242,1245,1261,1265,1268,1272,1275,1291,1297,1303,1307,1310,1328,1331,1347,1350,1354,1357,1384,1388,1391,1405,1408,1427,1431,1434,1516,1520,1523,1540,1543,1557,1561,1564,1578,1581,1598,1602,1644,1648,1651,1751,1754],[984,985,979],"h1",{"id":986},"using-git-tags-a-practical-guide",[21,988,989],{},"Tags in Git are references that point to specific points in your repository's history. They're commonly used to mark release points (v1.0, v2.0, etc.) and provide a way to bookmark important commits. Unlike branches, tags don't change once created — they're fixed pointers to a specific commit.",[25,991,993],{"id":992},"types-of-tags","Types of Tags",[21,995,996],{},"Git supports two types of tags:",[21,998,999,1002],{},[415,1000,1001],{},"Lightweight tags"," are simply a pointer to a commit. They're quick to create but contain no additional information.",[21,1004,1005,1008],{},[415,1006,1007],{},"Annotated tags"," are stored as full objects in the Git database. They contain the tagger's name, email, date, and a tagging message. They can also be signed and verified with GPG. For releases and any tags you want to share, annotated tags are recommended.",[25,1010,1012],{"id":1011},"creating-tags","Creating Tags",[1014,1015,1017],"h3",{"id":1016},"creating-a-lightweight-tag","Creating a Lightweight Tag",[21,1019,1020],{},"To create a lightweight tag on the current commit:",[66,1022,1024],{"className":199,"code":1023,"language":201,"meta":71,"style":71},"git tag v1.0.0\n",[33,1025,1026],{"__ignoreMap":71},[75,1027,1028,1030,1033],{"class":77,"line":78},[75,1029,714],{"class":208},[75,1031,1032],{"class":212}," tag",[75,1034,1035],{"class":212}," v1.0.0\n",[1014,1037,1039],{"id":1038},"creating-an-annotated-tag","Creating an Annotated Tag",[21,1041,1042],{},"To create an annotated tag with a message:",[66,1044,1046],{"className":199,"code":1045,"language":201,"meta":71,"style":71},"git tag -a v1.0.0 -m \"Release version 1.0.0\"\n",[33,1047,1048],{"__ignoreMap":71},[75,1049,1050,1052,1054,1057,1060,1062],{"class":77,"line":78},[75,1051,714],{"class":208},[75,1053,1032],{"class":212},[75,1055,1056],{"class":224}," -a",[75,1058,1059],{"class":212}," v1.0.0",[75,1061,251],{"class":224},[75,1063,1064],{"class":212}," \"Release version 1.0.0\"\n",[21,1066,439,1067,1070,1071,1074,1075,1077],{},[33,1068,1069],{},"-a"," flag specifies an annotated tag, and ",[33,1072,1073],{},"-m"," provides the tag message. If you omit ",[33,1076,1073],{},", Git will open your editor for you to write a message.",[1014,1079,1081],{"id":1080},"tagging-a-specific-commit","Tagging a Specific Commit",[21,1083,1084],{},"If you need to tag a commit that isn't the current HEAD, specify the commit hash:",[66,1086,1088],{"className":199,"code":1087,"language":201,"meta":71,"style":71},"git tag -a v1.0.0 -m \"Release version 1.0.0\" 9fceb02\n",[33,1089,1090],{"__ignoreMap":71},[75,1091,1092,1094,1096,1098,1100,1102,1105],{"class":77,"line":78},[75,1093,714],{"class":208},[75,1095,1032],{"class":212},[75,1097,1056],{"class":224},[75,1099,1059],{"class":212},[75,1101,251],{"class":224},[75,1103,1104],{"class":212}," \"Release version 1.0.0\"",[75,1106,1107],{"class":212}," 9fceb02\n",[21,1109,1110,1111,615],{},"You can find commit hashes using ",[33,1112,1113],{},"git log --oneline",[25,1115,1117],{"id":1116},"listing-tags","Listing Tags",[21,1119,1120],{},"To list all tags in your repository:",[66,1122,1124],{"className":199,"code":1123,"language":201,"meta":71,"style":71},"git tag\n",[33,1125,1126],{"__ignoreMap":71},[75,1127,1128,1130],{"class":77,"line":78},[75,1129,714],{"class":208},[75,1131,1132],{"class":212}," tag\n",[21,1134,1135],{},"To filter tags by pattern:",[66,1137,1139],{"className":199,"code":1138,"language":201,"meta":71,"style":71},"git tag -l \"v1.*\"\n",[33,1140,1141],{"__ignoreMap":71},[75,1142,1143,1145,1147,1150],{"class":77,"line":78},[75,1144,714],{"class":208},[75,1146,1032],{"class":212},[75,1148,1149],{"class":224}," -l",[75,1151,1152],{"class":212}," \"v1.*\"\n",[21,1154,1155],{},"To see tag details along with commit information:",[66,1157,1159],{"className":199,"code":1158,"language":201,"meta":71,"style":71},"git tag -n\n",[33,1160,1161],{"__ignoreMap":71},[75,1162,1163,1165,1167],{"class":77,"line":78},[75,1164,714],{"class":208},[75,1166,1032],{"class":212},[75,1168,1169],{"class":224}," -n\n",[21,1171,1172],{},"For more detailed information about a specific tag:",[66,1174,1176],{"className":199,"code":1175,"language":201,"meta":71,"style":71},"git show v1.0.0\n",[33,1177,1178],{"__ignoreMap":71},[75,1179,1180,1182,1185],{"class":77,"line":78},[75,1181,714],{"class":208},[75,1183,1184],{"class":212}," show",[75,1186,1035],{"class":212},[25,1188,1190],{"id":1189},"pushing-tags-to-remote","Pushing Tags to Remote",[21,1192,1193,1194,1197],{},"By default, ",[33,1195,1196],{},"git push"," doesn't transfer tags to remote servers. You need to explicitly push them.",[1014,1199,1201],{"id":1200},"push-a-single-tag","Push a Single Tag",[66,1203,1205],{"className":199,"code":1204,"language":201,"meta":71,"style":71},"git push origin v1.0.0\n",[33,1206,1207],{"__ignoreMap":71},[75,1208,1209,1211,1214,1217],{"class":77,"line":78},[75,1210,714],{"class":208},[75,1212,1213],{"class":212}," push",[75,1215,1216],{"class":212}," origin",[75,1218,1035],{"class":212},[1014,1220,1222],{"id":1221},"push-all-tags","Push All Tags",[21,1224,1225],{},"To push all your local tags at once:",[66,1227,1229],{"className":199,"code":1228,"language":201,"meta":71,"style":71},"git push origin --tags\n",[33,1230,1231],{"__ignoreMap":71},[75,1232,1233,1235,1237,1239],{"class":77,"line":78},[75,1234,714],{"class":208},[75,1236,1213],{"class":212},[75,1238,1216],{"class":212},[75,1240,1241],{"class":224}," --tags\n",[21,1243,1244],{},"If you only want to push annotated tags (not lightweight tags):",[66,1246,1248],{"className":199,"code":1247,"language":201,"meta":71,"style":71},"git push origin --follow-tags\n",[33,1249,1250],{"__ignoreMap":71},[75,1251,1252,1254,1256,1258],{"class":77,"line":78},[75,1253,714],{"class":208},[75,1255,1213],{"class":212},[75,1257,1216],{"class":212},[75,1259,1260],{"class":224}," --follow-tags\n",[25,1262,1264],{"id":1263},"deleting-tags","Deleting Tags",[21,1266,1267],{},"Sometimes you need to remove a tag — perhaps you tagged the wrong commit or made a typo in the tag name.",[1014,1269,1271],{"id":1270},"delete-a-local-tag","Delete a Local Tag",[21,1273,1274],{},"To delete a tag from your local repository:",[66,1276,1278],{"className":199,"code":1277,"language":201,"meta":71,"style":71},"git tag -d v1.0.0\n",[33,1279,1280],{"__ignoreMap":71},[75,1281,1282,1284,1286,1289],{"class":77,"line":78},[75,1283,714],{"class":208},[75,1285,1032],{"class":212},[75,1287,1288],{"class":224}," -d",[75,1290,1035],{"class":212},[21,1292,439,1293,1296],{},[33,1294,1295],{},"-d"," flag stands for delete. You'll see output confirming the deletion:",[66,1298,1301],{"className":1299,"code":1300,"language":327},[325],"Deleted tag 'v1.0.0' (was 9fceb02)\n",[33,1302,1300],{"__ignoreMap":71},[1014,1304,1306],{"id":1305},"delete-a-remote-tag","Delete a Remote Tag",[21,1308,1309],{},"Deleting locally doesn't affect the remote. To delete a tag from the remote repository:",[66,1311,1313],{"className":199,"code":1312,"language":201,"meta":71,"style":71},"git push origin --delete v1.0.0\n",[33,1314,1315],{"__ignoreMap":71},[75,1316,1317,1319,1321,1323,1326],{"class":77,"line":78},[75,1318,714],{"class":208},[75,1320,1213],{"class":212},[75,1322,1216],{"class":212},[75,1324,1325],{"class":224}," --delete",[75,1327,1035],{"class":212},[21,1329,1330],{},"Alternatively, you can use the older syntax:",[66,1332,1334],{"className":199,"code":1333,"language":201,"meta":71,"style":71},"git push origin :refs\u002Ftags\u002Fv1.0.0\n",[33,1335,1336],{"__ignoreMap":71},[75,1337,1338,1340,1342,1344],{"class":77,"line":78},[75,1339,714],{"class":208},[75,1341,1213],{"class":212},[75,1343,1216],{"class":212},[75,1345,1346],{"class":212}," :refs\u002Ftags\u002Fv1.0.0\n",[21,1348,1349],{},"Both commands achieve the same result. The first is more readable; the second pushes an empty reference to the remote tag, effectively deleting it.",[1014,1351,1353],{"id":1352},"delete-both-local-and-remote","Delete Both Local and Remote",[21,1355,1356],{},"To completely remove a tag, run both commands:",[66,1358,1360],{"className":199,"code":1359,"language":201,"meta":71,"style":71},"git tag -d v1.0.0\ngit push origin --delete v1.0.0\n",[33,1361,1362,1372],{"__ignoreMap":71},[75,1363,1364,1366,1368,1370],{"class":77,"line":78},[75,1365,714],{"class":208},[75,1367,1032],{"class":212},[75,1369,1288],{"class":224},[75,1371,1035],{"class":212},[75,1373,1374,1376,1378,1380,1382],{"class":77,"line":98},[75,1375,714],{"class":208},[75,1377,1213],{"class":212},[75,1379,1216],{"class":212},[75,1381,1325],{"class":224},[75,1383,1035],{"class":212},[25,1385,1387],{"id":1386},"checking-out-tags","Checking Out Tags",[21,1389,1390],{},"To view the code at a specific tag, you can check it out:",[66,1392,1394],{"className":199,"code":1393,"language":201,"meta":71,"style":71},"git checkout v1.0.0\n",[33,1395,1396],{"__ignoreMap":71},[75,1397,1398,1400,1403],{"class":77,"line":78},[75,1399,714],{"class":208},[75,1401,1402],{"class":212}," checkout",[75,1404,1035],{"class":212},[21,1406,1407],{},"This puts you in a \"detached HEAD\" state — you're not on any branch. If you want to make changes based on a tag, create a new branch:",[66,1409,1411],{"className":199,"code":1410,"language":201,"meta":71,"style":71},"git checkout -b release-1.0-hotfix v1.0.0\n",[33,1412,1413],{"__ignoreMap":71},[75,1414,1415,1417,1419,1422,1425],{"class":77,"line":78},[75,1416,714],{"class":208},[75,1418,1402],{"class":212},[75,1420,1421],{"class":224}," -b",[75,1423,1424],{"class":212}," release-1.0-hotfix",[75,1426,1035],{"class":212},[25,1428,1430],{"id":1429},"renaming-a-tag","Renaming a Tag",[21,1432,1433],{},"Git doesn't have a direct rename command for tags. Instead, you create a new tag pointing to the same commit, then delete the old one:",[66,1435,1437],{"className":199,"code":1436,"language":201,"meta":71,"style":71},"# Create new tag pointing to the same commit as old tag\ngit tag new-tag old-tag\n\n# Delete old tag locally\ngit tag -d old-tag\n\n# Delete old tag from remote\ngit push origin --delete old-tag\n\n# Push new tag to remote\ngit push origin new-tag\n",[33,1438,1439,1444,1456,1460,1465,1475,1479,1484,1496,1500,1505],{"__ignoreMap":71},[75,1440,1441],{"class":77,"line":78},[75,1442,1443],{"class":460},"# Create new tag pointing to the same commit as old tag\n",[75,1445,1446,1448,1450,1453],{"class":77,"line":98},[75,1447,714],{"class":208},[75,1449,1032],{"class":212},[75,1451,1452],{"class":212}," new-tag",[75,1454,1455],{"class":212}," old-tag\n",[75,1457,1458],{"class":77,"line":119},[75,1459,481],{"emptyLinePlaceholder":480},[75,1461,1462],{"class":77,"line":484},[75,1463,1464],{"class":460},"# Delete old tag locally\n",[75,1466,1467,1469,1471,1473],{"class":77,"line":490},[75,1468,714],{"class":208},[75,1470,1032],{"class":212},[75,1472,1288],{"class":224},[75,1474,1455],{"class":212},[75,1476,1477],{"class":77,"line":499},[75,1478,481],{"emptyLinePlaceholder":480},[75,1480,1481],{"class":77,"line":504},[75,1482,1483],{"class":460},"# Delete old tag from remote\n",[75,1485,1486,1488,1490,1492,1494],{"class":77,"line":510},[75,1487,714],{"class":208},[75,1489,1213],{"class":212},[75,1491,1216],{"class":212},[75,1493,1325],{"class":224},[75,1495,1455],{"class":212},[75,1497,1498],{"class":77,"line":519},[75,1499,481],{"emptyLinePlaceholder":480},[75,1501,1502],{"class":77,"line":524},[75,1503,1504],{"class":460},"# Push new tag to remote\n",[75,1506,1507,1509,1511,1513],{"class":77,"line":530},[75,1508,714],{"class":208},[75,1510,1213],{"class":212},[75,1512,1216],{"class":212},[75,1514,1515],{"class":212}," new-tag\n",[25,1517,1519],{"id":1518},"comparing-tags","Comparing Tags",[21,1521,1522],{},"To see what changed between two tags:",[66,1524,1526],{"className":199,"code":1525,"language":201,"meta":71,"style":71},"git diff v1.0.0 v2.0.0\n",[33,1527,1528],{"__ignoreMap":71},[75,1529,1530,1532,1535,1537],{"class":77,"line":78},[75,1531,714],{"class":208},[75,1533,1534],{"class":212}," diff",[75,1536,1059],{"class":212},[75,1538,1539],{"class":212}," v2.0.0\n",[21,1541,1542],{},"To see the commits between two tags:",[66,1544,1546],{"className":199,"code":1545,"language":201,"meta":71,"style":71},"git log v1.0.0..v2.0.0\n",[33,1547,1548],{"__ignoreMap":71},[75,1549,1550,1552,1554],{"class":77,"line":78},[75,1551,714],{"class":208},[75,1553,871],{"class":212},[75,1555,1556],{"class":212}," v1.0.0..v2.0.0\n",[25,1558,1560],{"id":1559},"fetching-remote-tags","Fetching Remote Tags",[21,1562,1563],{},"To fetch all tags from a remote:",[66,1565,1567],{"className":199,"code":1566,"language":201,"meta":71,"style":71},"git fetch --tags\n",[33,1568,1569],{"__ignoreMap":71},[75,1570,1571,1573,1576],{"class":77,"line":78},[75,1572,714],{"class":208},[75,1574,1575],{"class":212}," fetch",[75,1577,1241],{"class":224},[21,1579,1580],{},"If remote tags have been deleted and you want to sync your local tags:",[66,1582,1584],{"className":199,"code":1583,"language":201,"meta":71,"style":71},"git fetch --prune --prune-tags\n",[33,1585,1586],{"__ignoreMap":71},[75,1587,1588,1590,1592,1595],{"class":77,"line":78},[75,1589,714],{"class":208},[75,1591,1575],{"class":212},[75,1593,1594],{"class":224}," --prune",[75,1596,1597],{"class":224}," --prune-tags\n",[25,1599,1601],{"id":1600},"best-practices","Best Practices",[192,1603,1604,1610,1626,1632,1638],{},[52,1605,1606,1609],{},[415,1607,1608],{},"Use annotated tags for releases"," — they contain metadata that helps with tracking who created the tag and when.",[52,1611,1612,1615,1616,555,1619,555,1622,1625],{},[415,1613,1614],{},"Follow semantic versioning"," — use a consistent naming scheme like ",[33,1617,1618],{},"v1.0.0",[33,1620,1621],{},"v1.0.1",[33,1623,1624],{},"v1.1.0"," to make versions meaningful.",[52,1627,1628,1631],{},[415,1629,1630],{},"Write meaningful tag messages"," — document what's included in the release.",[52,1633,1634,1637],{},[415,1635,1636],{},"Don't move tags after pushing"," — if a tag has been pushed to a shared remote, treat it as immutable. Create a new tag instead.",[52,1639,1640,1643],{},[415,1641,1642],{},"Use lightweight tags for temporary or private markers"," — they're perfect for bookmarking commits during development.",[25,1645,1647],{"id":1646},"summary","Summary",[21,1649,1650],{},"Git tags are a simple but powerful feature for marking important points in your project's history. The key commands to remember:",[1652,1653,1654,1667],"table",{},[1655,1656,1657],"thead",{},[1658,1659,1660,1664],"tr",{},[1661,1662,1663],"th",{},"Operation",[1661,1665,1666],{},"Command",[1668,1669,1670,1681,1691,1701,1711,1721,1731,1741],"tbody",{},[1658,1671,1672,1676],{},[1673,1674,1675],"td",{},"Create annotated tag",[1673,1677,1678],{},[33,1679,1680],{},"git tag -a v1.0.0 -m \"message\"",[1658,1682,1683,1686],{},[1673,1684,1685],{},"Create lightweight tag",[1673,1687,1688],{},[33,1689,1690],{},"git tag v1.0.0",[1658,1692,1693,1696],{},[1673,1694,1695],{},"List tags",[1673,1697,1698],{},[33,1699,1700],{},"git tag",[1658,1702,1703,1706],{},[1673,1704,1705],{},"Push single tag",[1673,1707,1708],{},[33,1709,1710],{},"git push origin v1.0.0",[1658,1712,1713,1716],{},[1673,1714,1715],{},"Push all tags",[1673,1717,1718],{},[33,1719,1720],{},"git push origin --tags",[1658,1722,1723,1726],{},[1673,1724,1725],{},"Delete local tag",[1673,1727,1728],{},[33,1729,1730],{},"git tag -d v1.0.0",[1658,1732,1733,1736],{},[1673,1734,1735],{},"Delete remote tag",[1673,1737,1738],{},[33,1739,1740],{},"git push origin --delete v1.0.0",[1658,1742,1743,1746],{},[1673,1744,1745],{},"Checkout tag",[1673,1747,1748],{},[33,1749,1750],{},"git checkout v1.0.0",[21,1752,1753],{},"With these commands in your toolkit, you'll be able to effectively manage releases and important milestones in your Git repositories.",[561,1755,1756],{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sPyeA, html code.shiki .sPyeA{--shiki-default:#6A737D;--shiki-dark:#8B949E}",{"title":71,"searchDepth":119,"depth":119,"links":1758},[1759,1760,1765,1766,1770,1775,1776,1777,1778,1779,1780],{"id":992,"depth":98,"text":993},{"id":1011,"depth":98,"text":1012,"children":1761},[1762,1763,1764],{"id":1016,"depth":119,"text":1017},{"id":1038,"depth":119,"text":1039},{"id":1080,"depth":119,"text":1081},{"id":1116,"depth":98,"text":1117},{"id":1189,"depth":98,"text":1190,"children":1767},[1768,1769],{"id":1200,"depth":119,"text":1201},{"id":1221,"depth":119,"text":1222},{"id":1263,"depth":98,"text":1264,"children":1771},[1772,1773,1774],{"id":1270,"depth":119,"text":1271},{"id":1305,"depth":119,"text":1306},{"id":1352,"depth":119,"text":1353},{"id":1386,"depth":98,"text":1387},{"id":1429,"depth":98,"text":1430},{"id":1518,"depth":98,"text":1519},{"id":1559,"depth":98,"text":1560},{"id":1600,"depth":98,"text":1601},{"id":1646,"depth":98,"text":1647},"2025-01-15",{},"\u002Fblog\u002F2025-11-26-using-git-tags",{"title":979,"description":989},"blog\u002F2025-11-26-using-git-tags",[714,1787,1788],"version-control","development","-SU_4kGsdmyxnKloEvYRWFW6FbSNNjUx7tJ2KyIhhjA",{"id":1791,"title":1792,"authors":1793,"body":1794,"date":1976,"description":1792,"extension":577,"meta":1977,"navigation":480,"path":1978,"seo":1979,"stem":1980,"tags":1981,"toc":6,"__hash__":1983},"blog\u002Fblog\u002F2024-05-15-using-systemd-timers.md","Automating Tasks with systemd timers",[15,16],{"type":18,"value":1795,"toc":1965},[1796,1798,1812,1814,1817,1831,1835,1839,1845,1848,1853,1856,1862,1866,1876,1879,1884,1886,1892,1902,1906,1909,1914,1919,1923,1926,1931,1934,1938,1941,1946,1949,1951,1962],[21,1797,1792],{},[21,1799,1800,1801,1804,1805,1808,1809,1811],{},"Automating repetitive tasks is a key part of system administration, and traditionally, ",[33,1802,1803],{},"cron"," has been the go-to tool for this purpose. However, ",[33,1806,1807],{},"systemd"," timers offer a more powerful and flexible alternative. In this post, we'll walk through setting up a ",[33,1810,1807],{}," timer to run a script hourly on Debian 12.",[25,1813,44],{"id":43},[21,1815,1816],{},"Make sure you have the following:",[49,1818,1819,1825],{},[52,1820,1821,1822,615],{},"A script you want to run periodically. For this example, we'll use ",[33,1823,1824],{},"\u002Fhome\u002Fadmin\u002Fbin\u002Fbackup.sh",[52,1826,1827,1828,1830],{},"Root privileges to create and manage ",[33,1829,1807],{}," service and timer files.",[25,1832,1834],{"id":1833},"step-by-step-guide","Step-by-Step Guide",[1014,1836,1838],{"id":1837},"_1-create-the-service-file","1. Create the Service File",[21,1840,1841,1842,1844],{},"First, we need to create a ",[33,1843,1807],{}," service file that specifies the task we want to run. This file will define the script to execute, the user to run it as, and the working directory.",[21,1846,1847],{},"Run the following command to create the service file:",[21,1849,1850],{},[33,1851,1852],{},"sudo nano \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service",[21,1854,1855],{},"Add the following content to the file:",[66,1857,1860],{"className":1858,"code":1859,"language":327},[325],"[Unit]\nDescription=Run backup script hourly\n\n[Service]\nUser=admin\nWorkingDirectory=\u002Fhome\u002Fadmin\nExecStart=\u002Fhome\u002Fadmin\u002Fbin\u002Fbackup.sh\n",[33,1861,1859],{"__ignoreMap":71},[1014,1863,1865],{"id":1864},"_2-create-the-timer-file","2. Create the Timer File",[21,1867,1868,1869,1871,1872,1875],{},"Next, create a timer file that tells ",[33,1870,1807],{}," when to run the service. For an hourly schedule, we can use the ",[33,1873,1874],{},"OnCalendar"," directive.",[21,1877,1878],{},"Run the following command to create the timer file:",[21,1880,1881],{},[33,1882,1883],{},"sudo nano \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer",[21,1885,1855],{},[66,1887,1890],{"className":1888,"code":1889,"language":327},[325],"[Unit]\nDescription=Run backup.service hourly\n\n[Timer]\nOnCalendar=hourly\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n",[33,1891,1889],{"__ignoreMap":71},[21,1893,439,1894,1897,1898,1901],{},[33,1895,1896],{},"OnCalendar=hourly"," directive schedules the job to run every hour. The ",[33,1899,1900],{},"Persistent=true"," directive ensures the job will run as soon as possible if the system was off at the scheduled time.",[1014,1903,1905],{"id":1904},"_3-enable-and-start-the-timer","3. Enable and Start the Timer",[21,1907,1908],{},"Enable and start the timer to ensure it runs automatically:",[21,1910,1911],{},[33,1912,1913],{},"sudo systemctl enable backup.timer",[21,1915,1916],{},[33,1917,1918],{},"sudo systemctl start backup.timer",[1014,1920,1922],{"id":1921},"_4-verify-the-timer","4. Verify the Timer",[21,1924,1925],{},"Check the status of the timer to ensure it's running correctly:",[21,1927,1928],{},[33,1929,1930],{},"sudo systemctl status backup.timer",[21,1932,1933],{},"You should see output indicating that the timer is active and when it is next scheduled to run.",[1014,1935,1937],{"id":1936},"_5-list-all-timers","5. List All Timers",[21,1939,1940],{},"To view all active timers, use the following command:",[21,1942,1943],{},[33,1944,1945],{},"systemctl list-timers",[21,1947,1948],{},"This will list all active timers, their next run time, and their last run time.",[25,1950,541],{"id":540},[21,1952,1953,1954,1956,1957,555,1959,1961],{},"Using ",[33,1955,1807],{}," timers on Debian 12 is a robust and flexible way to schedule periodic tasks. Unlike ",[33,1958,1803],{},[33,1960,1807],{}," timers offer more features and finer control over job execution, including the ability to specify the user and working directory. By following the steps above, you can easily set up an hourly job to automate your tasks.",[21,1963,1964],{},"Happy automating!",{"title":71,"searchDepth":119,"depth":119,"links":1966},[1967,1968,1975],{"id":43,"depth":98,"text":44},{"id":1833,"depth":98,"text":1834,"children":1969},[1970,1971,1972,1973,1974],{"id":1837,"depth":119,"text":1838},{"id":1864,"depth":119,"text":1865},{"id":1904,"depth":119,"text":1905},{"id":1921,"depth":119,"text":1922},{"id":1936,"depth":119,"text":1937},{"id":540,"depth":98,"text":541},"2024-05-15",{"type":579,"layout":580},"\u002Fblog\u002F2024-05-15-using-systemd-timers",{"title":1792,"description":1792},"blog\u002F2024-05-15-using-systemd-timers",[585,1982],"mongodb","7uSiEEWBN_DxvlmbW0OzhWuwlNthU9nqFHv51n1Fmto",{"id":1985,"title":1986,"authors":1987,"body":1988,"date":2400,"description":2401,"extension":577,"meta":2402,"navigation":480,"path":2403,"seo":2404,"stem":2405,"tags":2406,"toc":6,"__hash__":2407},"blog\u002Fblog\u002F2024-05-10-using-ssh-agent.md","Setting Up ed25519 SSH Keys and ssh-agent in WSL and Linux",[15,16],{"type":18,"value":1989,"toc":2388},[1990,1997,2001,2004,2017,2021,2025,2028,2054,2058,2061,2086,2090,2097,2101,2105,2111,2126,2130,2136,2152,2211,2222,2379,2385],[21,1991,1992,1993,1996],{},"Secure Shell (SSH) is a fundamental tool for developers, system administrators, and anyone requiring secure remote access to servers and systems. SSH keys provide a more secure method of authentication than traditional passwords by using a private-public key pair. This blog post guides you through setting up ed25519 SSH keys and configuring ",[33,1994,1995],{},"ssh-agent"," in both Windows Subsystem for Linux (WSL) and native Linux environments to enhance the security and simplicity of your remote connections.",[25,1998,2000],{"id":1999},"understanding-ssh-keys-and-ssh-agent","Understanding SSH Keys and ssh-agent",[21,2002,2003],{},"Let’s begin by understanding the core components:",[49,2005,2006,2012],{},[52,2007,2008,2011],{},[415,2009,2010],{},"SSH Keys",": An SSH key pair consists of a private key, which is kept secret, and a public key, which is shared with servers. Authentication is done by proving you possess the private key without transmitting it over the network.",[52,2013,2014,2016],{},[415,2015,1995],{},": This background program caches your private keys and handles key-based authentication on your behalf. It's especially useful for managing multiple keys or automating scripts without the need to enter passwords repeatedly.",[25,2018,2020],{"id":2019},"setting-up-ssh-keys","Setting Up SSH Keys",[1014,2022,2024],{"id":2023},"_1-generating-ssh-keys","1. Generating SSH Keys",[21,2026,2027],{},"To generate ed25519 SSH keys, follow these steps for both WSL and Linux:",[192,2029,2030,2033,2044,2051],{},[52,2031,2032],{},"Open a terminal.",[52,2034,2035,2036,2039,2040,2043],{},"Run the command to create a new SSH key pair:\n",[33,2037,2038],{},"ssh-keygen -t ed25519 -C \"your_email@example.com\"","\nReplace ",[33,2041,2042],{},"\"your_email@example.com\""," with your email or a label for this key. This command creates an ed25519 key pair, known for its high security and efficiency.",[52,2045,2046,2047,2050],{},"You will be prompted to enter a file path to save the keys. The default is usually ",[33,2048,2049],{},"~\u002F.ssh\u002Fid_ed25519",". Press Enter to accept the default or specify a different path.",[52,2052,2053],{},"Enter a passphrase for additional security when prompted. This passphrase will protect your private key on your local machine.",[1014,2055,2057],{"id":2056},"_2-copying-the-public-key-to-the-server","2. Copying the Public Key to the Server",[21,2059,2060],{},"To authenticate using your new SSH key, you need to place the public key on the server:",[192,2062,2063,2069,2072],{},[52,2064,2065,2066],{},"Display your public key with:\n",[33,2067,2068],{},"cat ~\u002F.ssh\u002Fid_ed25519.pub",[52,2070,2071],{},"Copy the displayed key.",[52,2073,2074,2075,2078,2079,2039,2082,2085],{},"On the server, paste this key into ",[33,2076,2077],{},"~\u002F.ssh\u002Fauthorized_keys",". If you can SSH into the server with a password, use this shortcut:\n",[33,2080,2081],{},"ssh-copy-id -i ~\u002F.ssh\u002Fid_ed25519.pub username@server_address",[33,2083,2084],{},"username@server_address"," with your actual server login.",[1014,2087,2089],{"id":2088},"_3-testing-your-ssh-connection","3. Testing Your SSH Connection",[21,2091,2092,2093,2096],{},"Test the SSH connection to ensure your key works:\n",[33,2094,2095],{},"ssh username@server_address","\nIf set up correctly, you'll be prompted for the passphrase of your private key (if you set one) rather than the user's password on the server.",[25,2098,2100],{"id":2099},"configuring-ssh-agent","Configuring ssh-agent",[1014,2102,2104],{"id":2103},"_1-starting-ssh-agent","1. Starting ssh-agent",[21,2106,2107,2108,2110],{},"For both WSL and Linux, you start ",[33,2109,1995],{}," to manage your keys:",[192,2112,2113,2119],{},[52,2114,2115,2116],{},"Start the agent:\n",[33,2117,2118],{},"eval \"$(ssh-agent -s)\"",[52,2120,2121,2122,2125],{},"Add your private key to the agent:\n",[33,2123,2124],{},"ssh-add ~\u002F.ssh\u002Fid_ed25519","\nIf you used a different path or key name, adjust the path accordingly.",[1014,2127,2129],{"id":2128},"_2-automating-ssh-agent-on-startup","2. Automating ssh-agent on Startup",[21,2131,2132,2133,2135],{},"To avoid manually starting ",[33,2134,1995],{}," every session, automate it:",[49,2137,2138],{},[52,2139,2140,2143,2144,2147,2148,2151],{},[415,2141,2142],{},"In Linux",": Add the following to your ",[33,2145,2146],{},"~\u002F.bashrc"," or ",[33,2149,2150],{},"~\u002F.zshrc",":",[66,2153,2155],{"className":199,"code":2154,"language":201,"meta":71,"style":71},"if [ -z \"$SSH_AUTH_SOCK\" ]; then\n    eval \"$(ssh-agent -s)\"\n    ssh-add ~\u002F.ssh\u002Fid_ed25519\nfi\n",[33,2156,2157,2183,2198,2206],{"__ignoreMap":71},[75,2158,2159,2162,2165,2168,2171,2174,2177,2180],{"class":77,"line":78},[75,2160,2161],{"class":85},"if",[75,2163,2164],{"class":81}," [ ",[75,2166,2167],{"class":85},"-z",[75,2169,2170],{"class":212}," \"",[75,2172,2173],{"class":81},"$SSH_AUTH_SOCK",[75,2175,2176],{"class":212},"\"",[75,2178,2179],{"class":81}," ]; ",[75,2181,2182],{"class":85},"then\n",[75,2184,2185,2188,2191,2193,2195],{"class":77,"line":98},[75,2186,2187],{"class":224},"    eval",[75,2189,2190],{"class":212}," \"$(",[75,2192,1995],{"class":208},[75,2194,260],{"class":224},[75,2196,2197],{"class":212},")\"\n",[75,2199,2200,2203],{"class":77,"line":119},[75,2201,2202],{"class":208},"    ssh-add",[75,2204,2205],{"class":212}," ~\u002F.ssh\u002Fid_ed25519\n",[75,2207,2208],{"class":77,"line":484},[75,2209,2210],{"class":85},"fi\n",[49,2212,2213],{},[52,2214,2215,2218,2219,2221],{},[415,2216,2217],{},"In WSL",": We need to manage ",[33,2220,1995],{}," differently because of how WSL works. Paste this into your .bashrc file in your home directory.",[66,2223,2225],{"className":199,"code":2224,"language":201,"meta":71,"style":71},"ssh_pid=$(pidof ssh-agent)\n\n# If the agent is not running, start it, and save the environment to a file\nif [ \"$ssh_pid\" = \"\" ]; then\n        ssh_env=\"$(ssh-agent -s)\"\n        echo \"$ssh_env\" | head -n 2 | tee ~\u002F.ssh_agent_env > \u002Fdev\u002Fnull\nfi\n\n# Load the environment from the file\nif [ -f ~\u002F.ssh_agent_env ]; then\n        eval \"$(cat ~\u002F.ssh_agent_env)\"\nfi\n",[33,2226,2227,2247,2251,2256,2279,2295,2332,2336,2340,2345,2362,2374],{"__ignoreMap":71},[75,2228,2229,2232,2235,2238,2241,2244],{"class":77,"line":78},[75,2230,2231],{"class":81},"ssh_pid",[75,2233,2234],{"class":85},"=",[75,2236,2237],{"class":81},"$(",[75,2239,2240],{"class":208},"pidof",[75,2242,2243],{"class":212}," ssh-agent",[75,2245,2246],{"class":81},")\n",[75,2248,2249],{"class":77,"line":98},[75,2250,481],{"emptyLinePlaceholder":480},[75,2252,2253],{"class":77,"line":119},[75,2254,2255],{"class":460},"# If the agent is not running, start it, and save the environment to a file\n",[75,2257,2258,2260,2262,2264,2267,2269,2272,2275,2277],{"class":77,"line":484},[75,2259,2161],{"class":85},[75,2261,2164],{"class":81},[75,2263,2176],{"class":212},[75,2265,2266],{"class":81},"$ssh_pid",[75,2268,2176],{"class":212},[75,2270,2271],{"class":85}," =",[75,2273,2274],{"class":212}," \"\"",[75,2276,2179],{"class":81},[75,2278,2182],{"class":85},[75,2280,2281,2284,2286,2289,2291,2293],{"class":77,"line":490},[75,2282,2283],{"class":81},"        ssh_env",[75,2285,2234],{"class":85},[75,2287,2288],{"class":212},"\"$(",[75,2290,1995],{"class":208},[75,2292,260],{"class":224},[75,2294,2197],{"class":212},[75,2296,2297,2300,2302,2305,2307,2310,2313,2316,2319,2321,2324,2327,2329],{"class":77,"line":499},[75,2298,2299],{"class":224},"        echo",[75,2301,2170],{"class":212},[75,2303,2304],{"class":81},"$ssh_env",[75,2306,2176],{"class":212},[75,2308,2309],{"class":85}," |",[75,2311,2312],{"class":208}," head",[75,2314,2315],{"class":224}," -n",[75,2317,2318],{"class":224}," 2",[75,2320,2309],{"class":85},[75,2322,2323],{"class":208}," tee",[75,2325,2326],{"class":212}," ~\u002F.ssh_agent_env",[75,2328,812],{"class":85},[75,2330,2331],{"class":212}," \u002Fdev\u002Fnull\n",[75,2333,2334],{"class":77,"line":504},[75,2335,2210],{"class":85},[75,2337,2338],{"class":77,"line":510},[75,2339,481],{"emptyLinePlaceholder":480},[75,2341,2342],{"class":77,"line":519},[75,2343,2344],{"class":460},"# Load the environment from the file\n",[75,2346,2347,2349,2351,2354,2357,2360],{"class":77,"line":524},[75,2348,2161],{"class":85},[75,2350,2164],{"class":81},[75,2352,2353],{"class":85},"-f",[75,2355,2356],{"class":85}," ~",[75,2358,2359],{"class":81},"\u002F.ssh_agent_env ]; ",[75,2361,2182],{"class":85},[75,2363,2364,2367,2369,2371],{"class":77,"line":530},[75,2365,2366],{"class":224},"        eval",[75,2368,2190],{"class":212},[75,2370,691],{"class":208},[75,2372,2373],{"class":212}," ~\u002F.ssh_agent_env)\"\n",[75,2375,2377],{"class":77,"line":2376},12,[75,2378,2210],{"class":85},[21,2380,2381,2382,2384],{},"By setting up SSH keys and configuring ",[33,2383,1995],{},", you enhance the security and convenience of your remote connections. This setup eliminates the risk associated with passwords and streamlines your workflow, especially when handling multiple servers or automated tasks. Happy secure connecting!",[561,2386,2387],{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sPyeA, html code.shiki .sPyeA{--shiki-default:#6A737D;--shiki-dark:#8B949E}",{"title":71,"searchDepth":119,"depth":119,"links":2389},[2390,2391,2396],{"id":1999,"depth":98,"text":2000},{"id":2019,"depth":98,"text":2020,"children":2392},[2393,2394,2395],{"id":2023,"depth":119,"text":2024},{"id":2056,"depth":119,"text":2057},{"id":2088,"depth":119,"text":2089},{"id":2099,"depth":98,"text":2100,"children":2397},[2398,2399],{"id":2103,"depth":119,"text":2104},{"id":2128,"depth":119,"text":2129},"2024-05-10","Secure Shell (SSH) is a fundamental tool for developers, system administrators, and anyone requiring secure remote access to servers and systems. SSH keys provide a more secure method of authentication than traditional passwords by using a private-public key pair. This blog post guides you through setting up ed25519 SSH keys and configuring ssh-agent in both Windows Subsystem for Linux (WSL) and native Linux environments to enhance the security and simplicity of your remote connections.",{"type":579,"layout":580},"\u002Fblog\u002F2024-05-10-using-ssh-agent",{"title":1986,"description":2401},"blog\u002F2024-05-10-using-ssh-agent",[585,1982,586,587],"0RA47xQJ40-z1E80DchqB_jM5yMMJJksFWBu5UfVQ7I",{"id":2409,"title":2410,"authors":2411,"body":2412,"date":2648,"description":2649,"extension":577,"meta":2650,"navigation":480,"path":2651,"seo":2652,"stem":2653,"tags":2654,"toc":6,"__hash__":2655},"blog\u002Fblog\u002F2024-05-08-howto-migrate-a-monogdb.md","How to Migrate Data from One MongoDB Database to Another",[15,16],{"type":18,"value":2413,"toc":2639},[2414,2425,2429,2432,2446,2449,2456,2465,2480,2483,2504,2507,2513,2524,2547,2550,2579,2588,2592,2631,2633,2636],[21,2415,2416,2417,2420,2421,2424],{},"When working with MongoDB, there are times when you need to migrate data from one database to another. This could be for backup purposes, testing, or moving a production database to a new environment. In this post, I'll guide you through the process of dumping data from a MongoDB database named ",[33,2418,2419],{},"db1"," and restoring it to another database named ",[33,2422,2423],{},"db2",". This is a common task for database administrators and developers alike.",[25,2426,2428],{"id":2427},"overview-of-tools-used","Overview of Tools Used",[21,2430,2431],{},"To perform this operation, we will use two MongoDB tools:",[49,2433,2434,2440],{},[52,2435,2436,2439],{},[415,2437,2438],{},"mongodump",": This tool is used to export the contents of a MongoDB database into a set of BSON files.",[52,2441,2442,2445],{},[415,2443,2444],{},"mongorestore",": This tool is used to import the contents of a BSON file dump into a MongoDB database.",[21,2447,2448],{},"Both tools are part of the MongoDB database tools package and can be executed from the command line.",[25,2450,2452,2453,2455],{"id":2451},"step-1-dump-the-db1-database","Step 1: Dump the ",[33,2454,2419],{}," Database",[21,2457,2458,2459,2461,2462,2464],{},"The first step in migrating your database is to create a backup of the ",[33,2460,2419],{}," database. This is done using the ",[33,2463,2438],{}," command.",[66,2466,2468],{"className":199,"code":2467,"language":201,"meta":71,"style":71},"mongodump --db=db1 --archive=db1.dump\n",[33,2469,2470],{"__ignoreMap":71},[75,2471,2472,2474,2477],{"class":77,"line":78},[75,2473,2438],{"class":208},[75,2475,2476],{"class":224}," --db=db1",[75,2478,2479],{"class":224}," --archive=db1.dump\n",[21,2481,2482],{},"Here’s what each part of this command does:",[49,2484,2485,2494],{},[52,2486,2487,2490,2491,2493],{},[33,2488,2489],{},"--db=db1",": This specifies that we are dumping the ",[33,2492,2419],{}," database.",[52,2495,2496,2499,2500,2503],{},[33,2497,2498],{},"--out=\u002Fpath\u002Fto\u002Fdump",": This is the directory where the dump files will be saved. You should replace ",[33,2501,2502],{},"\u002Fpath\u002Fto\u002Fdump"," with the actual path where you want the backup to be stored.",[21,2505,2506],{},"Make sure MongoDB is running, and you have the necessary permissions to access and dump the database.",[25,2508,2510,2511,2455],{"id":2509},"step-2-restore-the-dump-to-the-db2-database","Step 2: Restore the Dump to the ",[33,2512,2423],{},[21,2514,2515,2516,2518,2519,2521,2522,615],{},"Once you have a dump of the ",[33,2517,2419],{}," database, the next step is to restore this data to the ",[33,2520,2423],{}," database using ",[33,2523,2444],{},[66,2525,2527],{"className":199,"code":2526,"language":201,"meta":71,"style":71},"mongorestore --nsFrom='db1.*' --nsTo='db2.*' --archive=db1.dump\n",[33,2528,2529],{"__ignoreMap":71},[75,2530,2531,2533,2536,2539,2542,2545],{"class":77,"line":78},[75,2532,2444],{"class":208},[75,2534,2535],{"class":224}," --nsFrom=",[75,2537,2538],{"class":212},"'db1.*'",[75,2540,2541],{"class":224}," --nsTo=",[75,2543,2544],{"class":212},"'db2.*'",[75,2546,2479],{"class":224},[21,2548,2549],{},"Let’s break down this command:",[49,2551,2552,2563,2571],{},[52,2553,2554,2557,2558,2560,2561,2493],{},[33,2555,2556],{},"--nsFrom='db1.*'",": This tells ",[33,2559,2444],{}," that we want to restore from the ",[33,2562,2419],{},[52,2564,2565,2568,2569,615],{},[33,2566,2567],{},"--nsTo='db2.*'",": This specifies that the target database for the restore is ",[33,2570,2423],{},[52,2572,2573,2576,2577,615],{},[33,2574,2575],{},"\u002Fpath\u002Fto\u002Fdump\u002Fdb1",": This is the path to the directory containing the dumped files from ",[33,2578,2419],{},[21,2580,2581,2582,2584,2585,2587],{},"This command will import all collections from the ",[33,2583,2419],{}," database into the ",[33,2586,2423],{}," database, effectively cloning the data.",[25,2589,2591],{"id":2590},"important-considerations","Important Considerations",[49,2593,2594,2616,2625],{},[52,2595,2596,2599,2600,555,2603,2606,2607,2610,2611,36,2613,2615],{},[415,2597,2598],{},"Authentication",": If your MongoDB instance uses authentication, append ",[33,2601,2602],{},"--username",[33,2604,2605],{},"--password",", and ",[33,2608,2609],{},"--authenticationDatabase"," options to both ",[33,2612,2438],{},[33,2614,2444],{}," commands.",[52,2617,2618,2621,2622,2624],{},[415,2619,2620],{},"Data Integrity",": Ensure there are no active writes to the ",[33,2623,2419],{}," database during the dump to maintain data integrity.",[52,2626,2627,2630],{},[415,2628,2629],{},"Server Performance",": Both dumping and restoring can be resource-intensive operations. Plan to perform these during off-peak hours if possible.",[25,2632,541],{"id":540},[21,2634,2635],{},"By following these steps, you can easily migrate data from one MongoDB database to another. This method is not only useful for backups but also for replicating data across different environments. Always ensure you test these operations in a development or staging environment before applying them in production to avoid any data loss.",[561,2637,2638],{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}",{"title":71,"searchDepth":119,"depth":119,"links":2640},[2641,2642,2644,2646,2647],{"id":2427,"depth":98,"text":2428},{"id":2451,"depth":98,"text":2643},"Step 1: Dump the db1 Database",{"id":2509,"depth":98,"text":2645},"Step 2: Restore the Dump to the db2 Database",{"id":2590,"depth":98,"text":2591},{"id":540,"depth":98,"text":541},"2024-05-08","When working with MongoDB, there are times when you need to migrate data from one database to another. This could be for backup purposes, testing, or moving a production database to a new environment. In this post, I'll guide you through the process of dumping data from a MongoDB database named db1 and restoring it to another database named db2. This is a common task for database administrators and developers alike.",{"type":579,"layout":580},"\u002Fblog\u002F2024-05-08-howto-migrate-a-monogdb",{"title":2410,"description":2649},"blog\u002F2024-05-08-howto-migrate-a-monogdb",[585,1982],"bn7CFPp921cPhuEg0FVmc-ZurrLNgDxDCg4VlRLrviU",{"id":2657,"title":2658,"authors":2659,"body":2660,"date":2795,"description":2796,"extension":577,"meta":2797,"navigation":480,"path":2798,"seo":2799,"stem":2800,"tags":2801,"toc":6,"__hash__":2802},"blog\u002Fblog\u002F2024-02-06-using-journctl.md","Using journalctl",[15,16],{"type":18,"value":2661,"toc":2790},[2662,2675,2679,2691,2705,2708,2712,2715,2732,2736,2753,2768,2771,2774,2788],[21,2663,2664,2667,2668,2671,2672,2674],{},[33,2665,2666],{},"journalctl"," is a powerful command-line utility for querying and displaying messages from the journal, managed by ",[33,2669,2670],{},"systemd-journald",". It is commonly used on Linux systems to retrieve and analyse log data. This guide aims to provide instructions on using ",[33,2673,2666],{}," based on the questions you've previously asked.",[25,2676,2678],{"id":2677},"getting-the-last-1000-lines","Getting the Last 1000 Lines",[21,2680,2681,2682,2147,2685,2688,2689,2151],{},"To retrieve the last 1000 lines from the journal logs, you can use the ",[33,2683,2684],{},"-n",[33,2686,2687],{},"--lines"," option with ",[33,2690,2666],{},[66,2692,2694],{"className":199,"code":2693,"language":201,"meta":71,"style":71},"journalctl -n 1000\n\n",[33,2695,2696],{"__ignoreMap":71},[75,2697,2698,2700,2702],{"class":77,"line":78},[75,2699,2666],{"class":208},[75,2701,2315],{"class":224},[75,2703,2704],{"class":224}," 1000\n",[21,2706,2707],{},"This command will display the most recent 1000 lines of the journal logs",[25,2709,2711],{"id":2710},"getting-the-last-100-lines-and-then-following-the-log","Getting the last 100 lines and then following the log",[21,2713,2714],{},"To get the last 100 lines and follow live updates, you can combine the -n option with the -f or --follow option:",[66,2716,2718],{"className":199,"code":2717,"language":201,"meta":71,"style":71},"journalctl -n 100 -f\n",[33,2719,2720],{"__ignoreMap":71},[75,2721,2722,2724,2726,2729],{"class":77,"line":78},[75,2723,2666],{"class":208},[75,2725,2315],{"class":224},[75,2727,2728],{"class":224}," 100",[75,2730,2731],{"class":224}," -f\n",[25,2733,2735],{"id":2734},"display-logs-since-a-specific-time","Display logs since a specific time.",[21,2737,2738,2739,2742,2743,2746,2747,2147,2750,2151],{},"If you want to see logs starting from a particular point in time, you can use the ",[33,2740,2741],{},"--since"," option. The time format can be flexible, such as ",[33,2744,2745],{},"YYYY-MM-DD HH:MM:SS",", or even simpler like ",[33,2748,2749],{},"yesterday",[33,2751,2752],{},"1 hour ago",[66,2754,2756],{"className":199,"code":2755,"language":201,"meta":71,"style":71},"journalctl --since \"2024-02-01 14:00:00\"\n",[33,2757,2758],{"__ignoreMap":71},[75,2759,2760,2762,2765],{"class":77,"line":78},[75,2761,2666],{"class":208},[75,2763,2764],{"class":224}," --since",[75,2766,2767],{"class":212}," \"2024-02-01 14:00:00\"\n",[21,2769,2770],{},"This command will display logs from February 1st, 2024, starting at 2:00 PM.",[21,2772,2773],{},"Alternatively, to view logs from the last hour:",[66,2775,2777],{"className":199,"code":2776,"language":201,"meta":71,"style":71},"journalctl --since \"1 hour ago\"\n",[33,2778,2779],{"__ignoreMap":71},[75,2780,2781,2783,2785],{"class":77,"line":78},[75,2782,2666],{"class":208},[75,2784,2764],{"class":224},[75,2786,2787],{"class":212}," \"1 hour ago\"\n",[561,2789,2638],{},{"title":71,"searchDepth":119,"depth":119,"links":2791},[2792,2793,2794],{"id":2677,"depth":98,"text":2678},{"id":2710,"depth":98,"text":2711},{"id":2734,"depth":98,"text":2735},"2024-02-06","journalctl is a powerful command-line utility for querying and displaying messages from the journal, managed by systemd-journald. It is commonly used on Linux systems to retrieve and analyse log data. This guide aims to provide instructions on using journalctl based on the questions you've previously asked.",{"type":579,"layout":580},"\u002Fblog\u002F2024-02-06-using-journctl",{"title":2658,"description":2796},"blog\u002F2024-02-06-using-journctl",[585],"XXaztZn7AsnemHZDIChx0CIc900pUeMJsMeuvWkmHUw",{"id":2804,"title":2805,"authors":2806,"body":2807,"date":2935,"description":2936,"extension":577,"meta":2937,"navigation":480,"path":2938,"seo":2939,"stem":2940,"tags":2941,"toc":6,"__hash__":2943},"blog\u002Fblog\u002F2023-10-03-using-git-reset.md","Using git reset",[15,16],{"type":18,"value":2808,"toc":2930},[2809,2817,2821,2824,2864,2868,2871,2891,2903,2907,2916,2918,2927],[21,2810,439,2811,2814,2815,615],{},[33,2812,2813],{},"git reset"," command in Git allows you to undo commits and manipulate the state of your repository. It can be a tricky and potentially dangerous tool if used incorrectly, but it's very useful for backing out changes, correcting mistakes, and rewriting commit history. This post will cover common uses cases and options for ",[33,2816,2813],{},[25,2818,2820],{"id":2819},"when-would-you-use-git-reset","When would you use git reset?",[21,2822,2823],{},"Some examples of when git reset comes in handy:",[49,2825,2826,2833,2840,2846,2853],{},[52,2827,2828,2829,2832],{},"Undoing the last commit - To undo the changes from your most recent commit, use ",[33,2830,2831],{},"git reset --hard HEAD~1",". This will move the HEAD pointer back one commit and discard those changes.",[52,2834,2835,2836,2839],{},"Undoing the last commit, but keeping the changes - To undo the changes from your most recent commit, use ",[33,2837,2838],{},"git reset --soft HEAD~1",". This will move the HEAD pointer back one commit and but keep the changes, the changes remain staged, ready to be committed.",[52,2841,2842,2843,615],{},"Discarding unstaged changes - If you've made changes to files but haven't staged or committed them yet, you can discard the changes with ",[33,2844,2845],{},"git reset --hard HEAD",[52,2847,2848,2849,2852],{},"Resetting to an older commit - To undo a series of commits, you can reset back to a previous commit by using its commit hash. For example, ",[33,2850,2851],{},"git reset --hard 0b7e6123"," will reset to that specific commit, undoing all commits after it.",[52,2854,2855,2856,2859,2860,2863],{},"Unstaging files - To unstage files you previously staged with ",[33,2857,2858],{},"git add",", use ",[33,2861,2862],{},"git reset HEAD \u003Cfile>",". The changes will be preserved but removed from the staging area.",[25,2865,2867],{"id":2866},"git-reset-options","Git reset options",[21,2869,2870],{},"The main options for git reset are:",[49,2872,2873,2879,2885],{},[52,2874,2875,2878],{},[33,2876,2877],{},"--soft"," - Undoes commits but preserves changes in working directory.",[52,2880,2881,2884],{},[33,2882,2883],{},"--mixed"," - Default option. Undoes commits and unstages changes, but keeps them in working directory.",[52,2886,2887,2890],{},[33,2888,2889],{},"--hard"," - Completely undoes commits and any changes, leaving the working directory in a \"clean\" state.",[21,2892,2893,2896,2897,2899,2900],{},[642,2894],{"value":2895},"\u003C danger >"," Since ",[33,2898,2889],{}," deletes uncommitted changes, make sure your work is backed up before using this option. ",[642,2901],{"value":2902},"\u003C \u002Fdanger >",[25,2904,2906],{"id":2905},"resetting-vs-reverting","Resetting vs reverting",[21,2908,2909,2910,36,2912,2915],{},"It's important to understand the difference between ",[33,2911,2813],{},[33,2913,2914],{},"git revert",". Reset erases commits from the current branch, while revert creates a new commit that reverts or rolls back an existing commit. Generally, revert is safer and avoids deleting work that hasn't been pushed yet.",[984,2917,541],{"id":540},[21,2919,2920,2921,2923,2924,2926],{},"Git reset is a powerful command for undoing changes and altering commit history. Understanding how it works helps you recover from mistakes and maintain a linear project history. Be very careful when using ",[33,2922,2813],{}," with the ",[33,2925,2889],{}," option to avoid losing important work. Following best practices for using git reset will help you wield this tool more safely!",[21,2928,2929],{},"Let me know if you would like me to expand or modify this blog post draft. I can add more examples or clarify any of the concepts.",{"title":71,"searchDepth":119,"depth":119,"links":2931},[2932,2933,2934],{"id":2819,"depth":98,"text":2820},{"id":2866,"depth":98,"text":2867},{"id":2905,"depth":98,"text":2906},"2023-10-03","The git reset command in Git allows you to undo commits and manipulate the state of your repository. It can be a tricky and potentially dangerous tool if used incorrectly, but it's very useful for backing out changes, correcting mistakes, and rewriting commit history. This post will cover common uses cases and options for git reset.",{"type":579,"layout":580},"\u002Fblog\u002F2023-10-03-using-git-reset",{"title":2805,"description":2936},"blog\u002F2023-10-03-using-git-reset",[587,585,586,714,2942],"programming","FER9C2DaLvkE5fkCJvQ2qsC_jHC2Aa0jDrcUAPC-JHg",{"id":2945,"title":2946,"authors":2947,"body":2948,"date":3128,"description":2952,"extension":577,"meta":3129,"navigation":480,"path":3130,"seo":3131,"stem":3132,"tags":3133,"toc":480,"__hash__":3135},"blog\u002Fblog\u002F2023-09-10-encrypting-disk-image.md","Setting up an encrypted disk image on Debian 12 with Cryptsetup",[15,16],{"type":18,"value":2949,"toc":3119},[2950,2953,2955,2958,2978,2982,2989,2995,3002,3006,3016,3022,3025,3029,3032,3038,3041,3045,3048,3054,3061,3065,3068,3074,3081,3085,3088,3094,3097,3103,3106,3109,3112],[21,2951,2952],{},"Encrypted disk images allow you to securely store sensitive files on your Linux system. The contents of the image can only be accessed by providing the correct passphrase. In this post, we'll walk through creating an encrypted image using the cryptsetup utility on Debian 12.",[25,2954,44],{"id":43},[21,2956,2957],{},"To follow along on Debian 12, you'll need:",[49,2959,2960,2972,2975],{},[52,2961,439,2962,2965,2966],{},[33,2963,2964],{},"cryptsetup"," package installed:",[66,2967,2970],{"className":2968,"code":2969,"language":327},[325],"sudo apt install cryptsetup\n",[33,2971,2969],{"__ignoreMap":71},[52,2973,2974],{},"Enough free disk space to store the encrypted image file",[52,2976,2977],{},"The passphrase you want to use to encrypt the image",[25,2979,2981],{"id":2980},"create-the-image-file","Create the Image File",[21,2983,2984,2985,2988],{},"First, we need to create a file that will be used for our encrypted volume. The ",[33,2986,2987],{},"dd"," command makes this easy. Let's create a 1GB image file:",[66,2990,2993],{"className":2991,"code":2992,"language":327},[325],"dd if=\u002Fdev\u002Fzero of=secret.img bs=1M count=1024\n",[33,2994,2992],{"__ignoreMap":71},[21,2996,2997,2998,3001],{},"This creates a file called ",[33,2999,3000],{},"secret.img"," filled with 1GB of null bytes to use as the container for our encrypted filesystem.",[25,3003,3005],{"id":3004},"initialise-encryption","Initialise Encryption",[21,3007,3008,3009,3011,3012,3015],{},"With our image file created, we can now set up encryption on it using ",[33,3010,2964],{},". The ",[33,3013,3014],{},"luksFormat"," option initialises the encryption parameters:",[66,3017,3020],{"className":3018,"code":3019,"language":327},[325],"sudo cryptsetup -y luksFormat secret.img\n",[33,3021,3019],{"__ignoreMap":71},[21,3023,3024],{},"You'll be prompted to enter and confirm a passphrase. This passphrase will be required to unlock and access the contents of the image in the future. You can also specify the cipher and key size if desired.",[25,3026,3028],{"id":3027},"open-the-encrypted-device","Open the Encrypted Device",[21,3030,3031],{},"Next, we need to \"open\" the encrypted image which maps it to a logical device mapper node. This allows accessing the encrypted volume similar to a normal block device:",[66,3033,3036],{"className":3034,"code":3035,"language":327},[325],"sudo cryptsetup luksOpen secret.img secret\n",[33,3037,3035],{"__ignoreMap":71},[21,3039,3040],{},"Enter the passphrase when prompted to unlock the device.",[25,3042,3044],{"id":3043},"format-the-mapped-device","Format the Mapped Device",[21,3046,3047],{},"Now that the crypt device is unlocked and mapped, we can format it with a filesystem:",[66,3049,3052],{"className":3050,"code":3051,"language":327},[325],"sudo mkfs.ext4 \u002Fdev\u002Fmapper\u002Fsecret\n",[33,3053,3051],{"__ignoreMap":71},[21,3055,3056,3057,3060],{},"This formats the mapped device ",[33,3058,3059],{},"\u002Fdev\u002Fmapper\u002Fsecret"," with the ext4 filesystem.",[25,3062,3064],{"id":3063},"mount-the-image","Mount the Image",[21,3066,3067],{},"With the device formatted, we can now mount it to access the encrypted contents! First create a mount point and then mount the device:",[66,3069,3072],{"className":3070,"code":3071,"language":327},[325],"sudo mkdir \u002Fmnt\u002Fsecret\nsudo mount \u002Fdev\u002Fmapper\u002Fsecret \u002Fmnt\u002Fsecret\n",[33,3073,3071],{"__ignoreMap":71},[21,3075,3076,3077,3080],{},"You can now read and write files under ",[33,3078,3079],{},"\u002Fmnt\u002Fsecret"," normally and they will be securely stored encrypted.",[25,3082,3084],{"id":3083},"unmount-and-close-the-device","Unmount and Close the Device",[21,3086,3087],{},"To finish using the encrypted volume for now, first unmount it:",[66,3089,3092],{"className":3090,"code":3091,"language":327},[325],"sudo umount \u002Fmnt\u002Fsecret\n",[33,3093,3091],{"__ignoreMap":71},[21,3095,3096],{},"Then close the device mapping:",[66,3098,3101],{"className":3099,"code":3100,"language":327},[325],"sudo cryptsetup luksClose secret\n",[33,3102,3100],{"__ignoreMap":71},[21,3104,3105],{},"This will close the mapped device and shut down access to the encrypted volume. You'll have to unlock it again later to mount it.",[21,3107,3108],{},"At this point the image contents are safely encrypted at rest when not in use. To access the files again, repeat steps 3-5 to unlock, map, mount and use the image.",[21,3110,3111],{},"That covers the basics of setting up disk encryption using Linux's built-in Cryptsetup tool on Debian 12. With a few simple commands, you can create encrypted containers to keep your data secure and private. The encrypted data is inaccessible without the passphrase, providing effective defence against unauthorised access.",[21,3113,3114,3115,3118],{},"There are many other options available with Cryptsetup like keyfiles, multiple key encryption, and resizing images. Be sure to read ",[33,3116,3117],{},"man cryptsetup"," for more details on these advanced features.",{"title":71,"searchDepth":98,"depth":98,"links":3120},[3121,3122,3123,3124,3125,3126,3127],{"id":43,"depth":98,"text":44},{"id":2980,"depth":98,"text":2981},{"id":3004,"depth":98,"text":3005},{"id":3027,"depth":98,"text":3028},{"id":3043,"depth":98,"text":3044},{"id":3063,"depth":98,"text":3064},{"id":3083,"depth":98,"text":3084},"2023-09-10",{"type":579,"layout":580},"\u002Fblog\u002F2023-09-10-encrypting-disk-image",{"title":2946,"description":2952},"blog\u002F2023-09-10-encrypting-disk-image",[3134,587,585],"debian","ZYObvQgCancVwi0gziZRYB-OiYYDGjq7Q6ScIgriQUA",{"id":3137,"title":3138,"authors":3139,"body":3140,"date":3351,"description":3144,"extension":577,"meta":3352,"navigation":480,"path":3353,"seo":3354,"stem":3355,"tags":3356,"toc":480,"__hash__":3358},"blog\u002Fblog\u002F2023-09-01-optional-chaining-in-javascript.md","Optional Chaining in JavaScript",[15,16],{"type":18,"value":3141,"toc":3345},[3142,3145,3149,3152,3155,3172,3186,3190,3193,3196,3270,3273,3289,3302,3306,3309,3313,3316,3336,3339,3342],[21,3143,3144],{},"Optional chaining is a feature introduced in ECMAScript 2020 that allows for accessing properties on an object without having to explicitly check if the object is null or undefined first. This results in less code and improves readability. Let's take a look at what optional chaining is and how to use it.",[25,3146,3148],{"id":3147},"what-is-optional-chaining","What is Optional Chaining?",[21,3150,3151],{},"Optional chaining allows you to access properties on an object only if that object is not null or undefined. If the object is null or undefined, instead of throwing an error, the expression short circuits and returns undefined.",[21,3153,3154],{},"Here is the syntax:",[66,3156,3160],{"className":3157,"code":3158,"language":3159,"meta":71,"style":71},"language-js shiki shiki-themes github-light github-dark-default","obj?.prop\nobj?.[expr] \n","js",[33,3161,3162,3167],{"__ignoreMap":71},[75,3163,3164],{"class":77,"line":78},[75,3165,3166],{"class":81},"obj?.prop\n",[75,3168,3169],{"class":77,"line":98},[75,3170,3171],{"class":81},"obj?.[expr]\n",[21,3173,3174,3175,3178,3179,3181,3182,3185],{},"This checks if ",[33,3176,3177],{},"obj"," exists before accessing the property. If ",[33,3180,3177],{}," is nullish (null or undefined), ",[33,3183,3184],{},"obj.prop"," will evaluate to undefined instead of throwing an error.",[25,3187,3189],{"id":3188},"why-use-optional-chaining","Why Use Optional Chaining?",[21,3191,3192],{},"Optional chaining improves checking for nullish values when accessing nested properties.",[21,3194,3195],{},"Without optional chaining, you have to explicitly check if the object exists before accessing nested properties:",[66,3197,3199],{"className":3157,"code":3198,"language":3159,"meta":71,"style":71},"let result = null;\n\nif (obj != null) {\n  if (obj.child != null) {\n    result = obj.child.name;  \n  }\n}\n",[33,3200,3201,3217,3221,3236,3250,3260,3265],{"__ignoreMap":71},[75,3202,3203,3206,3209,3211,3214],{"class":77,"line":78},[75,3204,3205],{"class":85},"let",[75,3207,3208],{"class":81}," result ",[75,3210,2234],{"class":85},[75,3212,3213],{"class":224}," null",[75,3215,3216],{"class":81},";\n",[75,3218,3219],{"class":77,"line":98},[75,3220,481],{"emptyLinePlaceholder":480},[75,3222,3223,3225,3228,3231,3233],{"class":77,"line":119},[75,3224,2161],{"class":85},[75,3226,3227],{"class":81}," (obj ",[75,3229,3230],{"class":85},"!=",[75,3232,3213],{"class":224},[75,3234,3235],{"class":81},") {\n",[75,3237,3238,3241,3244,3246,3248],{"class":77,"line":484},[75,3239,3240],{"class":85},"  if",[75,3242,3243],{"class":81}," (obj.child ",[75,3245,3230],{"class":85},[75,3247,3213],{"class":224},[75,3249,3235],{"class":81},[75,3251,3252,3255,3257],{"class":77,"line":490},[75,3253,3254],{"class":81},"    result ",[75,3256,2234],{"class":85},[75,3258,3259],{"class":81}," obj.child.name;  \n",[75,3261,3262],{"class":77,"line":499},[75,3263,3264],{"class":81},"  }\n",[75,3266,3267],{"class":77,"line":504},[75,3268,3269],{"class":81},"}\n",[21,3271,3272],{},"With optional chaining, this becomes:",[66,3274,3276],{"className":3157,"code":3275,"language":3159,"meta":71,"style":71},"let result = obj?.child?.name;\n",[33,3277,3278],{"__ignoreMap":71},[75,3279,3280,3282,3284,3286],{"class":77,"line":78},[75,3281,3205],{"class":85},[75,3283,3208],{"class":81},[75,3285,2234],{"class":85},[75,3287,3288],{"class":81}," obj?.child?.name;\n",[21,3290,3291,3292,2147,3294,3297,3298,3301],{},"The code is much cleaner! If ",[33,3293,3177],{},[33,3295,3296],{},"child"," are nullish, ",[33,3299,3300],{},"result"," will simply be undefined instead of an error being thrown.",[25,3303,3305],{"id":3304},"browser-support","Browser Support",[21,3307,3308],{},"Optional chaining has good browser support but is not yet supported in Internet Explorer. It can be used in production if IE support is not needed.",[25,3310,3312],{"id":3311},"use-cases","Use Cases",[21,3314,3315],{},"Optional chaining is useful whenever you need to access properties of an object that might be nullish. Some examples:",[49,3317,3318,3324,3330],{},[52,3319,3320,3321],{},"Accessing nested properties: ",[33,3322,3323],{},"user?.address?.city",[52,3325,3326,3327],{},"Calling functions that may not exist: ",[33,3328,3329],{},"obj.callback?.()",[52,3331,3332,3333],{},"Accessing arrays that may be undefined: ",[33,3334,3335],{},"arr?.[0]",[21,3337,3338],{},"It avoids having to check each reference for null\u002Fundefined before accessing properties.",[21,3340,3341],{},"Optional chaining is a great feature to clean up code that accesses nested properties. It avoids verbose null checks and makes code more readable. Support is decent in modern browsers.",[561,3343,3344],{},"html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}",{"title":71,"searchDepth":98,"depth":98,"links":3346},[3347,3348,3349,3350],{"id":3147,"depth":98,"text":3148},{"id":3188,"depth":98,"text":3189},{"id":3304,"depth":98,"text":3305},{"id":3311,"depth":98,"text":3312},"2023-09-01",{"type":579,"layout":580},"\u002Fblog\u002F2023-09-01-optional-chaining-in-javascript",{"title":3138,"description":3144},"blog\u002F2023-09-01-optional-chaining-in-javascript",[2942,3357],"javascript","3nve6ZEnP39RYJdK5i02s1c8h1DzMcwDOaZ0i4QNcEo",{"id":3360,"title":3361,"authors":3362,"body":3363,"date":3920,"description":71,"extension":577,"meta":3921,"navigation":480,"path":3922,"seo":3923,"stem":3924,"tags":3925,"toc":480,"__hash__":3927},"blog\u002Fblog\u002F2023-08-14-fizzbuzzin-py-and-js.md","Solving FizzBuzz in Python and JavaScript",[15,16],{"type":18,"value":3364,"toc":3915},[3365,3369,3372,3375,3379,3382,3606,3609,3660,3663,3669,3672,3676,3679,3881,3884,3900,3903,3909,3912],[25,3366,3368],{"id":3367},"what-is-fizzbuzz","What is FizzBuzz?",[21,3370,3371],{},"FizzBuzz is a simple coding problem used in interviews to test basic programming skills. The task is to print the numbers from 1 to a given number, but print \"Fizz\" instead if the number is divisible by 3, \"Buzz\" instead if divisible by 5, and \"FizzBuzz\" if divisible by both 3 and 5.",[21,3373,3374],{},"The origins of FizzBuzz are unclear, but it has become a popular interview screening technique to quickly assess a candidate's ability to write basic code. Though simple, it requires knowledge of loops, conditionals, and modular division.",[25,3376,3378],{"id":3377},"python-solution","Python solution",[21,3380,3381],{},"Here is how to implement FizzBuzz in Python:",[66,3383,3387],{"className":3384,"code":3385,"language":3386,"meta":71,"style":71},"language-python shiki shiki-themes github-light github-dark-default","import sys\n\ndef fizzbuzz(end):\n  for num in range(1, end+1):\n    if num % 3 == 0 and num % 5 == 0:\n      print(\"FizzBuzz\")\n    elif num % 3 == 0:\n      print(\"Fizz\")\n    elif num % 5 == 0:\n      print(\"Buzz\")\n    else:\n      print(num)\n\nif __name__ == \"__main__\":\n  end = int(sys.argv[1])\n  fizzbuzz(end)\n","python",[33,3388,3389,3397,3401,3413,3444,3480,3492,3509,3520,3536,3547,3554,3561,3566,3581,3600],{"__ignoreMap":71},[75,3390,3391,3394],{"class":77,"line":78},[75,3392,3393],{"class":85},"import",[75,3395,3396],{"class":81}," sys\n",[75,3398,3399],{"class":77,"line":98},[75,3400,481],{"emptyLinePlaceholder":480},[75,3402,3403,3406,3410],{"class":77,"line":119},[75,3404,3405],{"class":85},"def",[75,3407,3409],{"class":3408},"sVjZL"," fizzbuzz",[75,3411,3412],{"class":81},"(end):\n",[75,3414,3415,3418,3421,3424,3427,3430,3433,3436,3439,3441],{"class":77,"line":484},[75,3416,3417],{"class":85},"  for",[75,3419,3420],{"class":81}," num ",[75,3422,3423],{"class":85},"in",[75,3425,3426],{"class":224}," range",[75,3428,3429],{"class":81},"(",[75,3431,3432],{"class":224},"1",[75,3434,3435],{"class":81},", end",[75,3437,3438],{"class":85},"+",[75,3440,3432],{"class":224},[75,3442,3443],{"class":81},"):\n",[75,3445,3446,3449,3451,3454,3457,3460,3463,3466,3468,3470,3473,3475,3477],{"class":77,"line":490},[75,3447,3448],{"class":85},"    if",[75,3450,3420],{"class":81},[75,3452,3453],{"class":85},"%",[75,3455,3456],{"class":224}," 3",[75,3458,3459],{"class":85}," ==",[75,3461,3462],{"class":224}," 0",[75,3464,3465],{"class":85}," and",[75,3467,3420],{"class":81},[75,3469,3453],{"class":85},[75,3471,3472],{"class":224}," 5",[75,3474,3459],{"class":85},[75,3476,3462],{"class":224},[75,3478,3479],{"class":81},":\n",[75,3481,3482,3485,3487,3490],{"class":77,"line":499},[75,3483,3484],{"class":224},"      print",[75,3486,3429],{"class":81},[75,3488,3489],{"class":212},"\"FizzBuzz\"",[75,3491,2246],{"class":81},[75,3493,3494,3497,3499,3501,3503,3505,3507],{"class":77,"line":504},[75,3495,3496],{"class":85},"    elif",[75,3498,3420],{"class":81},[75,3500,3453],{"class":85},[75,3502,3456],{"class":224},[75,3504,3459],{"class":85},[75,3506,3462],{"class":224},[75,3508,3479],{"class":81},[75,3510,3511,3513,3515,3518],{"class":77,"line":510},[75,3512,3484],{"class":224},[75,3514,3429],{"class":81},[75,3516,3517],{"class":212},"\"Fizz\"",[75,3519,2246],{"class":81},[75,3521,3522,3524,3526,3528,3530,3532,3534],{"class":77,"line":519},[75,3523,3496],{"class":85},[75,3525,3420],{"class":81},[75,3527,3453],{"class":85},[75,3529,3472],{"class":224},[75,3531,3459],{"class":85},[75,3533,3462],{"class":224},[75,3535,3479],{"class":81},[75,3537,3538,3540,3542,3545],{"class":77,"line":524},[75,3539,3484],{"class":224},[75,3541,3429],{"class":81},[75,3543,3544],{"class":212},"\"Buzz\"",[75,3546,2246],{"class":81},[75,3548,3549,3552],{"class":77,"line":530},[75,3550,3551],{"class":85},"    else",[75,3553,3479],{"class":81},[75,3555,3556,3558],{"class":77,"line":2376},[75,3557,3484],{"class":224},[75,3559,3560],{"class":81},"(num)\n",[75,3562,3564],{"class":77,"line":3563},13,[75,3565,481],{"emptyLinePlaceholder":480},[75,3567,3569,3571,3574,3576,3579],{"class":77,"line":3568},14,[75,3570,2161],{"class":85},[75,3572,3573],{"class":224}," __name__",[75,3575,3459],{"class":85},[75,3577,3578],{"class":212}," \"__main__\"",[75,3580,3479],{"class":81},[75,3582,3584,3587,3589,3592,3595,3597],{"class":77,"line":3583},15,[75,3585,3586],{"class":81},"  end ",[75,3588,2234],{"class":85},[75,3590,3591],{"class":224}," int",[75,3593,3594],{"class":81},"(sys.argv[",[75,3596,3432],{"class":224},[75,3598,3599],{"class":81},"])\n",[75,3601,3603],{"class":77,"line":3602},16,[75,3604,3605],{"class":81},"  fizzbuzz(end)\n",[21,3607,3608],{},"Let's break this down:",[49,3610,3611,3617,3623,3642,3648,3654],{},[52,3612,3613,3616],{},[33,3614,3615],{},"import sys"," imports the sys module to access command line arguments",[52,3618,3619,3622],{},[33,3620,3621],{},"def fizzbuzz(end):"," defines the fizzbuzz function",[52,3624,3625,3626],{},"The main logic is inside the function:\n",[49,3627,3628,3631,3636,3639],{},[52,3629,3630],{},"Loop from 1 to end",[52,3632,3633,3634],{},"Check for divisibility by 3 and 5 using ",[33,3635,3453],{},[52,3637,3638],{},"Print FizzBuzz, Fizz or Buzz if conditions met",[52,3640,3641],{},"Else, print the number",[52,3643,3644,3647],{},[33,3645,3646],{},"if __name__ == \"__main__\":"," runs the code only if executed directly",[52,3649,3650,3653],{},[33,3651,3652],{},"end = int(sys.argv[1])"," gets the end number from command line",[52,3655,3656,3659],{},[33,3657,3658],{},"fizzbuzz(end)"," calls the function",[21,3661,3662],{},"To run it:",[66,3664,3667],{"className":3665,"code":3666,"language":327},[325],"python fizzbuzz.py 100\n",[33,3668,3666],{"__ignoreMap":71},[21,3670,3671],{},"This structure allows the FizzBuzz logic to be reused by importing the script.",[25,3673,3675],{"id":3674},"javascript-solution","JavaScript solution",[21,3677,3678],{},"Here is how to implement FizzBuzz in JavaScript using Node.js:",[66,3680,3682],{"className":3157,"code":3681,"language":3159,"meta":71,"style":71},"const end = parseInt(process.argv[2]);\n\nfor (let i = 1; i \u003C= end; i++) {\n  if (i % 3 === 0 && i % 5 === 0) {\n    console.log(\"FizzBuzz\");\n  } else if (i % 3 === 0) {\n    console.log(\"Fizz\");\n  } else if (i % 5 === 0) {\n    console.log(\"Buzz\");\n  } else {\n    console.log(i);\n  }\n}\n",[33,3683,3684,3706,3710,3742,3773,3788,3811,3823,3843,3855,3864,3873,3877],{"__ignoreMap":71},[75,3685,3686,3689,3692,3694,3697,3700,3703],{"class":77,"line":78},[75,3687,3688],{"class":85},"const",[75,3690,3691],{"class":224}," end",[75,3693,2271],{"class":85},[75,3695,3696],{"class":3408}," parseInt",[75,3698,3699],{"class":81},"(process.argv[",[75,3701,3702],{"class":224},"2",[75,3704,3705],{"class":81},"]);\n",[75,3707,3708],{"class":77,"line":98},[75,3709,481],{"emptyLinePlaceholder":480},[75,3711,3712,3715,3718,3720,3723,3725,3728,3731,3734,3737,3740],{"class":77,"line":119},[75,3713,3714],{"class":85},"for",[75,3716,3717],{"class":81}," (",[75,3719,3205],{"class":85},[75,3721,3722],{"class":81}," i ",[75,3724,2234],{"class":85},[75,3726,3727],{"class":224}," 1",[75,3729,3730],{"class":81},"; i ",[75,3732,3733],{"class":85},"\u003C=",[75,3735,3736],{"class":81}," end; i",[75,3738,3739],{"class":85},"++",[75,3741,3235],{"class":81},[75,3743,3744,3746,3749,3751,3753,3756,3758,3761,3763,3765,3767,3769,3771],{"class":77,"line":484},[75,3745,3240],{"class":85},[75,3747,3748],{"class":81}," (i ",[75,3750,3453],{"class":85},[75,3752,3456],{"class":224},[75,3754,3755],{"class":85}," ===",[75,3757,3462],{"class":224},[75,3759,3760],{"class":85}," &&",[75,3762,3722],{"class":81},[75,3764,3453],{"class":85},[75,3766,3472],{"class":224},[75,3768,3755],{"class":85},[75,3770,3462],{"class":224},[75,3772,3235],{"class":81},[75,3774,3775,3778,3781,3783,3785],{"class":77,"line":490},[75,3776,3777],{"class":81},"    console.",[75,3779,3780],{"class":3408},"log",[75,3782,3429],{"class":81},[75,3784,3489],{"class":212},[75,3786,3787],{"class":81},");\n",[75,3789,3790,3793,3796,3799,3801,3803,3805,3807,3809],{"class":77,"line":499},[75,3791,3792],{"class":81},"  } ",[75,3794,3795],{"class":85},"else",[75,3797,3798],{"class":85}," if",[75,3800,3748],{"class":81},[75,3802,3453],{"class":85},[75,3804,3456],{"class":224},[75,3806,3755],{"class":85},[75,3808,3462],{"class":224},[75,3810,3235],{"class":81},[75,3812,3813,3815,3817,3819,3821],{"class":77,"line":504},[75,3814,3777],{"class":81},[75,3816,3780],{"class":3408},[75,3818,3429],{"class":81},[75,3820,3517],{"class":212},[75,3822,3787],{"class":81},[75,3824,3825,3827,3829,3831,3833,3835,3837,3839,3841],{"class":77,"line":510},[75,3826,3792],{"class":81},[75,3828,3795],{"class":85},[75,3830,3798],{"class":85},[75,3832,3748],{"class":81},[75,3834,3453],{"class":85},[75,3836,3472],{"class":224},[75,3838,3755],{"class":85},[75,3840,3462],{"class":224},[75,3842,3235],{"class":81},[75,3844,3845,3847,3849,3851,3853],{"class":77,"line":519},[75,3846,3777],{"class":81},[75,3848,3780],{"class":3408},[75,3850,3429],{"class":81},[75,3852,3544],{"class":212},[75,3854,3787],{"class":81},[75,3856,3857,3859,3861],{"class":77,"line":524},[75,3858,3792],{"class":81},[75,3860,3795],{"class":85},[75,3862,3863],{"class":81}," {\n",[75,3865,3866,3868,3870],{"class":77,"line":530},[75,3867,3777],{"class":81},[75,3869,3780],{"class":3408},[75,3871,3872],{"class":81},"(i);\n",[75,3874,3875],{"class":77,"line":2376},[75,3876,3264],{"class":81},[75,3878,3879],{"class":77,"line":3563},[75,3880,3269],{"class":81},[21,3882,3883],{},"The logic follows the same structure:",[49,3885,3886,3889,3891,3894,3897],{},[52,3887,3888],{},"Get the end number from command line argument",[52,3890,3630],{},[52,3892,3893],{},"Check for divisibility by 3 and 5",[52,3895,3896],{},"Print FizzBuzz, Fizz or Buzz based on conditions",[52,3898,3899],{},"Else print the number",[21,3901,3902],{},"To run:",[66,3904,3907],{"className":3905,"code":3906,"language":327},[325],"node fizzbuzz.js 100\n",[33,3908,3906],{"__ignoreMap":71},[21,3910,3911],{},"FizzBuzz tests basic coding concepts in any language. By accepting input and using modular division and conditionals, we can easily solve this problem. This example demonstrates a structured approach in both Python and JavaScript.",[561,3913,3914],{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sVjZL, html code.shiki .sVjZL{--shiki-default:#6F42C1;--shiki-dark:#D2A8FF}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":71,"searchDepth":98,"depth":98,"links":3916},[3917,3918,3919],{"id":3367,"depth":98,"text":3368},{"id":3377,"depth":98,"text":3378},{"id":3674,"depth":98,"text":3675},"2023-08-14",{"type":579,"layout":580},"\u002Fblog\u002F2023-08-14-fizzbuzzin-py-and-js",{"title":3361,"description":71},"blog\u002F2023-08-14-fizzbuzzin-py-and-js",[2942,3357,3386,3926],"interview","Hbe54Ws939iFWFuFOnQEv2EgHP4QJ0m0XSMdWO1a0xQ",{"id":3929,"title":3930,"authors":3931,"body":3932,"date":4643,"description":3936,"extension":577,"meta":4644,"navigation":480,"path":4645,"seo":4646,"stem":4647,"tags":4648,"toc":480,"__hash__":4649},"blog\u002Fblog\u002F2023-07-21-map-reduce-filter.md","Leveraging Map, Reduce and Filter for Cleaner Code",[15,16],{"type":18,"value":3933,"toc":4637},[3934,3937,3941,3944,3947,4035,4038,4117,4120,4124,4127,4130,4222,4225,4310,4313,4317,4320,4323,4403,4406,4482,4485,4489,4492,4628,4631,4634],[21,3935,3936],{},"When working with array data in JavaScript or Python, transforming, combining and filtering that data is a common task. However, using explicit loops every time can make your code messy and hard to understand. Luckily, there are built-in methods for handling these operations in a simple, declarative way - namely map(), reduce() and filter(). Mastering these 3 functional programming concepts can help you write cleaner and more maintainable code.",[25,3938,3940],{"id":3939},"map-for-easy-data-transformation","Map for Easy Data Transformation",[21,3942,3943],{},"The map() method allows transforming each item in an array by applying a function to it. This returns a new array with the transformed values, without mutating the original.",[21,3945,3946],{},"Here's an example doubling some numbers in JavaScript:",[66,3948,3950],{"className":3157,"code":3949,"language":3159,"meta":71,"style":71},"const numbers = [1, 2, 3, 4, 5];\n\nconst doubled = numbers.map(x => x * 2);\n\n\u002F\u002F doubled is [2, 4, 6, 8, 10]\n",[33,3951,3952,3988,3992,4026,4030],{"__ignoreMap":71},[75,3953,3954,3956,3959,3961,3964,3966,3968,3970,3972,3975,3977,3980,3982,3985],{"class":77,"line":78},[75,3955,3688],{"class":85},[75,3957,3958],{"class":224}," numbers",[75,3960,2271],{"class":85},[75,3962,3963],{"class":81}," [",[75,3965,3432],{"class":224},[75,3967,555],{"class":81},[75,3969,3702],{"class":224},[75,3971,555],{"class":81},[75,3973,3974],{"class":224},"3",[75,3976,555],{"class":81},[75,3978,3979],{"class":224},"4",[75,3981,555],{"class":81},[75,3983,3984],{"class":224},"5",[75,3986,3987],{"class":81},"];\n",[75,3989,3990],{"class":77,"line":98},[75,3991,481],{"emptyLinePlaceholder":480},[75,3993,3994,3996,3999,4001,4004,4007,4009,4013,4016,4019,4022,4024],{"class":77,"line":119},[75,3995,3688],{"class":85},[75,3997,3998],{"class":224}," doubled",[75,4000,2271],{"class":85},[75,4002,4003],{"class":81}," numbers.",[75,4005,4006],{"class":3408},"map",[75,4008,3429],{"class":81},[75,4010,4012],{"class":4011},"sjlk-","x",[75,4014,4015],{"class":85}," =>",[75,4017,4018],{"class":81}," x ",[75,4020,4021],{"class":85},"*",[75,4023,2318],{"class":224},[75,4025,3787],{"class":81},[75,4027,4028],{"class":77,"line":484},[75,4029,481],{"emptyLinePlaceholder":480},[75,4031,4032],{"class":77,"line":490},[75,4033,4034],{"class":460},"\u002F\u002F doubled is [2, 4, 6, 8, 10]\n",[21,4036,4037],{},"And doing the same operation in Python:",[66,4039,4043],{"className":4040,"code":4041,"language":4042,"meta":71,"style":71},"language-py shiki shiki-themes github-light github-dark-default","numbers = [1, 2, 3, 4, 5]\n\ndoubled = list(map(lambda x: x*2, numbers))\n\n# doubled is [2, 4, 6, 8, 10]  \n","py",[33,4044,4045,4075,4079,4108,4112],{"__ignoreMap":71},[75,4046,4047,4050,4052,4054,4056,4058,4060,4062,4064,4066,4068,4070,4072],{"class":77,"line":78},[75,4048,4049],{"class":81},"numbers ",[75,4051,2234],{"class":85},[75,4053,3963],{"class":81},[75,4055,3432],{"class":224},[75,4057,555],{"class":81},[75,4059,3702],{"class":224},[75,4061,555],{"class":81},[75,4063,3974],{"class":224},[75,4065,555],{"class":81},[75,4067,3979],{"class":224},[75,4069,555],{"class":81},[75,4071,3984],{"class":224},[75,4073,4074],{"class":81},"]\n",[75,4076,4077],{"class":77,"line":98},[75,4078,481],{"emptyLinePlaceholder":480},[75,4080,4081,4084,4086,4089,4091,4093,4095,4098,4101,4103,4105],{"class":77,"line":119},[75,4082,4083],{"class":81},"doubled ",[75,4085,2234],{"class":85},[75,4087,4088],{"class":224}," list",[75,4090,3429],{"class":81},[75,4092,4006],{"class":224},[75,4094,3429],{"class":81},[75,4096,4097],{"class":85},"lambda",[75,4099,4100],{"class":81}," x: x",[75,4102,4021],{"class":85},[75,4104,3702],{"class":224},[75,4106,4107],{"class":81},", numbers))\n",[75,4109,4110],{"class":77,"line":484},[75,4111,481],{"emptyLinePlaceholder":480},[75,4113,4114],{"class":77,"line":490},[75,4115,4116],{"class":460},"# doubled is [2, 4, 6, 8, 10]\n",[21,4118,4119],{},"Map makes it easy to re-shape data without causing side effects elsewhere in your code.",[25,4121,4123],{"id":4122},"reduce-for-combining-data","Reduce for Combining Data",[21,4125,4126],{},"The reduce() method combines all the items in an array into a single value, by repeatedly applying a function. This is great for aggregating data.",[21,4128,4129],{},"Here's an example summing an array of numbers in JavaScript:",[66,4131,4133],{"className":3157,"code":4132,"language":3159,"meta":71,"style":71},"const numbers = [1, 2, 3, 4, 5];\n\nconst sum = numbers.reduce((acc, x) => acc + x, 0); \n\n\u002F\u002F sum is 15\n",[33,4134,4135,4165,4169,4213,4217],{"__ignoreMap":71},[75,4136,4137,4139,4141,4143,4145,4147,4149,4151,4153,4155,4157,4159,4161,4163],{"class":77,"line":78},[75,4138,3688],{"class":85},[75,4140,3958],{"class":224},[75,4142,2271],{"class":85},[75,4144,3963],{"class":81},[75,4146,3432],{"class":224},[75,4148,555],{"class":81},[75,4150,3702],{"class":224},[75,4152,555],{"class":81},[75,4154,3974],{"class":224},[75,4156,555],{"class":81},[75,4158,3979],{"class":224},[75,4160,555],{"class":81},[75,4162,3984],{"class":224},[75,4164,3987],{"class":81},[75,4166,4167],{"class":77,"line":98},[75,4168,481],{"emptyLinePlaceholder":480},[75,4170,4171,4173,4176,4178,4180,4183,4186,4189,4191,4193,4196,4199,4202,4204,4207,4210],{"class":77,"line":119},[75,4172,3688],{"class":85},[75,4174,4175],{"class":224}," sum",[75,4177,2271],{"class":85},[75,4179,4003],{"class":81},[75,4181,4182],{"class":3408},"reduce",[75,4184,4185],{"class":81},"((",[75,4187,4188],{"class":4011},"acc",[75,4190,555],{"class":81},[75,4192,4012],{"class":4011},[75,4194,4195],{"class":81},") ",[75,4197,4198],{"class":85},"=>",[75,4200,4201],{"class":81}," acc ",[75,4203,3438],{"class":85},[75,4205,4206],{"class":81}," x, ",[75,4208,4209],{"class":224},"0",[75,4211,4212],{"class":81},"); \n",[75,4214,4215],{"class":77,"line":484},[75,4216,481],{"emptyLinePlaceholder":480},[75,4218,4219],{"class":77,"line":490},[75,4220,4221],{"class":460},"\u002F\u002F sum is 15\n",[21,4223,4224],{},"And the same operation in Python:",[66,4226,4228],{"className":4040,"code":4227,"language":4042,"meta":71,"style":71},"from functools import reduce\n\nnumbers = [1, 2, 3, 4, 5]\n\nsum = reduce(lambda acc, x: acc + x, numbers)\n\n# sum is 15\n",[33,4229,4230,4243,4247,4275,4279,4301,4305],{"__ignoreMap":71},[75,4231,4232,4235,4238,4240],{"class":77,"line":78},[75,4233,4234],{"class":85},"from",[75,4236,4237],{"class":81}," functools ",[75,4239,3393],{"class":85},[75,4241,4242],{"class":4011}," reduce\n",[75,4244,4245],{"class":77,"line":98},[75,4246,481],{"emptyLinePlaceholder":480},[75,4248,4249,4251,4253,4255,4257,4259,4261,4263,4265,4267,4269,4271,4273],{"class":77,"line":119},[75,4250,4049],{"class":81},[75,4252,2234],{"class":85},[75,4254,3963],{"class":81},[75,4256,3432],{"class":224},[75,4258,555],{"class":81},[75,4260,3702],{"class":224},[75,4262,555],{"class":81},[75,4264,3974],{"class":224},[75,4266,555],{"class":81},[75,4268,3979],{"class":224},[75,4270,555],{"class":81},[75,4272,3984],{"class":224},[75,4274,4074],{"class":81},[75,4276,4277],{"class":77,"line":484},[75,4278,481],{"emptyLinePlaceholder":480},[75,4280,4281,4284,4286,4289,4291,4293,4296,4298],{"class":77,"line":490},[75,4282,4283],{"class":224},"sum",[75,4285,2271],{"class":85},[75,4287,4288],{"class":4011}," reduce",[75,4290,3429],{"class":81},[75,4292,4097],{"class":85},[75,4294,4295],{"class":81}," acc, x: acc ",[75,4297,3438],{"class":85},[75,4299,4300],{"class":81}," x, numbers)\n",[75,4302,4303],{"class":77,"line":499},[75,4304,481],{"emptyLinePlaceholder":480},[75,4306,4307],{"class":77,"line":504},[75,4308,4309],{"class":460},"# sum is 15\n",[21,4311,4312],{},"Reduce can be used for anything from calculating totals to generating reports.",[25,4314,4316],{"id":4315},"filter-for-selecting-data","Filter for Selecting Data",[21,4318,4319],{},"The filter() method returns a subset of an array, based on whether the items pass a conditional test. It's useful for filtering out unwanted values.",[21,4321,4322],{},"Here's an example getting only even numbers from an array in JavaScript:",[66,4324,4326],{"className":3157,"code":4325,"language":3159,"meta":71,"style":71},"const numbers = [1, 2, 3, 4, 5];\n\nconst evens = numbers.filter(x => x % 2 === 0); \n\n\u002F\u002F evens is [2, 4]\n",[33,4327,4328,4358,4362,4394,4398],{"__ignoreMap":71},[75,4329,4330,4332,4334,4336,4338,4340,4342,4344,4346,4348,4350,4352,4354,4356],{"class":77,"line":78},[75,4331,3688],{"class":85},[75,4333,3958],{"class":224},[75,4335,2271],{"class":85},[75,4337,3963],{"class":81},[75,4339,3432],{"class":224},[75,4341,555],{"class":81},[75,4343,3702],{"class":224},[75,4345,555],{"class":81},[75,4347,3974],{"class":224},[75,4349,555],{"class":81},[75,4351,3979],{"class":224},[75,4353,555],{"class":81},[75,4355,3984],{"class":224},[75,4357,3987],{"class":81},[75,4359,4360],{"class":77,"line":98},[75,4361,481],{"emptyLinePlaceholder":480},[75,4363,4364,4366,4369,4371,4373,4376,4378,4380,4382,4384,4386,4388,4390,4392],{"class":77,"line":119},[75,4365,3688],{"class":85},[75,4367,4368],{"class":224}," evens",[75,4370,2271],{"class":85},[75,4372,4003],{"class":81},[75,4374,4375],{"class":3408},"filter",[75,4377,3429],{"class":81},[75,4379,4012],{"class":4011},[75,4381,4015],{"class":85},[75,4383,4018],{"class":81},[75,4385,3453],{"class":85},[75,4387,2318],{"class":224},[75,4389,3755],{"class":85},[75,4391,3462],{"class":224},[75,4393,4212],{"class":81},[75,4395,4396],{"class":77,"line":484},[75,4397,481],{"emptyLinePlaceholder":480},[75,4399,4400],{"class":77,"line":490},[75,4401,4402],{"class":460},"\u002F\u002F evens is [2, 4]\n",[21,4404,4405],{},"And in Python:",[66,4407,4409],{"className":4040,"code":4408,"language":4042,"meta":71,"style":71},"numbers = [1, 2, 3, 4, 5]\n\nevens = list(filter(lambda x: x % 2 == 0, numbers))\n\n# evens is [2, 4] \n",[33,4410,4411,4439,4443,4473,4477],{"__ignoreMap":71},[75,4412,4413,4415,4417,4419,4421,4423,4425,4427,4429,4431,4433,4435,4437],{"class":77,"line":78},[75,4414,4049],{"class":81},[75,4416,2234],{"class":85},[75,4418,3963],{"class":81},[75,4420,3432],{"class":224},[75,4422,555],{"class":81},[75,4424,3702],{"class":224},[75,4426,555],{"class":81},[75,4428,3974],{"class":224},[75,4430,555],{"class":81},[75,4432,3979],{"class":224},[75,4434,555],{"class":81},[75,4436,3984],{"class":224},[75,4438,4074],{"class":81},[75,4440,4441],{"class":77,"line":98},[75,4442,481],{"emptyLinePlaceholder":480},[75,4444,4445,4448,4450,4452,4454,4456,4458,4460,4463,4465,4467,4469,4471],{"class":77,"line":119},[75,4446,4447],{"class":81},"evens ",[75,4449,2234],{"class":85},[75,4451,4088],{"class":224},[75,4453,3429],{"class":81},[75,4455,4375],{"class":224},[75,4457,3429],{"class":81},[75,4459,4097],{"class":85},[75,4461,4462],{"class":81}," x: x ",[75,4464,3453],{"class":85},[75,4466,2318],{"class":224},[75,4468,3459],{"class":85},[75,4470,3462],{"class":224},[75,4472,4107],{"class":81},[75,4474,4475],{"class":77,"line":484},[75,4476,481],{"emptyLinePlaceholder":480},[75,4478,4479],{"class":77,"line":490},[75,4480,4481],{"class":460},"# evens is [2, 4]\n",[21,4483,4484],{},"Filter gives you an easy way to narrow down arrays to only the data you need.",[25,4486,4488],{"id":4487},"chaining-for-data-pipelines","Chaining for Data Pipelines",[21,4490,4491],{},"The real power comes when you combine these methods to transform, combine and filter data in a pipeline:",[66,4493,4495],{"className":3157,"code":4494,"language":3159,"meta":71,"style":71},"const numbers = [1, 2, 3, 4, 5];\n\nconst pipeline = numbers\n  .map(x => x * 2)\n  .filter(x => x % 4 === 0) \n  .reduce((acc, x) => acc + x, 0);\n  \n\u002F\u002F pipeline is 12\n",[33,4496,4497,4527,4531,4543,4564,4590,4618,4623],{"__ignoreMap":71},[75,4498,4499,4501,4503,4505,4507,4509,4511,4513,4515,4517,4519,4521,4523,4525],{"class":77,"line":78},[75,4500,3688],{"class":85},[75,4502,3958],{"class":224},[75,4504,2271],{"class":85},[75,4506,3963],{"class":81},[75,4508,3432],{"class":224},[75,4510,555],{"class":81},[75,4512,3702],{"class":224},[75,4514,555],{"class":81},[75,4516,3974],{"class":224},[75,4518,555],{"class":81},[75,4520,3979],{"class":224},[75,4522,555],{"class":81},[75,4524,3984],{"class":224},[75,4526,3987],{"class":81},[75,4528,4529],{"class":77,"line":98},[75,4530,481],{"emptyLinePlaceholder":480},[75,4532,4533,4535,4538,4540],{"class":77,"line":119},[75,4534,3688],{"class":85},[75,4536,4537],{"class":224}," pipeline",[75,4539,2271],{"class":85},[75,4541,4542],{"class":81}," numbers\n",[75,4544,4545,4548,4550,4552,4554,4556,4558,4560,4562],{"class":77,"line":484},[75,4546,4547],{"class":81},"  .",[75,4549,4006],{"class":3408},[75,4551,3429],{"class":81},[75,4553,4012],{"class":4011},[75,4555,4015],{"class":85},[75,4557,4018],{"class":81},[75,4559,4021],{"class":85},[75,4561,2318],{"class":224},[75,4563,2246],{"class":81},[75,4565,4566,4568,4570,4572,4574,4576,4578,4580,4583,4585,4587],{"class":77,"line":490},[75,4567,4547],{"class":81},[75,4569,4375],{"class":3408},[75,4571,3429],{"class":81},[75,4573,4012],{"class":4011},[75,4575,4015],{"class":85},[75,4577,4018],{"class":81},[75,4579,3453],{"class":85},[75,4581,4582],{"class":224}," 4",[75,4584,3755],{"class":85},[75,4586,3462],{"class":224},[75,4588,4589],{"class":81},") \n",[75,4591,4592,4594,4596,4598,4600,4602,4604,4606,4608,4610,4612,4614,4616],{"class":77,"line":499},[75,4593,4547],{"class":81},[75,4595,4182],{"class":3408},[75,4597,4185],{"class":81},[75,4599,4188],{"class":4011},[75,4601,555],{"class":81},[75,4603,4012],{"class":4011},[75,4605,4195],{"class":81},[75,4607,4198],{"class":85},[75,4609,4201],{"class":81},[75,4611,3438],{"class":85},[75,4613,4206],{"class":81},[75,4615,4209],{"class":224},[75,4617,3787],{"class":81},[75,4619,4620],{"class":77,"line":504},[75,4621,4622],{"class":81},"  \n",[75,4624,4625],{"class":77,"line":510},[75,4626,4627],{"class":460},"\u002F\u002F pipeline is 12\n",[21,4629,4630],{},"This allows declaratively operating on data for cleaner and more readable code.",[21,4632,4633],{},"Leveraging map(), reduce() and filter() can help simplify your code and make it more understandable and maintainable.",[561,4635,4636],{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sVjZL, html code.shiki .sVjZL{--shiki-default:#6F42C1;--shiki-dark:#D2A8FF}html pre.shiki code .sjlk-, html code.shiki .sjlk-{--shiki-default:#E36209;--shiki-dark:#FFA657}html pre.shiki code .sPyeA, html code.shiki .sPyeA{--shiki-default:#6A737D;--shiki-dark:#8B949E}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":71,"searchDepth":98,"depth":98,"links":4638},[4639,4640,4641,4642],{"id":3939,"depth":98,"text":3940},{"id":4122,"depth":98,"text":4123},{"id":4315,"depth":98,"text":4316},{"id":4487,"depth":98,"text":4488},"2023-07-21",{"type":579,"layout":580},"\u002Fblog\u002F2023-07-21-map-reduce-filter",{"title":3930,"description":3936},"blog\u002F2023-07-21-map-reduce-filter",[2942,3357,3386],"Jrt7gFcg4NdRR16Tu8kB-CO0z9ZMrPIz0kGEp8H7gak",{"id":4651,"title":4652,"authors":4653,"body":4654,"date":5067,"description":4658,"extension":577,"meta":5068,"navigation":480,"path":5069,"seo":5070,"stem":5071,"tags":5072,"toc":6,"__hash__":5073},"blog\u002Fblog\u002F2023-01-14-anagram.md","Is a string an anagram?",[15,16],{"type":18,"value":4655,"toc":5063},[4656,4659,4662,4665,4785,4788,4791,4794,4797,4800,5048,5051,5054,5057,5060],[21,4657,4658],{},"An anagram is a word or phrase formed by rearranging the letters of another word or phrase. For example, \"elbow\" and \"below\" are anagrams of each other. In coding interviews and programming challenges, you may be asked to write a function that checks if two input strings are anagrams. Here's how to do this in both Python and JavaScript.",[25,4660,4661],{"id":3377},"Python Solution",[21,4663,4664],{},"Here is one way to check if two strings are anagrams in Python:",[66,4666,4668],{"className":3384,"code":4667,"language":3386,"meta":71,"style":71},"def are_anagrams(str1, str2):\n  # Convert both strings to lowercase and remove whitespace\n  str1 = str1.replace(\" \", \"\").lower()\n  str2 = str2.replace(\" \", \"\").lower()\n  \n  # Check if lengths are different\n  if len(str1) != len(str2):\n    return False\n  \n  # Sort the strings and compare\n  return sorted(str1) == sorted(str2)\n",[33,4669,4670,4680,4685,4706,4724,4728,4733,4750,4758,4762,4767],{"__ignoreMap":71},[75,4671,4672,4674,4677],{"class":77,"line":78},[75,4673,3405],{"class":85},[75,4675,4676],{"class":3408}," are_anagrams",[75,4678,4679],{"class":81},"(str1, str2):\n",[75,4681,4682],{"class":77,"line":98},[75,4683,4684],{"class":460},"  # Convert both strings to lowercase and remove whitespace\n",[75,4686,4687,4690,4692,4695,4698,4700,4703],{"class":77,"line":119},[75,4688,4689],{"class":81},"  str1 ",[75,4691,2234],{"class":85},[75,4693,4694],{"class":81}," str1.replace(",[75,4696,4697],{"class":212},"\" \"",[75,4699,555],{"class":81},[75,4701,4702],{"class":212},"\"\"",[75,4704,4705],{"class":81},").lower()\n",[75,4707,4708,4711,4713,4716,4718,4720,4722],{"class":77,"line":484},[75,4709,4710],{"class":81},"  str2 ",[75,4712,2234],{"class":85},[75,4714,4715],{"class":81}," str2.replace(",[75,4717,4697],{"class":212},[75,4719,555],{"class":81},[75,4721,4702],{"class":212},[75,4723,4705],{"class":81},[75,4725,4726],{"class":77,"line":490},[75,4727,4622],{"class":81},[75,4729,4730],{"class":77,"line":499},[75,4731,4732],{"class":460},"  # Check if lengths are different\n",[75,4734,4735,4737,4740,4743,4745,4747],{"class":77,"line":504},[75,4736,3240],{"class":85},[75,4738,4739],{"class":224}," len",[75,4741,4742],{"class":81},"(str1) ",[75,4744,3230],{"class":85},[75,4746,4739],{"class":224},[75,4748,4749],{"class":81},"(str2):\n",[75,4751,4752,4755],{"class":77,"line":510},[75,4753,4754],{"class":85},"    return",[75,4756,4757],{"class":224}," False\n",[75,4759,4760],{"class":77,"line":519},[75,4761,4622],{"class":81},[75,4763,4764],{"class":77,"line":524},[75,4765,4766],{"class":460},"  # Sort the strings and compare\n",[75,4768,4769,4772,4775,4777,4780,4782],{"class":77,"line":530},[75,4770,4771],{"class":85},"  return",[75,4773,4774],{"class":224}," sorted",[75,4776,4742],{"class":81},[75,4778,4779],{"class":85},"==",[75,4781,4774],{"class":224},[75,4783,4784],{"class":81},"(str2)\n",[21,4786,4787],{},"This works by first cleaning up the input strings - converting them to lowercase and removing any whitespace.",[21,4789,4790],{},"We then check if the lengths are different - if so, they cannot be anagrams.",[21,4792,4793],{},"Finally, we sort each string alphabetically and compare the sorted versions. If they are equal, the original strings must have been anagrams.",[25,4795,4796],{"id":3674},"JavaScript Solution",[21,4798,4799],{},"Similarly in JavaScript:",[66,4801,4803],{"className":3157,"code":4802,"language":3159,"meta":71,"style":71},"function areAnagrams(str1, str2) {\n\n  \u002F\u002F Remove whitespace and lowercase\n  let s1 = str1.replace(\u002F\\s\u002Fg, '').toLowerCase();\n  let s2 = str2.replace(\u002F\\s\u002Fg, '').toLowerCase();  \n\n  \u002F\u002F Check length\n  if (s1.length !== s2.length) return false;\n\n  \u002F\u002F Sort and compare\n  let sorted1 = s1.split('').sort().join(''); \n  let sorted2 = s2.split('').sort().join('');\n\n  return sorted1 === sorted2;\n\n}\n",[33,4804,4805,4825,4829,4834,4876,4911,4915,4920,4948,4952,4957,4993,5024,5028,5040,5044],{"__ignoreMap":71},[75,4806,4807,4810,4813,4815,4818,4820,4823],{"class":77,"line":78},[75,4808,4809],{"class":85},"function",[75,4811,4812],{"class":3408}," areAnagrams",[75,4814,3429],{"class":81},[75,4816,4817],{"class":4011},"str1",[75,4819,555],{"class":81},[75,4821,4822],{"class":4011},"str2",[75,4824,3235],{"class":81},[75,4826,4827],{"class":77,"line":98},[75,4828,481],{"emptyLinePlaceholder":480},[75,4830,4831],{"class":77,"line":119},[75,4832,4833],{"class":460},"  \u002F\u002F Remove whitespace and lowercase\n",[75,4835,4836,4839,4842,4844,4847,4850,4852,4855,4858,4860,4863,4865,4868,4870,4873],{"class":77,"line":484},[75,4837,4838],{"class":85},"  let",[75,4840,4841],{"class":81}," s1 ",[75,4843,2234],{"class":85},[75,4845,4846],{"class":81}," str1.",[75,4848,4849],{"class":3408},"replace",[75,4851,3429],{"class":81},[75,4853,4854],{"class":212},"\u002F",[75,4856,4857],{"class":224},"\\s",[75,4859,4854],{"class":212},[75,4861,4862],{"class":85},"g",[75,4864,555],{"class":81},[75,4866,4867],{"class":212},"''",[75,4869,944],{"class":81},[75,4871,4872],{"class":3408},"toLowerCase",[75,4874,4875],{"class":81},"();\n",[75,4877,4878,4880,4883,4885,4888,4890,4892,4894,4896,4898,4900,4902,4904,4906,4908],{"class":77,"line":490},[75,4879,4838],{"class":85},[75,4881,4882],{"class":81}," s2 ",[75,4884,2234],{"class":85},[75,4886,4887],{"class":81}," str2.",[75,4889,4849],{"class":3408},[75,4891,3429],{"class":81},[75,4893,4854],{"class":212},[75,4895,4857],{"class":224},[75,4897,4854],{"class":212},[75,4899,4862],{"class":85},[75,4901,555],{"class":81},[75,4903,4867],{"class":212},[75,4905,944],{"class":81},[75,4907,4872],{"class":3408},[75,4909,4910],{"class":81},"();  \n",[75,4912,4913],{"class":77,"line":499},[75,4914,481],{"emptyLinePlaceholder":480},[75,4916,4917],{"class":77,"line":504},[75,4918,4919],{"class":460},"  \u002F\u002F Check length\n",[75,4921,4922,4924,4927,4930,4933,4936,4938,4940,4943,4946],{"class":77,"line":510},[75,4923,3240],{"class":85},[75,4925,4926],{"class":81}," (s1.",[75,4928,4929],{"class":224},"length",[75,4931,4932],{"class":85}," !==",[75,4934,4935],{"class":81}," s2.",[75,4937,4929],{"class":224},[75,4939,4195],{"class":81},[75,4941,4942],{"class":85},"return",[75,4944,4945],{"class":224}," false",[75,4947,3216],{"class":81},[75,4949,4950],{"class":77,"line":519},[75,4951,481],{"emptyLinePlaceholder":480},[75,4953,4954],{"class":77,"line":524},[75,4955,4956],{"class":460},"  \u002F\u002F Sort and compare\n",[75,4958,4959,4961,4964,4966,4969,4972,4974,4976,4978,4981,4984,4987,4989,4991],{"class":77,"line":530},[75,4960,4838],{"class":85},[75,4962,4963],{"class":81}," sorted1 ",[75,4965,2234],{"class":85},[75,4967,4968],{"class":81}," s1.",[75,4970,4971],{"class":3408},"split",[75,4973,3429],{"class":81},[75,4975,4867],{"class":212},[75,4977,944],{"class":81},[75,4979,4980],{"class":3408},"sort",[75,4982,4983],{"class":81},"().",[75,4985,4986],{"class":3408},"join",[75,4988,3429],{"class":81},[75,4990,4867],{"class":212},[75,4992,4212],{"class":81},[75,4994,4995,4997,5000,5002,5004,5006,5008,5010,5012,5014,5016,5018,5020,5022],{"class":77,"line":2376},[75,4996,4838],{"class":85},[75,4998,4999],{"class":81}," sorted2 ",[75,5001,2234],{"class":85},[75,5003,4935],{"class":81},[75,5005,4971],{"class":3408},[75,5007,3429],{"class":81},[75,5009,4867],{"class":212},[75,5011,944],{"class":81},[75,5013,4980],{"class":3408},[75,5015,4983],{"class":81},[75,5017,4986],{"class":3408},[75,5019,3429],{"class":81},[75,5021,4867],{"class":212},[75,5023,3787],{"class":81},[75,5025,5026],{"class":77,"line":3563},[75,5027,481],{"emptyLinePlaceholder":480},[75,5029,5030,5032,5034,5037],{"class":77,"line":3568},[75,5031,4771],{"class":85},[75,5033,4963],{"class":81},[75,5035,5036],{"class":85},"===",[75,5038,5039],{"class":81}," sorted2;\n",[75,5041,5042],{"class":77,"line":3583},[75,5043,481],{"emptyLinePlaceholder":480},[75,5045,5046],{"class":77,"line":3602},[75,5047,3269],{"class":81},[21,5049,5050],{},"The process is essentially the same - clean up the strings, check lengths, sort and compare.",[21,5052,5053],{},"This demonstrates how the core algorithm remains the same across languages, with minor syntactical differences.",[21,5055,5056],{},"Checking if two strings are anagrams is a common interview challenge, and solving it shows you can work with strings and sorting algorithms. With practice, translating a solution between Python and JavaScript gets easier.",[21,5058,5059],{},"Let me know if you would like me to expand or modify this draft blog post in any way!",[561,5061,5062],{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .sVjZL, html code.shiki .sVjZL{--shiki-default:#6F42C1;--shiki-dark:#D2A8FF}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sPyeA, html code.shiki .sPyeA{--shiki-default:#6A737D;--shiki-dark:#8B949E}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjlk-, html code.shiki .sjlk-{--shiki-default:#E36209;--shiki-dark:#FFA657}",{"title":71,"searchDepth":119,"depth":119,"links":5064},[5065,5066],{"id":3377,"depth":98,"text":4661},{"id":3674,"depth":98,"text":4796},"2023-01-14",{"type":579,"layout":580},"\u002Fblog\u002F2023-01-14-anagram",{"title":4652,"description":4658},"blog\u002F2023-01-14-anagram",[3386,2942,3926],"2_B_S6F72xODmlaZR0GQSQPw1MxR3Y-cat9lQafSg7w",{"id":5075,"title":5076,"authors":5077,"body":5078,"date":5483,"description":5484,"extension":577,"meta":5485,"navigation":480,"path":5486,"seo":5487,"stem":5488,"tags":5489,"toc":6,"__hash__":5491},"blog\u002Fblog\u002F2022-12-09-running-traefik.md","Running Traefik in Docker",[15,16],{"type":18,"value":5079,"toc":5478},[5080,5090,5093,5096,5100,5106,5293,5300,5303,5309,5315,5319,5322,5327,5442,5449,5451,5457,5464,5466,5469,5472,5475],[21,5081,5082,5089],{},[5083,5084,5088],"a",{"href":5085,"rel":5086},"https:\u002F\u002Ftraefik.io\u002F",[5087],"nofollow","Traefik"," is a popular open source edge router and reverse proxy that makes deploying microservices easy. It integrates with Docker and automatically configures SSL certificates via Let's Encrypt.",[21,5091,5092],{},"In this post, we'll be installing and configuring Traefik on Docker using Docker Compose. We'll also set up a sample website container and secure it with SSL using the integrated Let's Encrypt support.",[21,5094,5095],{},"To make things more modular, we'll split up the Traefik configuration and website container into separate Docker Compose files. We'll also secure the Traefik web UI by routing it through Traefik itself.",[25,5097,5099],{"id":5098},"installing-traefik","Installing Traefik",[21,5101,1841,5102,5105],{},[33,5103,5104],{},"docker-compose.traefik.yml"," file that will define our Traefik service:",[66,5107,5111],{"className":5108,"code":5109,"language":5110,"meta":71,"style":71},"language-yaml shiki shiki-themes github-light github-dark-default","version: '3'\n\nservices:\n\n  traefik:\n    image: traefik:v2.2\n    command: --api.insecure=true --providers.docker --log\n    ports:\n      - \"80:80\"\n      - \"443:443\" \n    volumes:\n      - \u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n      - traefik-public-certificates:\u002Fcertificates\n    labels:\n      - \"traefik.http.routers.traefik.rule=Host(`traefik.example.com`)\"\n      - \"traefik.http.routers.traefik.entrypoints=websecure\"\n      - \"traefik.http.routers.traefik.tls=true\"\n      - \"traefik.http.services.traefik.loadbalancer.server.port=8080\"\n\nvolumes:\n  traefik-public-certificates:  \n\nnetworks:\n  - proxy\n","yaml",[33,5112,5113,5125,5129,5136,5140,5147,5157,5167,5174,5182,5192,5199,5206,5213,5220,5227,5234,5242,5250,5255,5263,5272,5277,5285],{"__ignoreMap":71},[75,5114,5115,5119,5122],{"class":77,"line":78},[75,5116,5118],{"class":5117},"sXQ1o","version",[75,5120,5121],{"class":81},": ",[75,5123,5124],{"class":212},"'3'\n",[75,5126,5127],{"class":77,"line":98},[75,5128,481],{"emptyLinePlaceholder":480},[75,5130,5131,5134],{"class":77,"line":119},[75,5132,5133],{"class":5117},"services",[75,5135,3479],{"class":81},[75,5137,5138],{"class":77,"line":484},[75,5139,481],{"emptyLinePlaceholder":480},[75,5141,5142,5145],{"class":77,"line":490},[75,5143,5144],{"class":5117},"  traefik",[75,5146,3479],{"class":81},[75,5148,5149,5152,5154],{"class":77,"line":499},[75,5150,5151],{"class":5117},"    image",[75,5153,5121],{"class":81},[75,5155,5156],{"class":212},"traefik:v2.2\n",[75,5158,5159,5162,5164],{"class":77,"line":504},[75,5160,5161],{"class":5117},"    command",[75,5163,5121],{"class":81},[75,5165,5166],{"class":212},"--api.insecure=true --providers.docker --log\n",[75,5168,5169,5172],{"class":77,"line":510},[75,5170,5171],{"class":5117},"    ports",[75,5173,3479],{"class":81},[75,5175,5176,5179],{"class":77,"line":519},[75,5177,5178],{"class":81},"      - ",[75,5180,5181],{"class":212},"\"80:80\"\n",[75,5183,5184,5186,5189],{"class":77,"line":524},[75,5185,5178],{"class":81},[75,5187,5188],{"class":212},"\"443:443\"",[75,5190,5191],{"class":81}," \n",[75,5193,5194,5197],{"class":77,"line":530},[75,5195,5196],{"class":5117},"    volumes",[75,5198,3479],{"class":81},[75,5200,5201,5203],{"class":77,"line":2376},[75,5202,5178],{"class":81},[75,5204,5205],{"class":212},"\u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n",[75,5207,5208,5210],{"class":77,"line":3563},[75,5209,5178],{"class":81},[75,5211,5212],{"class":212},"traefik-public-certificates:\u002Fcertificates\n",[75,5214,5215,5218],{"class":77,"line":3568},[75,5216,5217],{"class":5117},"    labels",[75,5219,3479],{"class":81},[75,5221,5222,5224],{"class":77,"line":3583},[75,5223,5178],{"class":81},[75,5225,5226],{"class":212},"\"traefik.http.routers.traefik.rule=Host(`traefik.example.com`)\"\n",[75,5228,5229,5231],{"class":77,"line":3602},[75,5230,5178],{"class":81},[75,5232,5233],{"class":212},"\"traefik.http.routers.traefik.entrypoints=websecure\"\n",[75,5235,5237,5239],{"class":77,"line":5236},17,[75,5238,5178],{"class":81},[75,5240,5241],{"class":212},"\"traefik.http.routers.traefik.tls=true\"\n",[75,5243,5245,5247],{"class":77,"line":5244},18,[75,5246,5178],{"class":81},[75,5248,5249],{"class":212},"\"traefik.http.services.traefik.loadbalancer.server.port=8080\"\n",[75,5251,5253],{"class":77,"line":5252},19,[75,5254,481],{"emptyLinePlaceholder":480},[75,5256,5258,5261],{"class":77,"line":5257},20,[75,5259,5260],{"class":5117},"volumes",[75,5262,3479],{"class":81},[75,5264,5266,5269],{"class":77,"line":5265},21,[75,5267,5268],{"class":5117},"  traefik-public-certificates",[75,5270,5271],{"class":81},":  \n",[75,5273,5275],{"class":77,"line":5274},22,[75,5276,481],{"emptyLinePlaceholder":480},[75,5278,5280,5283],{"class":77,"line":5279},23,[75,5281,5282],{"class":5117},"networks",[75,5284,3479],{"class":81},[75,5286,5287,5290],{"class":77,"line":5},[75,5288,5289],{"class":81},"  - ",[75,5291,5292],{"class":212},"proxy\n",[21,5294,5295,5296,5299],{},"This exposes port 80 and 443, enables the API and logging, and configures a router and service for the Traefik web UI at ",[33,5297,5298],{},"traefik.example.com"," using SSL.",[21,5301,5302],{},"Let's start it up:",[66,5304,5307],{"className":5305,"code":5306,"language":327},[325],"docker-compose -f docker-compose.traefik.yml up -d\n",[33,5308,5306],{"__ignoreMap":71},[21,5310,5311,5312,615],{},"Traefik should now be up and running, with the web UI available at ",[33,5313,5314],{},"https:\u002F\u002Ftraefik.example.com",[25,5316,5318],{"id":5317},"adding-a-sample-website","Adding a Sample Website",[21,5320,5321],{},"Next, we'll create a Docker Compose file to run a simple website container:",[21,5323,5324],{},[415,5325,5326],{},"docker-compose.web.yml:",[66,5328,5330],{"className":5108,"code":5329,"language":5110,"meta":71,"style":71},"version: '3'  \n\nservices:\n\n  web:\n    image: nginx:alpine \n    labels:\n      - \"traefik.http.routers.web.rule=Host(`example.com`)\"\n      - \"traefik.http.routers.web.entrypoints=websecure\"\n      - \"traefik.http.routers.web.tls=true\"\n    networks:\n      - proxy\n\nnetworks:\n  proxy:\n    external: true\n",[33,5331,5332,5343,5347,5353,5357,5364,5375,5381,5388,5395,5402,5409,5415,5419,5425,5432],{"__ignoreMap":71},[75,5333,5334,5336,5338,5341],{"class":77,"line":78},[75,5335,5118],{"class":5117},[75,5337,5121],{"class":81},[75,5339,5340],{"class":212},"'3'",[75,5342,4622],{"class":81},[75,5344,5345],{"class":77,"line":98},[75,5346,481],{"emptyLinePlaceholder":480},[75,5348,5349,5351],{"class":77,"line":119},[75,5350,5133],{"class":5117},[75,5352,3479],{"class":81},[75,5354,5355],{"class":77,"line":484},[75,5356,481],{"emptyLinePlaceholder":480},[75,5358,5359,5362],{"class":77,"line":490},[75,5360,5361],{"class":5117},"  web",[75,5363,3479],{"class":81},[75,5365,5366,5368,5370,5373],{"class":77,"line":499},[75,5367,5151],{"class":5117},[75,5369,5121],{"class":81},[75,5371,5372],{"class":212},"nginx:alpine",[75,5374,5191],{"class":81},[75,5376,5377,5379],{"class":77,"line":504},[75,5378,5217],{"class":5117},[75,5380,3479],{"class":81},[75,5382,5383,5385],{"class":77,"line":510},[75,5384,5178],{"class":81},[75,5386,5387],{"class":212},"\"traefik.http.routers.web.rule=Host(`example.com`)\"\n",[75,5389,5390,5392],{"class":77,"line":519},[75,5391,5178],{"class":81},[75,5393,5394],{"class":212},"\"traefik.http.routers.web.entrypoints=websecure\"\n",[75,5396,5397,5399],{"class":77,"line":524},[75,5398,5178],{"class":81},[75,5400,5401],{"class":212},"\"traefik.http.routers.web.tls=true\"\n",[75,5403,5404,5407],{"class":77,"line":530},[75,5405,5406],{"class":5117},"    networks",[75,5408,3479],{"class":81},[75,5410,5411,5413],{"class":77,"line":2376},[75,5412,5178],{"class":81},[75,5414,5292],{"class":212},[75,5416,5417],{"class":77,"line":3563},[75,5418,481],{"emptyLinePlaceholder":480},[75,5420,5421,5423],{"class":77,"line":3568},[75,5422,5282],{"class":5117},[75,5424,3479],{"class":81},[75,5426,5427,5430],{"class":77,"line":3583},[75,5428,5429],{"class":5117},"  proxy",[75,5431,3479],{"class":81},[75,5433,5434,5437,5439],{"class":77,"line":3602},[75,5435,5436],{"class":5117},"    external",[75,5438,5121],{"class":81},[75,5440,5441],{"class":224},"true\n",[21,5443,5444,5445,5448],{},"This attaches the web container to the ",[33,5446,5447],{},"proxy"," network, allowing Traefik to automatically pick it up.",[21,5450,5302],{},[66,5452,5455],{"className":5453,"code":5454,"language":327},[325],"docker-compose -f docker-compose.web.yml up -d\n",[33,5456,5454],{"__ignoreMap":71},[21,5458,5459,5460,5463],{},"Traefik will grab the SSL certificate for ",[33,5461,5462],{},"example.com"," and route traffic to the web container.",[25,5465,541],{"id":540},[21,5467,5468],{},"With just a few labels, we can easily add containers to Traefik without needing to reconfigure the proxy itself. This makes it easy to deploy new microservices and scale horizontally.",[21,5470,5471],{},"Securing the Traefik web UI behind its own proxy is a best practice that ensures outside clients can't directly access the dashboard.",[21,5473,5474],{},"Let me know if you have any other questions!",[561,5476,5477],{},"html pre.shiki code .sXQ1o, html code.shiki .sXQ1o{--shiki-default:#22863A;--shiki-dark:#7EE787}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}",{"title":71,"searchDepth":119,"depth":119,"links":5479},[5480,5481,5482],{"id":5098,"depth":98,"text":5099},{"id":5317,"depth":98,"text":5318},{"id":540,"depth":98,"text":541},"2022-12-09","Traefik is a popular open source edge router and reverse proxy that makes deploying microservices easy. It integrates with Docker and automatically configures SSL certificates via Let's Encrypt.",{"type":579,"layout":580},"\u002Fblog\u002F2022-12-09-running-traefik",{"title":5076,"description":5484},"blog\u002F2022-12-09-running-traefik",[5490,442,587,585],"traefik","bmQTCDAr9Br1WOSiKykt8Beenwg39QQPymL5rV0huHo",{"id":5493,"title":5494,"authors":5495,"body":5496,"date":6279,"description":5502,"extension":577,"meta":6280,"navigation":480,"path":6281,"seo":6282,"stem":6283,"tags":6284,"toc":480,"__hash__":6285},"blog\u002Fblog\u002F2022-12-01-installing-docker-on-debian.md","Installing Docker on Debian 11",[15,16],{"type":18,"value":5497,"toc":6263},[5498,5505,5507,5529,5533,5565,5586,5590,5594,5655,5659,5709,5716,5776,5780,5787,5800,5804,5832,5836,6239,6246,6260],[21,5499,5500,5502,5503],{},[642,5501],{"value":844},"This should work as-is with Debian 12.",[642,5504],{"value":852},[25,5506,44],{"id":43},[49,5508,5509,5512],{},[52,5510,5511],{},"Debian 11 (Bullseye)",[52,5513,5514,5515],{},"One of the following architectures;\n",[49,5516,5517,5520,5523,5526],{},[52,5518,5519],{},"x86_64",[52,5521,5522],{},"amd64",[52,5524,5525],{},"armhf",[52,5527,5528],{},"arm64",[25,5530,5532],{"id":5531},"remove-any-previously-installed-versions-of-docker","Remove any previously installed versions of Docker",[66,5534,5538],{"className":5535,"code":5536,"language":5537,"meta":71,"style":71},"language-shell shiki shiki-themes github-light github-dark-default","sudo apt-get remove docker docker-engine docker.io containerd runc\n","shell",[33,5539,5540],{"__ignoreMap":71},[75,5541,5542,5544,5547,5550,5553,5556,5559,5562],{"class":77,"line":78},[75,5543,342],{"class":208},[75,5545,5546],{"class":212}," apt-get",[75,5548,5549],{"class":212}," remove",[75,5551,5552],{"class":212}," docker",[75,5554,5555],{"class":212}," docker-engine",[75,5557,5558],{"class":212}," docker.io",[75,5560,5561],{"class":212}," containerd",[75,5563,5564],{"class":212}," runc\n",[21,5566,5567,5569,5570,5573,5574,5576,5578,5581,5582,615,5584],{},[642,5568],{"value":644},"If you've had docker previously installed and would like a completely fresh start, it would be a good idea to cleanup the images, containers, volumes and networks, these are stored in ",[33,5571,5572],{},"\u002Fvar\u002Flib\u002Fdocker",". ",[642,5575],{"value":648},[642,5577],{"value":2895},[33,5579,5580],{},"sudo rm -rf \u002Fvar\u002Flib\u002Fdocker"," will do the job, but will delete everything in ",[33,5583,5572],{},[642,5585],{"value":2902},[25,5587,5589],{"id":5588},"register-the-official-docker-apt-repo","Register the official docker apt repo",[1014,5591,5593],{"id":5592},"make-sure-debian-is-upto-date","Make sure debian is upto date",[66,5595,5597],{"className":5535,"code":5596,"language":5537,"meta":71,"style":71},"sudo apt-get update\nsudo apt-get upgrade\nsudo apt-get install \\\n    ca-certificates \\\n    curl \\\n    gnupg \\\n    lsb-release\n",[33,5598,5599,5608,5617,5629,5636,5643,5650],{"__ignoreMap":71},[75,5600,5601,5603,5605],{"class":77,"line":78},[75,5602,342],{"class":208},[75,5604,5546],{"class":212},[75,5606,5607],{"class":212}," update\n",[75,5609,5610,5612,5614],{"class":77,"line":98},[75,5611,342],{"class":208},[75,5613,5546],{"class":212},[75,5615,5616],{"class":212}," upgrade\n",[75,5618,5619,5621,5623,5625],{"class":77,"line":119},[75,5620,342],{"class":208},[75,5622,5546],{"class":212},[75,5624,232],{"class":212},[75,5626,5628],{"class":5627},"sw7DY"," \\\n",[75,5630,5631,5634],{"class":77,"line":484},[75,5632,5633],{"class":212},"    ca-certificates",[75,5635,5628],{"class":5627},[75,5637,5638,5641],{"class":77,"line":490},[75,5639,5640],{"class":212},"    curl",[75,5642,5628],{"class":5627},[75,5644,5645,5648],{"class":77,"line":499},[75,5646,5647],{"class":212},"    gnupg",[75,5649,5628],{"class":5627},[75,5651,5652],{"class":77,"line":504},[75,5653,5654],{"class":212},"    lsb-release\n",[1014,5656,5658],{"id":5657},"add-dockers-gpg-key","Add Docker's GPG key",[66,5660,5662],{"className":5535,"code":5661,"language":5537,"meta":71,"style":71},"sudo mkdir -m 0755 -p \u002Fetc\u002Fapt\u002Fkeyrings\ncurl -fsSL https:\u002F\u002Fdownload.docker.com\u002Flinux\u002Fdebian\u002Fgpg | sudo gpg --dearmor -o \u002Fetc\u002Fapt\u002Fkeyrings\u002Fdocker.gpg\n",[33,5663,5664,5682],{"__ignoreMap":71},[75,5665,5666,5668,5671,5673,5676,5679],{"class":77,"line":78},[75,5667,342],{"class":208},[75,5669,5670],{"class":212}," mkdir",[75,5672,251],{"class":224},[75,5674,5675],{"class":224}," 0755",[75,5677,5678],{"class":224}," -p",[75,5680,5681],{"class":212}," \u002Fetc\u002Fapt\u002Fkeyrings\n",[75,5683,5684,5687,5690,5693,5695,5697,5700,5703,5706],{"class":77,"line":98},[75,5685,5686],{"class":208},"curl",[75,5688,5689],{"class":224}," -fsSL",[75,5691,5692],{"class":212}," https:\u002F\u002Fdownload.docker.com\u002Flinux\u002Fdebian\u002Fgpg",[75,5694,2309],{"class":85},[75,5696,257],{"class":208},[75,5698,5699],{"class":212}," gpg",[75,5701,5702],{"class":224}," --dearmor",[75,5704,5705],{"class":224}," -o",[75,5707,5708],{"class":212}," \u002Fetc\u002Fapt\u002Fkeyrings\u002Fdocker.gpg\n",[1014,5710,5712,5713],{"id":5711},"add-the-apt-repo-to-etcaptsourceslistd","Add the apt repo to ",[33,5714,5715],{},"\u002Fetc\u002Fapt\u002Fsources.list.d",[66,5717,5719],{"className":5535,"code":5718,"language":5537,"meta":71,"style":71},"echo \\\n  \"deb [arch=$(dpkg --print-architecture) signed-by=\u002Fetc\u002Fapt\u002Fkeyrings\u002Fdocker.gpg] \\ \n  https:\u002F\u002Fdownload.docker.com\u002Flinux\u002Fdebian \\\n  $(lsb_release -cs) stable\" | sudo tee \u002Fetc\u002Fapt\u002Fsources.list.d\u002Fdocker.list > \u002Fdev\u002Fnull\n",[33,5720,5721,5727,5741,5749],{"__ignoreMap":71},[75,5722,5723,5725],{"class":77,"line":78},[75,5724,806],{"class":224},[75,5726,5628],{"class":5627},[75,5728,5729,5732,5735,5738],{"class":77,"line":98},[75,5730,5731],{"class":212},"  \"deb [arch=$(",[75,5733,5734],{"class":208},"dpkg",[75,5736,5737],{"class":224}," --print-architecture",[75,5739,5740],{"class":212},") signed-by=\u002Fetc\u002Fapt\u002Fkeyrings\u002Fdocker.gpg] \\ \n",[75,5742,5743,5746],{"class":77,"line":119},[75,5744,5745],{"class":212},"  https:\u002F\u002Fdownload.docker.com\u002Flinux\u002Fdebian ",[75,5747,5748],{"class":5627},"\\\n",[75,5750,5751,5754,5757,5760,5763,5765,5767,5769,5772,5774],{"class":77,"line":484},[75,5752,5753],{"class":212},"  $(",[75,5755,5756],{"class":208},"lsb_release",[75,5758,5759],{"class":224}," -cs",[75,5761,5762],{"class":212},") stable\"",[75,5764,2309],{"class":85},[75,5766,257],{"class":208},[75,5768,2323],{"class":212},[75,5770,5771],{"class":212}," \u002Fetc\u002Fapt\u002Fsources.list.d\u002Fdocker.list",[75,5773,812],{"class":85},[75,5775,2331],{"class":212},[25,5777,5779],{"id":5778},"install-docker","Install Docker",[1014,5781,5783,5784,5786],{"id":5782},"update-the-apt-packge-files","Update the ",[33,5785,209],{}," packge files",[66,5788,5790],{"className":5535,"code":5789,"language":5537,"meta":71,"style":71},"sudo apt-get update\n",[33,5791,5792],{"__ignoreMap":71},[75,5793,5794,5796,5798],{"class":77,"line":78},[75,5795,342],{"class":208},[75,5797,5546],{"class":212},[75,5799,5607],{"class":212},[1014,5801,5803],{"id":5802},"install-docker-containerd-and-docker-compose","Install Docker, containerd and Docker Compose",[66,5805,5807],{"className":5535,"code":5806,"language":5537,"meta":71,"style":71},"sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\n",[33,5808,5809],{"__ignoreMap":71},[75,5810,5811,5813,5815,5817,5820,5823,5826,5829],{"class":77,"line":78},[75,5812,342],{"class":208},[75,5814,5546],{"class":212},[75,5816,232],{"class":212},[75,5818,5819],{"class":212}," docker-ce",[75,5821,5822],{"class":212}," docker-ce-cli",[75,5824,5825],{"class":212}," containerd.io",[75,5827,5828],{"class":212}," docker-buildx-plugin",[75,5830,5831],{"class":212}," docker-compose-plugin\n",[25,5833,5835],{"id":5834},"test-everything-is-working","Test everything is working",[66,5837,5839],{"className":5535,"code":5838,"language":5537,"meta":71,"style":71},"$ sudo docker run hello-world\n\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https:\u002F\u002Fhub.docker.com\u002F\n\nFor more examples and ideas, visit:\n https:\u002F\u002Fdocs.docker.com\u002Fget-started\u002F\n",[33,5840,5841,5856,5860,5864,5874,5909,5913,5942,5965,5997,6006,6044,6071,6106,6116,6120,6154,6172,6176,6207,6212,6216,6234],{"__ignoreMap":71},[75,5842,5843,5846,5848,5850,5853],{"class":77,"line":78},[75,5844,5845],{"class":208},"$",[75,5847,257],{"class":212},[75,5849,5552],{"class":212},[75,5851,5852],{"class":212}," run",[75,5854,5855],{"class":212}," hello-world\n",[75,5857,5858],{"class":77,"line":98},[75,5859,481],{"emptyLinePlaceholder":480},[75,5861,5862],{"class":77,"line":119},[75,5863,481],{"emptyLinePlaceholder":480},[75,5865,5866,5869,5871],{"class":77,"line":484},[75,5867,5868],{"class":208},"Hello",[75,5870,922],{"class":212},[75,5872,5873],{"class":212}," Docker!\n",[75,5875,5876,5879,5882,5885,5888,5891,5894,5897,5900,5903,5906],{"class":77,"line":490},[75,5877,5878],{"class":208},"This",[75,5880,5881],{"class":212}," message",[75,5883,5884],{"class":212}," shows",[75,5886,5887],{"class":212}," that",[75,5889,5890],{"class":212}," your",[75,5892,5893],{"class":212}," installation",[75,5895,5896],{"class":212}," appears",[75,5898,5899],{"class":212}," to",[75,5901,5902],{"class":212}," be",[75,5904,5905],{"class":212}," working",[75,5907,5908],{"class":212}," correctly.\n",[75,5910,5911],{"class":77,"line":499},[75,5912,481],{"emptyLinePlaceholder":480},[75,5914,5915,5918,5921,5924,5927,5930,5933,5936,5939],{"class":77,"line":504},[75,5916,5917],{"class":208},"To",[75,5919,5920],{"class":212}," generate",[75,5922,5923],{"class":212}," this",[75,5925,5926],{"class":212}," message,",[75,5928,5929],{"class":212}," Docker",[75,5931,5932],{"class":212}," took",[75,5934,5935],{"class":212}," the",[75,5937,5938],{"class":212}," following",[75,5940,5941],{"class":212}," steps:\n",[75,5943,5944,5947,5950,5952,5955,5958,5960,5962],{"class":77,"line":510},[75,5945,5946],{"class":208}," 1.",[75,5948,5949],{"class":212}," The",[75,5951,5929],{"class":212},[75,5953,5954],{"class":212}," client",[75,5956,5957],{"class":212}," contacted",[75,5959,5935],{"class":212},[75,5961,5929],{"class":212},[75,5963,5964],{"class":212}," daemon.\n",[75,5966,5967,5970,5972,5974,5977,5980,5982,5985,5988,5990,5992,5994],{"class":77,"line":519},[75,5968,5969],{"class":208}," 2.",[75,5971,5949],{"class":212},[75,5973,5929],{"class":212},[75,5975,5976],{"class":212}," daemon",[75,5978,5979],{"class":212}," pulled",[75,5981,5935],{"class":212},[75,5983,5984],{"class":212}," \"hello-world\"",[75,5986,5987],{"class":212}," image",[75,5989,922],{"class":212},[75,5991,5935],{"class":212},[75,5993,5929],{"class":212},[75,5995,5996],{"class":212}," Hub.\n",[75,5998,5999,6002,6004],{"class":77,"line":524},[75,6000,6001],{"class":81},"    (",[75,6003,5522],{"class":208},[75,6005,2246],{"class":81},[75,6007,6008,6011,6013,6015,6017,6020,6023,6026,6029,6031,6033,6035,6038,6041],{"class":77,"line":530},[75,6009,6010],{"class":208}," 3.",[75,6012,5949],{"class":212},[75,6014,5929],{"class":212},[75,6016,5976],{"class":212},[75,6018,6019],{"class":212}," created",[75,6021,6022],{"class":212}," a",[75,6024,6025],{"class":212}," new",[75,6027,6028],{"class":212}," container",[75,6030,922],{"class":212},[75,6032,5887],{"class":212},[75,6034,5987],{"class":212},[75,6036,6037],{"class":212}," which",[75,6039,6040],{"class":212}," runs",[75,6042,6043],{"class":212}," the\n",[75,6045,6046,6049,6051,6054,6056,6059,6062,6065,6068],{"class":77,"line":2376},[75,6047,6048],{"class":208},"    executable",[75,6050,5887],{"class":212},[75,6052,6053],{"class":212}," produces",[75,6055,5935],{"class":212},[75,6057,6058],{"class":212}," output",[75,6060,6061],{"class":212}," you",[75,6063,6064],{"class":212}," are",[75,6066,6067],{"class":212}," currently",[75,6069,6070],{"class":212}," reading.\n",[75,6072,6073,6076,6078,6080,6082,6085,6087,6089,6091,6093,6095,6098,6100,6103],{"class":77,"line":3563},[75,6074,6075],{"class":208}," 4.",[75,6077,5949],{"class":212},[75,6079,5929],{"class":212},[75,6081,5976],{"class":212},[75,6083,6084],{"class":212}," streamed",[75,6086,5887],{"class":212},[75,6088,6058],{"class":212},[75,6090,5899],{"class":212},[75,6092,5935],{"class":212},[75,6094,5929],{"class":212},[75,6096,6097],{"class":212}," client,",[75,6099,6037],{"class":212},[75,6101,6102],{"class":212}," sent",[75,6104,6105],{"class":212}," it\n",[75,6107,6108,6111,6113],{"class":77,"line":3568},[75,6109,6110],{"class":208},"    to",[75,6112,5890],{"class":212},[75,6114,6115],{"class":212}," terminal.\n",[75,6117,6118],{"class":77,"line":3583},[75,6119,481],{"emptyLinePlaceholder":480},[75,6121,6122,6124,6127,6130,6133,6136,6138,6141,6143,6146,6149,6151],{"class":77,"line":3602},[75,6123,5917],{"class":208},[75,6125,6126],{"class":212}," try",[75,6128,6129],{"class":212}," something",[75,6131,6132],{"class":212}," more",[75,6134,6135],{"class":212}," ambitious,",[75,6137,6061],{"class":212},[75,6139,6140],{"class":212}," can",[75,6142,5852],{"class":212},[75,6144,6145],{"class":212}," an",[75,6147,6148],{"class":212}," Ubuntu",[75,6150,6028],{"class":212},[75,6152,6153],{"class":212}," with:\n",[75,6155,6156,6159,6161,6163,6166,6169],{"class":77,"line":5236},[75,6157,6158],{"class":208}," $",[75,6160,5552],{"class":212},[75,6162,5852],{"class":212},[75,6164,6165],{"class":224}," -it",[75,6167,6168],{"class":212}," ubuntu",[75,6170,6171],{"class":212}," bash\n",[75,6173,6174],{"class":77,"line":5244},[75,6175,481],{"emptyLinePlaceholder":480},[75,6177,6178,6181,6184,6187,6190,6192,6194,6197,6199,6202,6204],{"class":77,"line":5252},[75,6179,6180],{"class":208},"Share",[75,6182,6183],{"class":212}," images,",[75,6185,6186],{"class":212}," automate",[75,6188,6189],{"class":212}," workflows,",[75,6191,3465],{"class":212},[75,6193,6132],{"class":212},[75,6195,6196],{"class":212}," with",[75,6198,6022],{"class":212},[75,6200,6201],{"class":212}," free",[75,6203,5929],{"class":212},[75,6205,6206],{"class":212}," ID:\n",[75,6208,6209],{"class":77,"line":5257},[75,6210,6211],{"class":208}," https:\u002F\u002Fhub.docker.com\u002F\n",[75,6213,6214],{"class":77,"line":5265},[75,6215,481],{"emptyLinePlaceholder":480},[75,6217,6218,6221,6223,6226,6228,6231],{"class":77,"line":5274},[75,6219,6220],{"class":208},"For",[75,6222,6132],{"class":212},[75,6224,6225],{"class":212}," examples",[75,6227,3465],{"class":212},[75,6229,6230],{"class":212}," ideas,",[75,6232,6233],{"class":212}," visit:\n",[75,6235,6236],{"class":77,"line":5279},[75,6237,6238],{"class":208}," https:\u002F\u002Fdocs.docker.com\u002Fget-started\u002F\n",[21,6240,6241,6242,6245],{},"If you see the above message from running ",[33,6243,6244],{},"sudo docker run hello-world",", docker has been installed correctly and is now ready to start hosting containers for you.",[21,6247,6248,6250,6251,6253,6254,6257,6258],{},[642,6249],{"value":844},"Optionally, you can add yourself to the docker group, so you don't have to use ",[33,6252,342],{}," each time you want to run a docker command.\n",[33,6255,6256],{},"sudo usermod -a -G docker YOUR_USERNAME"," will do the job.",[642,6259],{"value":852},[561,6261,6262],{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sw7DY, html code.shiki .sw7DY{--shiki-default:#005CC5;--shiki-dark:#FF7B72}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}",{"title":71,"searchDepth":98,"depth":98,"links":6264},[6265,6266,6267,6273,6278],{"id":43,"depth":98,"text":44},{"id":5531,"depth":98,"text":5532},{"id":5588,"depth":98,"text":5589,"children":6268},[6269,6270,6271],{"id":5592,"depth":119,"text":5593},{"id":5657,"depth":119,"text":5658},{"id":5711,"depth":119,"text":6272},"Add the apt repo to \u002Fetc\u002Fapt\u002Fsources.list.d",{"id":5778,"depth":98,"text":5779,"children":6274},[6275,6277],{"id":5782,"depth":119,"text":6276},"Update the apt packge files",{"id":5802,"depth":119,"text":5803},{"id":5834,"depth":98,"text":5835},"2022-12-01",{"type":579,"layout":580},"\u002Fblog\u002F2022-12-01-installing-docker-on-debian",{"title":5494,"description":5502},"blog\u002F2022-12-01-installing-docker-on-debian",[3134,442,587,585,586],"YWQdGGM_v5ai-mTw5RvQucesa6gQoB_cKkSFMXzUyx0",{"id":6287,"title":6288,"authors":6289,"body":6290,"date":6660,"description":6294,"extension":577,"meta":6661,"navigation":480,"path":6662,"seo":6663,"stem":6664,"tags":6665,"toc":6,"__hash__":6666},"blog\u002Fblog\u002F2022-11-23-securing-ssh.md","Securing SSH with ssh keys",[15,16],{"type":18,"value":6291,"toc":6654},[6292,6295,6299,6302,6314,6317,6519,6526,6530,6543,6555,6564,6571,6577,6597,6600,6618,6621,6640,6651],[21,6293,6294],{},"In this article I'm going to describe how to secure a SSH server, so that it will only accept logins with ssh keys.",[25,6296,6298],{"id":6297},"generate-a-set-of-personal-ssh-keys","Generate a set of personal ssh keys",[21,6300,6301],{},"If you have already done this, you can skip this step.",[66,6303,6305],{"className":5535,"code":6304,"language":5537,"meta":71,"style":71},"$ ssh-keygen \n",[33,6306,6307],{"__ignoreMap":71},[75,6308,6309,6311],{"class":77,"line":78},[75,6310,5845],{"class":208},[75,6312,6313],{"class":212}," ssh-keygen\n",[21,6315,6316],{},"The output should look like this.",[66,6318,6320],{"className":5535,"code":6319,"language":5537,"meta":71,"style":71},"Generating public\u002Fprivate rsa key pair.\nEnter file in which to save the key (\u002Fhome\u002Fpfarmer\u002F.ssh\u002Fid_rsa):\nEnter passphrase (empty for no passphrase):\nEnter same passphrase again:\nYour identification has been saved in \u002Fhome\u002Fpfarmer\u002F.ssh\u002Fid_rsa\nYour public key has been saved in \u002Fhome\u002Fpfarmer\u002F.ssh\u002Fid_rsa.pub\nThe key fingerprint is:\nSHA256:iHjXjogLDiwNmy3NMc1woBAnGW9ywvUrpy\u002Fh\u002Fd6RqR8 pfarmer@server\nThe key's randomart image is:\n+---[RSA 3072]----+\n|=+o.             |\n|+=...            |\n|+.= ..           |\n| = * ..o         |\n|. +.=oo S        |\n|.O *+o o  o      |\n|B.B.+ . .E       |\n|+o +..  o o      |\n| .. ..o+.o       |\n+----[SHA256]-----+\n",[33,6321,6322,6338,6363,6382,6394,6416,6436,6449,6457,6464,6469,6474,6479,6484,6489,6494,6499,6504,6509,6514],{"__ignoreMap":71},[75,6323,6324,6327,6330,6333,6335],{"class":77,"line":78},[75,6325,6326],{"class":208},"Generating",[75,6328,6329],{"class":212}," public\u002Fprivate",[75,6331,6332],{"class":212}," rsa",[75,6334,909],{"class":212},[75,6336,6337],{"class":212}," pair.\n",[75,6339,6340,6343,6346,6349,6351,6353,6356,6358,6360],{"class":77,"line":98},[75,6341,6342],{"class":208},"Enter",[75,6344,6345],{"class":212}," file",[75,6347,6348],{"class":212}," in",[75,6350,6037],{"class":212},[75,6352,5899],{"class":212},[75,6354,6355],{"class":212}," save",[75,6357,5935],{"class":212},[75,6359,909],{"class":212},[75,6361,6362],{"class":81}," (\u002Fhome\u002Fpfarmer\u002F.ssh\u002Fid_rsa):\n",[75,6364,6365,6367,6370,6373,6375,6378,6380],{"class":77,"line":119},[75,6366,6342],{"class":208},[75,6368,6369],{"class":212}," passphrase",[75,6371,6372],{"class":81}," (empty ",[75,6374,3714],{"class":212},[75,6376,6377],{"class":212}," no",[75,6379,6369],{"class":212},[75,6381,3443],{"class":81},[75,6383,6384,6386,6389,6391],{"class":77,"line":484},[75,6385,6342],{"class":208},[75,6387,6388],{"class":212}," same",[75,6390,6369],{"class":212},[75,6392,6393],{"class":212}," again:\n",[75,6395,6396,6399,6402,6405,6408,6411,6413],{"class":77,"line":490},[75,6397,6398],{"class":208},"Your",[75,6400,6401],{"class":212}," identification",[75,6403,6404],{"class":212}," has",[75,6406,6407],{"class":212}," been",[75,6409,6410],{"class":212}," saved",[75,6412,6348],{"class":212},[75,6414,6415],{"class":212}," \u002Fhome\u002Fpfarmer\u002F.ssh\u002Fid_rsa\n",[75,6417,6418,6420,6423,6425,6427,6429,6431,6433],{"class":77,"line":499},[75,6419,6398],{"class":208},[75,6421,6422],{"class":212}," public",[75,6424,909],{"class":212},[75,6426,6404],{"class":212},[75,6428,6407],{"class":212},[75,6430,6410],{"class":212},[75,6432,6348],{"class":212},[75,6434,6435],{"class":212}," \u002Fhome\u002Fpfarmer\u002F.ssh\u002Fid_rsa.pub\n",[75,6437,6438,6441,6443,6446],{"class":77,"line":504},[75,6439,6440],{"class":208},"The",[75,6442,909],{"class":212},[75,6444,6445],{"class":212}," fingerprint",[75,6447,6448],{"class":212}," is:\n",[75,6450,6451,6454],{"class":77,"line":510},[75,6452,6453],{"class":208},"SHA256:iHjXjogLDiwNmy3NMc1woBAnGW9ywvUrpy\u002Fh\u002Fd6RqR8",[75,6455,6456],{"class":212}," pfarmer@server\n",[75,6458,6459,6461],{"class":77,"line":519},[75,6460,6440],{"class":208},[75,6462,6463],{"class":212}," key's randomart image is:\n",[75,6465,6466],{"class":77,"line":524},[75,6467,6468],{"class":212},"+---[RSA 3072]----+\n",[75,6470,6471],{"class":77,"line":530},[75,6472,6473],{"class":212},"|=+o.             |\n",[75,6475,6476],{"class":77,"line":2376},[75,6477,6478],{"class":212},"|+=...            |\n",[75,6480,6481],{"class":77,"line":3563},[75,6482,6483],{"class":212},"|+.= ..           |\n",[75,6485,6486],{"class":77,"line":3568},[75,6487,6488],{"class":212},"| = * ..o         |\n",[75,6490,6491],{"class":77,"line":3583},[75,6492,6493],{"class":212},"|. +.=oo S        |\n",[75,6495,6496],{"class":77,"line":3602},[75,6497,6498],{"class":212},"|.O *+o o  o      |\n",[75,6500,6501],{"class":77,"line":5236},[75,6502,6503],{"class":212},"|B.B.+ . .E       |\n",[75,6505,6506],{"class":77,"line":5244},[75,6507,6508],{"class":212},"|+o +..  o o      |\n",[75,6510,6511],{"class":77,"line":5252},[75,6512,6513],{"class":212},"| .. ..o+.o       |\n",[75,6515,6516],{"class":77,"line":5257},[75,6517,6518],{"class":212},"+----[SHA256]-----+\n",[21,6520,6521,6523,6524],{},[642,6522],{"value":844},"It would be wise to backup you ssh keys onto a USB stick, later on you will be disabling password authentication on your linux server, and if you only have remote access, if you lose or delete your ssh keys, you won't be able to login to your server again.",[642,6525],{"value":852},[25,6527,6529],{"id":6528},"copy-your-public-key-to-the-server","Copy your public key to the server",[21,6531,6532,6533,6536,6537,6539,6540,615],{},"You now need to copy your public key (",[33,6534,6535],{},"~\u002F.ssh\u002Fid_rsa.pub",") to the server, and put it in ",[33,6538,2077],{},", fortunately there is a handy script to help you do this, ",[33,6541,6542],{},"ssh-copy-id",[66,6544,6546],{"className":5535,"code":6545,"language":5537,"meta":71,"style":71},"ssh-copy-id username@servername\n",[33,6547,6548],{"__ignoreMap":71},[75,6549,6550,6552],{"class":77,"line":78},[75,6551,6542],{"class":208},[75,6553,6554],{"class":212}," username@servername\n",[21,6556,6557,6558,6560,6561,6563],{},"You will be prompted to enter you password, once you have entered it correctly, ",[33,6559,6542],{}," will copy your public key to the server. You should now be able to login to the server with your ssh key, instead of your password, you will now be asked for the passphrase of your ssh key, you can of course setup ",[33,6562,1995],{},", so you don't need to type your ssh key each time.",[25,6565,6567,6568],{"id":6566},"update-etcsshsshd_config","Update ",[33,6569,6570],{},"\u002Fetc\u002Fssh\u002Fsshd_config",[21,6572,6573,6574,6576],{},"Now we make some changes to ",[33,6575,6570],{},", the changes disallow password authentication, and we do a number of other things as well.\nFind and change the following lines;",[66,6578,6580],{"className":5535,"code":6579,"language":5537,"meta":71,"style":71},"PermitRootLogin yes\nPasswordAuthentication yes\n",[33,6581,6582,6590],{"__ignoreMap":71},[75,6583,6584,6587],{"class":77,"line":78},[75,6585,6586],{"class":208},"PermitRootLogin",[75,6588,6589],{"class":212}," yes\n",[75,6591,6592,6595],{"class":77,"line":98},[75,6593,6594],{"class":208},"PasswordAuthentication",[75,6596,6589],{"class":212},[21,6598,6599],{},"to",[66,6601,6603],{"className":5535,"code":6602,"language":5537,"meta":71,"style":71},"PermitRootLogin no\nPasswordAuthentication no\n",[33,6604,6605,6612],{"__ignoreMap":71},[75,6606,6607,6609],{"class":77,"line":78},[75,6608,6586],{"class":208},[75,6610,6611],{"class":212}," no\n",[75,6613,6614,6616],{"class":77,"line":98},[75,6615,6594],{"class":208},[75,6617,6611],{"class":212},[21,6619,6620],{},"Once those changes have been made, you need to restart sshd.",[66,6622,6624],{"className":5535,"code":6623,"language":5537,"meta":71,"style":71},"$ sudo systemctl restart ssh\n",[33,6625,6626],{"__ignoreMap":71},[75,6627,6628,6630,6632,6635,6638],{"class":77,"line":78},[75,6629,5845],{"class":208},[75,6631,257],{"class":212},[75,6633,6634],{"class":212}," systemctl",[75,6636,6637],{"class":212}," restart",[75,6639,726],{"class":212},[21,6641,6642,6644,6645,6648,6649],{},[642,6643],{"value":844},"You ",[415,6646,6647],{},"won't"," be logged out when you restart sshd, so keep the terminal open, and don't logout of the server until you have confirmed you can login via another terminal window.",[642,6650],{"value":852},[561,6652,6653],{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}",{"title":71,"searchDepth":119,"depth":119,"links":6655},[6656,6657,6658],{"id":6297,"depth":98,"text":6298},{"id":6528,"depth":98,"text":6529},{"id":6566,"depth":98,"text":6659},"Update \u002Fetc\u002Fssh\u002Fsshd_config","2022-11-23",{"type":579,"layout":580},"\u002Fblog\u002F2022-11-23-securing-ssh",{"title":6288,"description":6294},"blog\u002F2022-11-23-securing-ssh",[587,585,586,975],"pwM3koRdp4kPlvbE1vaHted53mxpTXtVuvIzAmbrE8E",{"id":6668,"title":6669,"authors":6670,"body":6671,"date":7180,"description":6675,"extension":577,"meta":7181,"navigation":480,"path":7182,"seo":7183,"stem":7184,"tags":7185,"toc":6,"__hash__":7186},"blog\u002Fblog\u002F2022-11-17-python-requests.md","Using Python requests",[15,16],{"type":18,"value":6672,"toc":7173},[6673,6676,6679,6683,6686,6692,6695,6699,6702,6733,6736,6770,6773,6805,6808,6857,6860,6864,6867,6870,6895,6898,6910,6913,6938,6941,6961,6965,6968,7041,7044,7103,7106,7137,7140,7144,7147,7161,7164,7167,7170],[21,6674,6675],{},"The Python requests module is an elegant and simple HTTP library for making requests in Python. Requests allows you to send HTTP requests extremely easily. No need to manually add query strings to URLs, or form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, thanks to urllib3.",[21,6677,6678],{},"With requests, you can focus on interacting with APIs and web services without having to worry about all the fiddly underlying details. In this post, we'll go over some of the basics of using requests to help you get started.",[25,6680,6682],{"id":6681},"installing-requests","Installing Requests",[21,6684,6685],{},"Installing requests is simple with pip:",[66,6687,6690],{"className":6688,"code":6689,"language":327},[325],"pip install requests\n",[33,6691,6689],{"__ignoreMap":71},[21,6693,6694],{},"This will install the latest version of requests from PyPI.",[25,6696,6698],{"id":6697},"making-requests","Making Requests",[21,6700,6701],{},"The requests module allows you to make HTTP requests extremely easily. Let's look at a simple GET request:",[66,6703,6705],{"className":3384,"code":6704,"language":3386,"meta":71,"style":71},"import requests\n\nresponse = requests.get('https:\u002F\u002Fapi.example.com\u002Fdata')\n",[33,6706,6707,6714,6718],{"__ignoreMap":71},[75,6708,6709,6711],{"class":77,"line":78},[75,6710,3393],{"class":85},[75,6712,6713],{"class":81}," requests\n",[75,6715,6716],{"class":77,"line":98},[75,6717,481],{"emptyLinePlaceholder":480},[75,6719,6720,6723,6725,6728,6731],{"class":77,"line":119},[75,6721,6722],{"class":81},"response ",[75,6724,2234],{"class":85},[75,6726,6727],{"class":81}," requests.get(",[75,6729,6730],{"class":212},"'https:\u002F\u002Fapi.example.com\u002Fdata'",[75,6732,2246],{"class":81},[21,6734,6735],{},"To make a POST request, just pass the data parameter:",[66,6737,6739],{"className":3384,"code":6738,"language":3386,"meta":71,"style":71},"requests.post('https:\u002F\u002Fapi.example.com\u002Fuser', data={'name':'John'})\n",[33,6740,6741],{"__ignoreMap":71},[75,6742,6743,6746,6749,6751,6754,6756,6759,6762,6764,6767],{"class":77,"line":78},[75,6744,6745],{"class":81},"requests.post(",[75,6747,6748],{"class":212},"'https:\u002F\u002Fapi.example.com\u002Fuser'",[75,6750,555],{"class":81},[75,6752,6753],{"class":4011},"data",[75,6755,2234],{"class":85},[75,6757,6758],{"class":81},"{",[75,6760,6761],{"class":212},"'name'",[75,6763,2151],{"class":81},[75,6765,6766],{"class":212},"'John'",[75,6768,6769],{"class":81},"})\n",[21,6771,6772],{},"You can also pass query string parameters as a dictionary to the params argument:",[66,6774,6776],{"className":3384,"code":6775,"language":3386,"meta":71,"style":71},"requests.get('https:\u002F\u002Fapi.example.com\u002Fsearch', params={'query':'requests'}) \n",[33,6777,6778],{"__ignoreMap":71},[75,6779,6780,6783,6786,6788,6791,6793,6795,6798,6800,6803],{"class":77,"line":78},[75,6781,6782],{"class":81},"requests.get(",[75,6784,6785],{"class":212},"'https:\u002F\u002Fapi.example.com\u002Fsearch'",[75,6787,555],{"class":81},[75,6789,6790],{"class":4011},"params",[75,6792,2234],{"class":85},[75,6794,6758],{"class":81},[75,6796,6797],{"class":212},"'query'",[75,6799,2151],{"class":81},[75,6801,6802],{"class":212},"'requests'",[75,6804,6769],{"class":81},[21,6806,6807],{},"For POST requests, you may want to send data in JSON format. Set the json parameter to a dictionary:",[66,6809,6811],{"className":3384,"code":6810,"language":3386,"meta":71,"style":71},"data = {'name':'John', 'age':22}\nrequests.post('https:\u002F\u002Fapi.example.com\u002Fuser', json=data)\n",[33,6812,6813,6841],{"__ignoreMap":71},[75,6814,6815,6818,6820,6823,6825,6827,6829,6831,6834,6836,6839],{"class":77,"line":78},[75,6816,6817],{"class":81},"data ",[75,6819,2234],{"class":85},[75,6821,6822],{"class":81}," {",[75,6824,6761],{"class":212},[75,6826,2151],{"class":81},[75,6828,6766],{"class":212},[75,6830,555],{"class":81},[75,6832,6833],{"class":212},"'age'",[75,6835,2151],{"class":81},[75,6837,6838],{"class":224},"22",[75,6840,3269],{"class":81},[75,6842,6843,6845,6847,6849,6852,6854],{"class":77,"line":98},[75,6844,6745],{"class":81},[75,6846,6748],{"class":212},[75,6848,555],{"class":81},[75,6850,6851],{"class":4011},"json",[75,6853,2234],{"class":85},[75,6855,6856],{"class":81},"data)\n",[21,6858,6859],{},"There are convenience methods like put(), delete(), head() and options() too for other HTTP verbs.",[25,6861,6863],{"id":6862},"response-content","Response Content",[21,6865,6866],{},"The Response object returned by requests contains all the information about the request.",[21,6868,6869],{},"The status code is accessed through the status_code attribute:",[66,6871,6873],{"className":3384,"code":6872,"language":3386,"meta":71,"style":71},"response = requests.get('https:\u002F\u002Fapi.example.com\u002Fdata')\nprint(response.status_code)\n",[33,6874,6875,6887],{"__ignoreMap":71},[75,6876,6877,6879,6881,6883,6885],{"class":77,"line":78},[75,6878,6722],{"class":81},[75,6880,2234],{"class":85},[75,6882,6727],{"class":81},[75,6884,6730],{"class":212},[75,6886,2246],{"class":81},[75,6888,6889,6892],{"class":77,"line":98},[75,6890,6891],{"class":224},"print",[75,6893,6894],{"class":81},"(response.status_code)\n",[21,6896,6897],{},"The text content is accessible by calling .text on the Response:",[66,6899,6901],{"className":3384,"code":6900,"language":3386,"meta":71,"style":71},"print(response.text)\n",[33,6902,6903],{"__ignoreMap":71},[75,6904,6905,6907],{"class":77,"line":78},[75,6906,6891],{"class":224},[75,6908,6909],{"class":81},"(response.text)\n",[21,6911,6912],{},"For JSON data, use the json() method:",[66,6914,6916],{"className":3384,"code":6915,"language":3386,"meta":71,"style":71},"data = response.json()\nprint(data['name'])  \n",[33,6917,6918,6927],{"__ignoreMap":71},[75,6919,6920,6922,6924],{"class":77,"line":78},[75,6921,6817],{"class":81},[75,6923,2234],{"class":85},[75,6925,6926],{"class":81}," response.json()\n",[75,6928,6929,6931,6934,6936],{"class":77,"line":98},[75,6930,6891],{"class":224},[75,6932,6933],{"class":81},"(data[",[75,6935,6761],{"class":212},[75,6937,3599],{"class":81},[21,6939,6940],{},"If any errors occur, you can check the Response error attribute. response.raise_for_status() will raise an exception if the status is 4XX or 5XX:",[66,6942,6944],{"className":3384,"code":6943,"language":3386,"meta":71,"style":71},"response.raise_for_status()\nresponse.status_code == requests.codes.ok\n",[33,6945,6946,6951],{"__ignoreMap":71},[75,6947,6948],{"class":77,"line":78},[75,6949,6950],{"class":81},"response.raise_for_status()\n",[75,6952,6953,6956,6958],{"class":77,"line":98},[75,6954,6955],{"class":81},"response.status_code ",[75,6957,4779],{"class":85},[75,6959,6960],{"class":81}," requests.codes.ok\n",[25,6962,6964],{"id":6963},"advanced-usage","Advanced Usage",[21,6966,6967],{},"Requests makes it easy to do more advanced operations like persisting sessions across requests using Sessions:",[66,6969,6971],{"className":3384,"code":6970,"language":3386,"meta":71,"style":71},"session = requests.Session()\n\nsession.get('https:\u002F\u002Fapi.example.com\u002Fuser')\n\nr = session.post('https:\u002F\u002Fapi.example.com\u002Flogin', data={'user':'foo', 'pass':'bar'})\n",[33,6972,6973,6983,6987,6996,7000],{"__ignoreMap":71},[75,6974,6975,6978,6980],{"class":77,"line":78},[75,6976,6977],{"class":81},"session ",[75,6979,2234],{"class":85},[75,6981,6982],{"class":81}," requests.Session()\n",[75,6984,6985],{"class":77,"line":98},[75,6986,481],{"emptyLinePlaceholder":480},[75,6988,6989,6992,6994],{"class":77,"line":119},[75,6990,6991],{"class":81},"session.get(",[75,6993,6748],{"class":212},[75,6995,2246],{"class":81},[75,6997,6998],{"class":77,"line":484},[75,6999,481],{"emptyLinePlaceholder":480},[75,7001,7002,7005,7007,7010,7013,7015,7017,7019,7021,7024,7026,7029,7031,7034,7036,7039],{"class":77,"line":490},[75,7003,7004],{"class":81},"r ",[75,7006,2234],{"class":85},[75,7008,7009],{"class":81}," session.post(",[75,7011,7012],{"class":212},"'https:\u002F\u002Fapi.example.com\u002Flogin'",[75,7014,555],{"class":81},[75,7016,6753],{"class":4011},[75,7018,2234],{"class":85},[75,7020,6758],{"class":81},[75,7022,7023],{"class":212},"'user'",[75,7025,2151],{"class":81},[75,7027,7028],{"class":212},"'foo'",[75,7030,555],{"class":81},[75,7032,7033],{"class":212},"'pass'",[75,7035,2151],{"class":81},[75,7037,7038],{"class":212},"'bar'",[75,7040,6769],{"class":81},[21,7042,7043],{},"You can stream responses directly without having to load everything into memory:",[66,7045,7047],{"className":3384,"code":7046,"language":3386,"meta":71,"style":71},"with requests.get('https:\u002F\u002Fapi.example.com\u002Fbigfile', stream=True) as r:\n    for chunk in r.iter_content(1024):\n        print(chunk)\n",[33,7048,7049,7077,7095],{"__ignoreMap":71},[75,7050,7051,7054,7056,7059,7061,7064,7066,7069,7071,7074],{"class":77,"line":78},[75,7052,7053],{"class":85},"with",[75,7055,6727],{"class":81},[75,7057,7058],{"class":212},"'https:\u002F\u002Fapi.example.com\u002Fbigfile'",[75,7060,555],{"class":81},[75,7062,7063],{"class":4011},"stream",[75,7065,2234],{"class":85},[75,7067,7068],{"class":224},"True",[75,7070,4195],{"class":81},[75,7072,7073],{"class":85},"as",[75,7075,7076],{"class":81}," r:\n",[75,7078,7079,7082,7085,7087,7090,7093],{"class":77,"line":98},[75,7080,7081],{"class":85},"    for",[75,7083,7084],{"class":81}," chunk ",[75,7086,3423],{"class":85},[75,7088,7089],{"class":81}," r.iter_content(",[75,7091,7092],{"class":224},"1024",[75,7094,3443],{"class":81},[75,7096,7097,7100],{"class":77,"line":119},[75,7098,7099],{"class":224},"        print",[75,7101,7102],{"class":81},"(chunk)\n",[21,7104,7105],{},"To use HTTP Basic Auth or Digest Auth, you can pass a tuple of (username, password) to the auth parameter:",[66,7107,7109],{"className":3384,"code":7108,"language":3386,"meta":71,"style":71},"requests.get('https:\u002F\u002Fapi.example.com\u002F', auth=('user','pass'))\n",[33,7110,7111],{"__ignoreMap":71},[75,7112,7113,7115,7118,7120,7123,7125,7127,7129,7132,7134],{"class":77,"line":78},[75,7114,6782],{"class":81},[75,7116,7117],{"class":212},"'https:\u002F\u002Fapi.example.com\u002F'",[75,7119,555],{"class":81},[75,7121,7122],{"class":4011},"auth",[75,7124,2234],{"class":85},[75,7126,3429],{"class":81},[75,7128,7023],{"class":212},[75,7130,7131],{"class":81},",",[75,7133,7033],{"class":212},[75,7135,7136],{"class":81},"))\n",[21,7138,7139],{},"Requests supports proxies, timeout settings, SSL verification, and much more. See the full documentation for details.",[25,7141,7143],{"id":7142},"recap","Recap",[21,7145,7146],{},"Hopefully this gives you a quick overview of how to get started with the Python requests module! Here's a quick recap:",[49,7148,7149,7152,7155,7158],{},[52,7150,7151],{},"Installing is easy with pip",[52,7153,7154],{},"Make HTTP requests with GET, POST, PUT, DELETE and more",[52,7156,7157],{},"Access response status, text, headers, JSON",[52,7159,7160],{},"Use advanced features like sessions, proxies, authentication",[21,7162,7163],{},"With its elegant API, Requests makes working with HTTP and APIs in Python a breeze. Be sure to check out the full documentation for more details. Happy making requests!",[21,7165,7166],{},"The requests module is a crucial part of the Python ecosystem. By handling the nitty-gritty details of making HTTP requests, it allows developers to focus on the essentials of their application. Requests makes integrating with web services and APIs a straightforward process.",[21,7168,7169],{},"I encourage you to try it out on your next Python project that involves any type of HTTP communication. The simple yet powerful API will quickly become something you can't live without!",[561,7171,7172],{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjlk-, html code.shiki .sjlk-{--shiki-default:#E36209;--shiki-dark:#FFA657}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}",{"title":71,"searchDepth":119,"depth":119,"links":7174},[7175,7176,7177,7178,7179],{"id":6681,"depth":98,"text":6682},{"id":6697,"depth":98,"text":6698},{"id":6862,"depth":98,"text":6863},{"id":6963,"depth":98,"text":6964},{"id":7142,"depth":98,"text":7143},"2022-11-17",{"type":579,"layout":580},"\u002Fblog\u002F2022-11-17-python-requests",{"title":6669,"description":6675},"blog\u002F2022-11-17-python-requests",[3386,2942,585],"oWvTltH_Oc8Ywu0eEYywckw6JpH0MMEKAVvJbogsnCA",{"id":7188,"title":7189,"authors":7190,"body":7191,"date":7535,"description":7195,"extension":577,"meta":7536,"navigation":480,"path":7537,"seo":7538,"stem":7539,"tags":7540,"toc":480,"__hash__":7541},"blog\u002Fblog\u002F2022-11-03-python-logging.md","Using Python logging",[15,16],{"type":18,"value":7192,"toc":7527},[7193,7196,7213,7216,7220,7223,7240,7243,7247,7250,7305,7325,7328,7334,7341,7345,7348,7417,7420,7424,7427,7458,7464,7468,7471,7491,7494,7496,7499,7516,7519,7521,7524],[21,7194,7195],{},"The logging module in Python provides a standardized way to record diagnostic and runtime information from your programs. The key benefits of logging are the ability to:",[49,7197,7198,7201,7204,7207,7210],{},[52,7199,7200],{},"Log messages with different verbosity levels (debug, info, warning, error)",[52,7202,7203],{},"Flexibly configure how and where log messages are outputted",[52,7205,7206],{},"Format log message output",[52,7208,7209],{},"Filter log messages",[52,7211,7212],{},"Log exceptions and stack traces",[21,7214,7215],{},"In this article, we'll cover the basic and advanced features of the logging module.",[25,7217,7219],{"id":7218},"logging-levels","Logging Levels",[21,7221,7222],{},"The logging module provides the following severity levels to indicate the importance of log messages:",[49,7224,7225,7228,7231,7234,7237],{},[52,7226,7227],{},"DEBUG - Debugging information, typically only enabled during development",[52,7229,7230],{},"INFO - General information about program operation",[52,7232,7233],{},"WARNING - Warning messages about possible issues",[52,7235,7236],{},"ERROR - Error messages when something has gone wrong",[52,7238,7239],{},"CRITICAL - Very severe error events",[21,7241,7242],{},"By default, the logging module captures all messages at WARNING and above.",[25,7244,7246],{"id":7245},"basic-logging-configuration","Basic Logging Configuration",[21,7248,7249],{},"To start logging from Python:",[66,7251,7253],{"className":3384,"code":7252,"language":3386,"meta":71,"style":71},"import logging\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.INFO) \n\nlogger.info(\"Started\")\n",[33,7254,7255,7262,7266,7281,7291,7295],{"__ignoreMap":71},[75,7256,7257,7259],{"class":77,"line":78},[75,7258,3393],{"class":85},[75,7260,7261],{"class":81}," logging\n",[75,7263,7264],{"class":77,"line":98},[75,7265,481],{"emptyLinePlaceholder":480},[75,7267,7268,7271,7273,7276,7279],{"class":77,"line":119},[75,7269,7270],{"class":81},"logger ",[75,7272,2234],{"class":85},[75,7274,7275],{"class":81}," logging.getLogger(",[75,7277,7278],{"class":224},"__name__",[75,7280,2246],{"class":81},[75,7282,7283,7286,7289],{"class":77,"line":484},[75,7284,7285],{"class":81},"logger.setLevel(logging.",[75,7287,7288],{"class":224},"INFO",[75,7290,4589],{"class":81},[75,7292,7293],{"class":77,"line":490},[75,7294,481],{"emptyLinePlaceholder":480},[75,7296,7297,7300,7303],{"class":77,"line":499},[75,7298,7299],{"class":81},"logger.info(",[75,7301,7302],{"class":212},"\"Started\"",[75,7304,2246],{"class":81},[49,7306,7307,7310,7316,7322],{},[52,7308,7309],{},"Import the logging module",[52,7311,7312,7313],{},"Get a logger instance using ",[33,7314,7315],{},"getLogger()",[52,7317,7318,7319],{},"Set the log level on the logger using ",[33,7320,7321],{},"setLevel()",[52,7323,7324],{},"Call log methods on the logger to generate log messages",[21,7326,7327],{},"This outputs a log message like:",[66,7329,7332],{"className":7330,"code":7331,"language":327},[325],"INFO:__main__:Started\n",[33,7333,7331],{"__ignoreMap":71},[21,7335,7336,7337,7340],{},"The log message contains the level (INFO), logger name (",[415,7338,7339],{},"main","), and message (Started).",[25,7342,7344],{"id":7343},"log-message-formatting","Log Message Formatting",[21,7346,7347],{},"To configure how log messages are formatted:",[66,7349,7351],{"className":3384,"code":7350,"language":3386,"meta":71,"style":71},"formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')\n\nhandler = logging.StreamHandler()\nhandler.setFormatter(formatter)\n\nlogger.addHandler(handler)\n",[33,7352,7353,7389,7393,7403,7408,7412],{"__ignoreMap":71},[75,7354,7355,7358,7360,7363,7366,7369,7372,7375,7377,7380,7382,7385,7387],{"class":77,"line":78},[75,7356,7357],{"class":81},"formatter ",[75,7359,2234],{"class":85},[75,7361,7362],{"class":81}," logging.Formatter(",[75,7364,7365],{"class":212},"'",[75,7367,7368],{"class":5627},"%(asctime)s",[75,7370,7371],{"class":212}," - ",[75,7373,7374],{"class":5627},"%(name)s",[75,7376,7371],{"class":212},[75,7378,7379],{"class":5627},"%(levelname)s",[75,7381,7371],{"class":212},[75,7383,7384],{"class":5627},"%(message)s",[75,7386,7365],{"class":212},[75,7388,2246],{"class":81},[75,7390,7391],{"class":77,"line":98},[75,7392,481],{"emptyLinePlaceholder":480},[75,7394,7395,7398,7400],{"class":77,"line":119},[75,7396,7397],{"class":81},"handler ",[75,7399,2234],{"class":85},[75,7401,7402],{"class":81}," logging.StreamHandler()\n",[75,7404,7405],{"class":77,"line":484},[75,7406,7407],{"class":81},"handler.setFormatter(formatter)\n",[75,7409,7410],{"class":77,"line":490},[75,7411,481],{"emptyLinePlaceholder":480},[75,7413,7414],{"class":77,"line":499},[75,7415,7416],{"class":81},"logger.addHandler(handler)\n",[21,7418,7419],{},"This formats each message as: timestamp - logger_name - level - message",[25,7421,7423],{"id":7422},"logging-to-a-file","Logging to a File",[21,7425,7426],{},"To log messages to a file instead of the console:",[66,7428,7430],{"className":3384,"code":7429,"language":3386,"meta":71,"style":71},"handler = logging.FileHandler('app.log')\nhandler.setFormatter(formatter)\n\nlogger.addHandler(handler) \n",[33,7431,7432,7446,7450,7454],{"__ignoreMap":71},[75,7433,7434,7436,7438,7441,7444],{"class":77,"line":78},[75,7435,7397],{"class":81},[75,7437,2234],{"class":85},[75,7439,7440],{"class":81}," logging.FileHandler(",[75,7442,7443],{"class":212},"'app.log'",[75,7445,2246],{"class":81},[75,7447,7448],{"class":77,"line":98},[75,7449,7407],{"class":81},[75,7451,7452],{"class":77,"line":119},[75,7453,481],{"emptyLinePlaceholder":480},[75,7455,7456],{"class":77,"line":484},[75,7457,7416],{"class":81},[21,7459,7460,7461,615],{},"This will write log messages to ",[33,7462,7463],{},"app.log",[25,7465,7467],{"id":7466},"advanced-configuration","Advanced Configuration",[21,7469,7470],{},"Here are some common advanced configuration needs:",[49,7472,7473,7476,7479,7482,7485,7488],{},[52,7474,7475],{},"Configure separate loggers for different modules\u002Fcomponents",[52,7477,7478],{},"Log to multiple destinations simultaneously",[52,7480,7481],{},"Set different log levels for different loggers",[52,7483,7484],{},"Custom logging handlers for syslog, HTTP, databases, etc",[52,7486,7487],{},"Handler filters to only capture certain log messages",[52,7489,7490],{},"Rotation of log files based on size",[21,7492,7493],{},"All this is achieved by working directly with the objects from the logging module - loggers, handlers, formatters, filters etc.",[25,7495,1601],{"id":1600},[21,7497,7498],{},"Some key best practices for working with logging in Python:",[49,7500,7501,7504,7507,7510,7513],{},[52,7502,7503],{},"Configure logging as early as possible in app initialisation",[52,7505,7506],{},"Use INFO for overall operational logging and DEBUG for diagnostics",[52,7508,7509],{},"Always log exceptions to get full tracebacks",[52,7511,7512],{},"Use meaningful logger names based on application\u002Fmodule",[52,7514,7515],{},"Avoid logging confidential information",[21,7517,7518],{},"This helps ensure logging is setup properly and provides the most useful diagnostic information.",[984,7520,541],{"id":540},[21,7522,7523],{},"Python's logging module provides a full-featured and flexible logging system. It takes some initial configuration, but once setup allows comprehensive logging and debugging with control over how much information is captured and where it is sent. Following the standard patterns and best practices will ensure you get the most benefit.",[561,7525,7526],{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sw7DY, html code.shiki .sw7DY{--shiki-default:#005CC5;--shiki-dark:#FF7B72}",{"title":71,"searchDepth":98,"depth":98,"links":7528},[7529,7530,7531,7532,7533,7534],{"id":7218,"depth":98,"text":7219},{"id":7245,"depth":98,"text":7246},{"id":7343,"depth":98,"text":7344},{"id":7422,"depth":98,"text":7423},{"id":7466,"depth":98,"text":7467},{"id":1600,"depth":98,"text":1601},"2022-11-03",{"type":579,"layout":580},"\u002Fblog\u002F2022-11-03-python-logging",{"title":7189,"description":7195},"blog\u002F2022-11-03-python-logging",[585,3386,2942],"MmkmDkfm51BXO5akJZVTQ-nIrr5lZLXZn77wRD4Fqzg",{"id":7543,"title":7544,"authors":7545,"body":7546,"date":7932,"description":7550,"extension":577,"meta":7933,"navigation":480,"path":7934,"seo":7935,"stem":7936,"tags":7937,"toc":480,"__hash__":7938},"blog\u002Fblog\u002F2022-10-15-using-nft.md","Using Linux nftables",[15,16],{"type":18,"value":7547,"toc":7925},[7548,7551,7555,7558,7561,7577,7580,7584,7587,7613,7619,7623,7638,7645,7669,7679,7720,7723,7727,7730,7735,7766,7771,7799,7804,7832,7837,7870,7873,7877,7880,7899,7902,7916,7919,7922],[21,7549,7550],{},"nftables is the new packet filtering framework built into the Linux kernel that replaces the older iptables technology. This tutorial will cover the basics of using nftables to configure a simple firewall on a Linux system.",[25,7552,7554],{"id":7553},"installing-nftables","Installing nftables",[21,7556,7557],{},"Most modern Linux distributions already have nftables installed and enabled by default.",[21,7559,7560],{},"On Debian\u002FUbuntu systems, you can install it with:",[66,7562,7564],{"className":5535,"code":7563,"language":5537,"meta":71,"style":71},"sudo apt install nftables\n",[33,7565,7566],{"__ignoreMap":71},[75,7567,7568,7570,7572,7574],{"class":77,"line":78},[75,7569,342],{"class":208},[75,7571,345],{"class":212},[75,7573,232],{"class":212},[75,7575,7576],{"class":212}," nftables\n",[21,7578,7579],{},"This will install the nft command line utility we will use to interact with nftables.",[25,7581,7583],{"id":7582},"nft-command-syntax","nft Command Syntax",[21,7585,7586],{},"The nft command allows us to add, delete, and list rules and sets for packet filtering. Some common syntax includes:",[49,7588,7589,7595,7601,7607],{},[52,7590,7591,7594],{},[33,7592,7593],{},"nft list ruleset"," - List all tables and chains",[52,7596,7597,7600],{},[33,7598,7599],{},"nft list table \u003Ctable>"," - List all chains in a specific table",[52,7602,7603,7606],{},[33,7604,7605],{},"nft add rule \u003Ctable> \u003Cchain> \u003Crulespec>"," - Append a rule to a chain",[52,7608,7609,7612],{},[33,7610,7611],{},"nft delete rule \u003Ctable> \u003Cchain> \u003Chandle>"," - Delete a specific rule",[21,7614,439,7615,7618],{},[33,7616,7617],{},"\u003Crulespec>"," defines the matching criteria and actions for packets.",[25,7620,7622],{"id":7621},"creating-custom-chains","Creating Custom Chains",[21,7624,7625,7626,7628,7629,555,7632,2606,7635,615],{},"By default nftables includes some built-in tables like ",[33,7627,4375],{}," which contains chains like ",[33,7630,7631],{},"input",[33,7633,7634],{},"output",[33,7636,7637],{},"forward",[21,7639,7640,7641,7644],{},"We can create custom chains under these tables using the ",[33,7642,7643],{},"add chain"," command:",[66,7646,7648],{"className":5535,"code":7647,"language":5537,"meta":71,"style":71},"nft add chain filter input my_chain\n",[33,7649,7650],{"__ignoreMap":71},[75,7651,7652,7655,7657,7660,7663,7666],{"class":77,"line":78},[75,7653,7654],{"class":208},"nft",[75,7656,822],{"class":212},[75,7658,7659],{"class":212}," chain",[75,7661,7662],{"class":212}," filter",[75,7664,7665],{"class":212}," input",[75,7667,7668],{"class":212}," my_chain\n",[21,7670,7671,7672,7675,7676,7678],{},"This adds a new chain called ",[33,7673,7674],{},"my_chain"," under the ",[33,7677,7631],{}," table. We can now add rules to this chain:",[66,7680,7682],{"className":5535,"code":7681,"language":5537,"meta":71,"style":71},"nft add rule filter input my_chain tcp dport { 22, 80 } accept\n",[33,7683,7684],{"__ignoreMap":71},[75,7685,7686,7688,7690,7693,7695,7697,7700,7703,7706,7708,7711,7714,7717],{"class":77,"line":78},[75,7687,7654],{"class":208},[75,7689,822],{"class":212},[75,7691,7692],{"class":212}," rule",[75,7694,7662],{"class":212},[75,7696,7665],{"class":212},[75,7698,7699],{"class":212}," my_chain",[75,7701,7702],{"class":212}," tcp",[75,7704,7705],{"class":212}," dport",[75,7707,6822],{"class":212},[75,7709,7710],{"class":212}," 22,",[75,7712,7713],{"class":224}," 80",[75,7715,7716],{"class":212}," }",[75,7718,7719],{"class":212}," accept\n",[21,7721,7722],{},"This will accept TCP traffic on ports 22 and 80.",[25,7724,7726],{"id":7725},"filtering-packets","Filtering Packets",[21,7728,7729],{},"nftables provides flexible syntax for matching packets and taking actions. Some examples:",[49,7731,7732],{},[52,7733,7734],{},"Match specific IP address",[66,7736,7738],{"className":5535,"code":7737,"language":5537,"meta":71,"style":71},"nft add rule filter input my_chain ip saddr 192.168.0.5 drop\n",[33,7739,7740],{"__ignoreMap":71},[75,7741,7742,7744,7746,7748,7750,7752,7754,7757,7760,7763],{"class":77,"line":78},[75,7743,7654],{"class":208},[75,7745,822],{"class":212},[75,7747,7692],{"class":212},[75,7749,7662],{"class":212},[75,7751,7665],{"class":212},[75,7753,7699],{"class":212},[75,7755,7756],{"class":212}," ip",[75,7758,7759],{"class":212}," saddr",[75,7761,7762],{"class":224}," 192.168.0.5",[75,7764,7765],{"class":212}," drop\n",[49,7767,7768],{},[52,7769,7770],{},"Match a port range",[66,7772,7774],{"className":5535,"code":7773,"language":5537,"meta":71,"style":71},"nft add rule filter input my_chain tcp dport {1024-65535} accept\n",[33,7775,7776],{"__ignoreMap":71},[75,7777,7778,7780,7782,7784,7786,7788,7790,7792,7794,7797],{"class":77,"line":78},[75,7779,7654],{"class":208},[75,7781,822],{"class":212},[75,7783,7692],{"class":212},[75,7785,7662],{"class":212},[75,7787,7665],{"class":212},[75,7789,7699],{"class":212},[75,7791,7702],{"class":212},[75,7793,7705],{"class":212},[75,7795,7796],{"class":212}," {1024-65535}",[75,7798,7719],{"class":212},[49,7800,7801],{},[52,7802,7803],{},"Match a network subnet",[66,7805,7807],{"className":5535,"code":7806,"language":5537,"meta":71,"style":71},"nft add rule filter input my_chain ip saddr 192.168.0.0\u002F24 drop\n",[33,7808,7809],{"__ignoreMap":71},[75,7810,7811,7813,7815,7817,7819,7821,7823,7825,7827,7830],{"class":77,"line":78},[75,7812,7654],{"class":208},[75,7814,822],{"class":212},[75,7816,7692],{"class":212},[75,7818,7662],{"class":212},[75,7820,7665],{"class":212},[75,7822,7699],{"class":212},[75,7824,7756],{"class":212},[75,7826,7759],{"class":212},[75,7828,7829],{"class":212}," 192.168.0.0\u002F24",[75,7831,7765],{"class":212},[49,7833,7834],{},[52,7835,7836],{},"Match by protocol and limit rate",[66,7838,7840],{"className":5535,"code":7839,"language":5537,"meta":71,"style":71},"nft add rule filter input my_chain icmp limit rate 1\u002Fsecond accept\n",[33,7841,7842],{"__ignoreMap":71},[75,7843,7844,7846,7848,7850,7852,7854,7856,7859,7862,7865,7868],{"class":77,"line":78},[75,7845,7654],{"class":208},[75,7847,822],{"class":212},[75,7849,7692],{"class":212},[75,7851,7662],{"class":212},[75,7853,7665],{"class":212},[75,7855,7699],{"class":212},[75,7857,7858],{"class":212}," icmp",[75,7860,7861],{"class":212}," limit",[75,7863,7864],{"class":212}," rate",[75,7866,7867],{"class":212}," 1\u002Fsecond",[75,7869,7719],{"class":212},[21,7871,7872],{},"The nft man pages provide more detail on all the matching criteria and actions available.",[25,7874,7876],{"id":7875},"saving-and-loading-rulesets","Saving and Loading Rulesets",[21,7878,7879],{},"To persist rules between reboots, we can save our nftables configuration to a file:",[66,7881,7883],{"className":5535,"code":7882,"language":5537,"meta":71,"style":71},"nft list ruleset > \u002Fetc\u002Fnftables.conf\n",[33,7884,7885],{"__ignoreMap":71},[75,7886,7887,7889,7891,7894,7896],{"class":77,"line":78},[75,7888,7654],{"class":208},[75,7890,4088],{"class":212},[75,7892,7893],{"class":212}," ruleset",[75,7895,812],{"class":85},[75,7897,7898],{"class":212}," \u002Fetc\u002Fnftables.conf\n",[21,7900,7901],{},"This will save the current nftables rules. We can load it on startup by adding:",[66,7903,7905],{"className":5535,"code":7904,"language":5537,"meta":71,"style":71},"nft -f \u002Fetc\u002Fnftables.conf\n",[33,7906,7907],{"__ignoreMap":71},[75,7908,7909,7911,7914],{"class":77,"line":78},[75,7910,7654],{"class":208},[75,7912,7913],{"class":224}," -f",[75,7915,7898],{"class":212},[21,7917,7918],{},"to our systemd unit file or init scripts.",[21,7920,7921],{},"This covers the basics of configuring a simple firewall with nftables on Linux. nftables provides a flexible and powerful framework for packet filtering and firewalling. As iptables is phased out, knowing how to use nftables will become an essential skill for Linux system administration.",[561,7923,7924],{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}",{"title":71,"searchDepth":98,"depth":98,"links":7926},[7927,7928,7929,7930,7931],{"id":7553,"depth":98,"text":7554},{"id":7582,"depth":98,"text":7583},{"id":7621,"depth":98,"text":7622},{"id":7725,"depth":98,"text":7726},{"id":7875,"depth":98,"text":7876},"2022-10-15",{"type":579,"layout":580},"\u002Fblog\u002F2022-10-15-using-nft",{"title":7544,"description":7550},"blog\u002F2022-10-15-using-nft",[3134,587,585,586],"qL9miTXHs11nOYmIoM5CeRkHOdYhxq8lkq8BEGsX8eE",{"id":7940,"title":7941,"authors":7942,"body":7943,"date":8505,"description":7947,"extension":577,"meta":8506,"navigation":480,"path":8507,"seo":8508,"stem":8509,"tags":8510,"toc":480,"__hash__":8511},"blog\u002Fblog\u002F2022-10-01-using-python-argparse.md","Using Python argparse",[15,16],{"type":18,"value":7944,"toc":8499},[7945,7948,7952,7955,7958,7975,7978,7982,7985,8125,8128,8131,8135,8138,8262,8265,8269,8272,8484,8487,8490,8493,8496],[21,7946,7947],{},"Python 3.11 introduced a new and improved argument parser in the argparse module. The new argument parser provides several handy features that make it easier to parse command line arguments in Python scripts. In this post, we'll take a look at how to use Python's new argument parser.",[25,7949,7951],{"id":7950},"background","Background",[21,7953,7954],{},"The argparse module has been around for a while in Python, providing a standard way to handle command line arguments. The old parser was good, but had some annoyances and limitations.",[21,7956,7957],{},"The new parser in Python 3.11 aims to resolve many of those issues and provide a more powerful and flexible interface for parsing arguments. Some of the major improvements include:",[49,7959,7960,7963,7966,7969,7972],{},[52,7961,7962],{},"Support for subcommands",[52,7964,7965],{},"Ability to define argument groups",[52,7967,7968],{},"More flexible naming and prefixes",[52,7970,7971],{},"Better help messages",[52,7973,7974],{},"Validation of argument types",[21,7976,7977],{},"Overall, the new parser makes it simpler and more intuitive to build robust command line interfaces in Python.",[25,7979,7981],{"id":7980},"basic-usage","Basic Usage",[21,7983,7984],{},"Let's look at a simple example of how to use the new argument parser. We'll write a Python script that takes an input file, output file, and an optional verbosity flag:",[66,7986,7988],{"className":3384,"code":7987,"language":3386,"meta":71,"style":71},"import argparse\n\nparser = argparse.ArgumentParser()\n\nparser.add_argument(\"input\", help=\"input file\")\nparser.add_argument(\"output\", help=\"output file\")\nparser.add_argument(\"-v\", \"--verbose\", action=\"store_true\", help=\"increase verbosity\")\n\nargs = parser.parse_args()\n\nprint(args.input)\nprint(args.output)\nprint(args.verbose)\n",[33,7989,7990,7997,8001,8011,8015,8035,8053,8086,8090,8100,8104,8111,8118],{"__ignoreMap":71},[75,7991,7992,7994],{"class":77,"line":78},[75,7993,3393],{"class":85},[75,7995,7996],{"class":81}," argparse\n",[75,7998,7999],{"class":77,"line":98},[75,8000,481],{"emptyLinePlaceholder":480},[75,8002,8003,8006,8008],{"class":77,"line":119},[75,8004,8005],{"class":81},"parser ",[75,8007,2234],{"class":85},[75,8009,8010],{"class":81}," argparse.ArgumentParser()\n",[75,8012,8013],{"class":77,"line":484},[75,8014,481],{"emptyLinePlaceholder":480},[75,8016,8017,8020,8023,8025,8028,8030,8033],{"class":77,"line":490},[75,8018,8019],{"class":81},"parser.add_argument(",[75,8021,8022],{"class":212},"\"input\"",[75,8024,555],{"class":81},[75,8026,8027],{"class":4011},"help",[75,8029,2234],{"class":85},[75,8031,8032],{"class":212},"\"input file\"",[75,8034,2246],{"class":81},[75,8036,8037,8039,8042,8044,8046,8048,8051],{"class":77,"line":499},[75,8038,8019],{"class":81},[75,8040,8041],{"class":212},"\"output\"",[75,8043,555],{"class":81},[75,8045,8027],{"class":4011},[75,8047,2234],{"class":85},[75,8049,8050],{"class":212},"\"output file\"",[75,8052,2246],{"class":81},[75,8054,8055,8057,8060,8062,8065,8067,8070,8072,8075,8077,8079,8081,8084],{"class":77,"line":504},[75,8056,8019],{"class":81},[75,8058,8059],{"class":212},"\"-v\"",[75,8061,555],{"class":81},[75,8063,8064],{"class":212},"\"--verbose\"",[75,8066,555],{"class":81},[75,8068,8069],{"class":4011},"action",[75,8071,2234],{"class":85},[75,8073,8074],{"class":212},"\"store_true\"",[75,8076,555],{"class":81},[75,8078,8027],{"class":4011},[75,8080,2234],{"class":85},[75,8082,8083],{"class":212},"\"increase verbosity\"",[75,8085,2246],{"class":81},[75,8087,8088],{"class":77,"line":510},[75,8089,481],{"emptyLinePlaceholder":480},[75,8091,8092,8095,8097],{"class":77,"line":519},[75,8093,8094],{"class":81},"args ",[75,8096,2234],{"class":85},[75,8098,8099],{"class":81}," parser.parse_args()\n",[75,8101,8102],{"class":77,"line":524},[75,8103,481],{"emptyLinePlaceholder":480},[75,8105,8106,8108],{"class":77,"line":530},[75,8107,6891],{"class":224},[75,8109,8110],{"class":81},"(args.input)\n",[75,8112,8113,8115],{"class":77,"line":2376},[75,8114,6891],{"class":224},[75,8116,8117],{"class":81},"(args.output)\n",[75,8119,8120,8122],{"class":77,"line":3563},[75,8121,6891],{"class":224},[75,8123,8124],{"class":81},"(args.verbose)\n",[21,8126,8127],{},"We first create an ArgumentParser object and then add arguments using the add_argument() method. We can specify parameters like help text, default values, and more. The parse_args() method parses the command line and returns an object with the values.",[21,8129,8130],{},"This provides an easy way to access the argument values in a structured way.",[25,8132,8134],{"id":8133},"argument-groups","Argument Groups",[21,8136,8137],{},"For additional structure, we can now add argument groups:",[66,8139,8141],{"className":3384,"code":8140,"language":3386,"meta":71,"style":71},"parser = argparse.ArgumentParser()\n\ninput_group = parser.add_argument_group(\"Input Options\")\ninput_group.add_argument(\"input\", help=\"input file\")\n\noutput_group = parser.add_argument_group(\"Output Options\")  \noutput_group.add_argument(\"output\", help=\"output file\")\n\nother_group = parser.add_argument_group(\"Other Options\")\nother_group.add_argument(\"-v\", \"--verbose\", action=\"store_true\")\n",[33,8142,8143,8151,8155,8170,8187,8191,8206,8223,8227,8241],{"__ignoreMap":71},[75,8144,8145,8147,8149],{"class":77,"line":78},[75,8146,8005],{"class":81},[75,8148,2234],{"class":85},[75,8150,8010],{"class":81},[75,8152,8153],{"class":77,"line":98},[75,8154,481],{"emptyLinePlaceholder":480},[75,8156,8157,8160,8162,8165,8168],{"class":77,"line":119},[75,8158,8159],{"class":81},"input_group ",[75,8161,2234],{"class":85},[75,8163,8164],{"class":81}," parser.add_argument_group(",[75,8166,8167],{"class":212},"\"Input Options\"",[75,8169,2246],{"class":81},[75,8171,8172,8175,8177,8179,8181,8183,8185],{"class":77,"line":484},[75,8173,8174],{"class":81},"input_group.add_argument(",[75,8176,8022],{"class":212},[75,8178,555],{"class":81},[75,8180,8027],{"class":4011},[75,8182,2234],{"class":85},[75,8184,8032],{"class":212},[75,8186,2246],{"class":81},[75,8188,8189],{"class":77,"line":490},[75,8190,481],{"emptyLinePlaceholder":480},[75,8192,8193,8196,8198,8200,8203],{"class":77,"line":499},[75,8194,8195],{"class":81},"output_group ",[75,8197,2234],{"class":85},[75,8199,8164],{"class":81},[75,8201,8202],{"class":212},"\"Output Options\"",[75,8204,8205],{"class":81},")  \n",[75,8207,8208,8211,8213,8215,8217,8219,8221],{"class":77,"line":504},[75,8209,8210],{"class":81},"output_group.add_argument(",[75,8212,8041],{"class":212},[75,8214,555],{"class":81},[75,8216,8027],{"class":4011},[75,8218,2234],{"class":85},[75,8220,8050],{"class":212},[75,8222,2246],{"class":81},[75,8224,8225],{"class":77,"line":510},[75,8226,481],{"emptyLinePlaceholder":480},[75,8228,8229,8232,8234,8236,8239],{"class":77,"line":519},[75,8230,8231],{"class":81},"other_group ",[75,8233,2234],{"class":85},[75,8235,8164],{"class":81},[75,8237,8238],{"class":212},"\"Other Options\"",[75,8240,2246],{"class":81},[75,8242,8243,8246,8248,8250,8252,8254,8256,8258,8260],{"class":77,"line":524},[75,8244,8245],{"class":81},"other_group.add_argument(",[75,8247,8059],{"class":212},[75,8249,555],{"class":81},[75,8251,8064],{"class":212},[75,8253,555],{"class":81},[75,8255,8069],{"class":4011},[75,8257,2234],{"class":85},[75,8259,8074],{"class":212},[75,8261,2246],{"class":81},[21,8263,8264],{},"This organizes related arguments into named groups in the help text.",[25,8266,8268],{"id":8267},"subcommands","Subcommands",[21,8270,8271],{},"One major new feature is the ability to specify subcommands, each with their own arguments:",[66,8273,8275],{"className":3384,"code":8274,"language":3386,"meta":71,"style":71},"parser = argparse.ArgumentParser()\n\nsubparsers = parser.add_subparsers(help=\"commands\")\n\n# A command \nparser_a = subparsers.add_parser(\"a\", help=\"A help\") \nparser_a.add_argument(\"bar\", type=int)\n\n# B command\nparser_b = subparsers.add_parser(\"b\", help=\"B help\")\nparser_b.add_argument(\"--baz\", choices=[\"a\", \"b\", \"c\"])\n\nargs = parser.parse_args() \n\nif args.command == \"a\":\n   print(args.bar)\nelif args.command == \"b\":\n   print(args.baz)\n",[33,8276,8277,8285,8289,8308,8312,8317,8341,8361,8365,8370,8393,8424,8428,8437,8441,8455,8463,8477],{"__ignoreMap":71},[75,8278,8279,8281,8283],{"class":77,"line":78},[75,8280,8005],{"class":81},[75,8282,2234],{"class":85},[75,8284,8010],{"class":81},[75,8286,8287],{"class":77,"line":98},[75,8288,481],{"emptyLinePlaceholder":480},[75,8290,8291,8294,8296,8299,8301,8303,8306],{"class":77,"line":119},[75,8292,8293],{"class":81},"subparsers ",[75,8295,2234],{"class":85},[75,8297,8298],{"class":81}," parser.add_subparsers(",[75,8300,8027],{"class":4011},[75,8302,2234],{"class":85},[75,8304,8305],{"class":212},"\"commands\"",[75,8307,2246],{"class":81},[75,8309,8310],{"class":77,"line":484},[75,8311,481],{"emptyLinePlaceholder":480},[75,8313,8314],{"class":77,"line":490},[75,8315,8316],{"class":460},"# A command \n",[75,8318,8319,8322,8324,8327,8330,8332,8334,8336,8339],{"class":77,"line":499},[75,8320,8321],{"class":81},"parser_a ",[75,8323,2234],{"class":85},[75,8325,8326],{"class":81}," subparsers.add_parser(",[75,8328,8329],{"class":212},"\"a\"",[75,8331,555],{"class":81},[75,8333,8027],{"class":4011},[75,8335,2234],{"class":85},[75,8337,8338],{"class":212},"\"A help\"",[75,8340,4589],{"class":81},[75,8342,8343,8346,8349,8351,8354,8356,8359],{"class":77,"line":504},[75,8344,8345],{"class":81},"parser_a.add_argument(",[75,8347,8348],{"class":212},"\"bar\"",[75,8350,555],{"class":81},[75,8352,8353],{"class":4011},"type",[75,8355,2234],{"class":85},[75,8357,8358],{"class":224},"int",[75,8360,2246],{"class":81},[75,8362,8363],{"class":77,"line":510},[75,8364,481],{"emptyLinePlaceholder":480},[75,8366,8367],{"class":77,"line":519},[75,8368,8369],{"class":460},"# B command\n",[75,8371,8372,8375,8377,8379,8382,8384,8386,8388,8391],{"class":77,"line":524},[75,8373,8374],{"class":81},"parser_b ",[75,8376,2234],{"class":85},[75,8378,8326],{"class":81},[75,8380,8381],{"class":212},"\"b\"",[75,8383,555],{"class":81},[75,8385,8027],{"class":4011},[75,8387,2234],{"class":85},[75,8389,8390],{"class":212},"\"B help\"",[75,8392,2246],{"class":81},[75,8394,8395,8398,8401,8403,8406,8408,8411,8413,8415,8417,8419,8422],{"class":77,"line":530},[75,8396,8397],{"class":81},"parser_b.add_argument(",[75,8399,8400],{"class":212},"\"--baz\"",[75,8402,555],{"class":81},[75,8404,8405],{"class":4011},"choices",[75,8407,2234],{"class":85},[75,8409,8410],{"class":81},"[",[75,8412,8329],{"class":212},[75,8414,555],{"class":81},[75,8416,8381],{"class":212},[75,8418,555],{"class":81},[75,8420,8421],{"class":212},"\"c\"",[75,8423,3599],{"class":81},[75,8425,8426],{"class":77,"line":2376},[75,8427,481],{"emptyLinePlaceholder":480},[75,8429,8430,8432,8434],{"class":77,"line":3563},[75,8431,8094],{"class":81},[75,8433,2234],{"class":85},[75,8435,8436],{"class":81}," parser.parse_args() \n",[75,8438,8439],{"class":77,"line":3568},[75,8440,481],{"emptyLinePlaceholder":480},[75,8442,8443,8445,8448,8450,8453],{"class":77,"line":3583},[75,8444,2161],{"class":85},[75,8446,8447],{"class":81}," args.command ",[75,8449,4779],{"class":85},[75,8451,8452],{"class":212}," \"a\"",[75,8454,3479],{"class":81},[75,8456,8457,8460],{"class":77,"line":3602},[75,8458,8459],{"class":224},"   print",[75,8461,8462],{"class":81},"(args.bar)\n",[75,8464,8465,8468,8470,8472,8475],{"class":77,"line":5236},[75,8466,8467],{"class":85},"elif",[75,8469,8447],{"class":81},[75,8471,4779],{"class":85},[75,8473,8474],{"class":212}," \"b\"",[75,8476,3479],{"class":81},[75,8478,8479,8481],{"class":77,"line":5244},[75,8480,8459],{"class":224},[75,8482,8483],{"class":81},"(args.baz)\n",[21,8485,8486],{},"We define subcommands \"a\" and \"b\", each with their own set of arguments. The user specifies the subcommand and arguments, which we can access on args.",[21,8488,8489],{},"This is perfect for building CLIs with multiple commands.",[21,8491,8492],{},"Python's new argparse module provides a robust and user-friendly interface for parsing command line arguments. Some of the major features include argument groups, subcommands, flexible naming, type checking, and auto-generated help. This improves on the older argparse and makes it easy to build powerful command line tools.",[21,8494,8495],{},"The new parser takes care of all the low-level details, allowing developers to focus on their program's logic and more easily create complex CLIs in Python.",[561,8497,8498],{},"html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}html pre.shiki code .sjlk-, html code.shiki .sjlk-{--shiki-default:#E36209;--shiki-dark:#FFA657}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sPyeA, html code.shiki .sPyeA{--shiki-default:#6A737D;--shiki-dark:#8B949E}",{"title":71,"searchDepth":98,"depth":98,"links":8500},[8501,8502,8503,8504],{"id":7950,"depth":98,"text":7951},{"id":7980,"depth":98,"text":7981},{"id":8133,"depth":98,"text":8134},{"id":8267,"depth":98,"text":8268},"2022-10-01",{"type":579,"layout":580},"\u002Fblog\u002F2022-10-01-using-python-argparse",{"title":7941,"description":7947},"blog\u002F2022-10-01-using-python-argparse",[3386,2942,585],"iGvOeuJoUuhpk_KNlP99ljK71MN1JZzKzYVkYJ5Vgys",{"id":8513,"title":8514,"authors":8515,"body":8516,"date":8908,"description":8520,"extension":577,"meta":8909,"navigation":480,"path":8910,"seo":8911,"stem":8912,"tags":8913,"toc":480,"__hash__":8914},"blog\u002Fblog\u002F2022-09-14-getting-started-with-git.md","Getting started with Git",[15,16],{"type":18,"value":8517,"toc":8901},[8518,8521,8525,8528,8532,8535,8538,8550,8557,8564,8579,8582,8586,8589,8592,8604,8607,8630,8633,8647,8650,8666,8669,8673,8676,8679,8690,8693,8708,8715,8718,8732,8735,8744,8767,8773,8776,8779,8794,8797,8801,8804,8807,8825,8828,8842,8845,8860,8863,8879,8882,8898],[21,8519,8520],{},"Git is a distributed version control system that allows teams to collaborate on code and track changes efficiently. Whether you're a solo developer or working on a large team, learning Git is essential for managing your projects. This post will provide a beginner's guide to getting started with Git.",[25,8522,8524],{"id":8523},"introduction-to-git","Introduction to Git",[21,8526,8527],{},"Git is a free and open source version control system initially developed by Linus Torvalds in 2005. With Git, every developer has a local copy of the code repository on their machine. This allows for easy branching and merging, and enables developers to work offline and commit changes when it's convenient. Git tracks all changes made to the code, storing this information in 'commits'. This gives you a full history of the project and allows reverting back to previous versions easily. The distributed nature of Git makes collaboration seamless and keeps the project moving forward.",[25,8529,8531],{"id":8530},"creating-a-git-repository","Creating a Git Repository",[21,8533,8534],{},"There are two ways to start a new Git repo - initialise a new one in an existing directory on your machine, or clone an existing repository from a remote source like GitHub.",[21,8536,8537],{},"To initialise a new repo locally, navigate to your project directory and type:",[66,8539,8541],{"className":199,"code":8540,"language":201,"meta":71,"style":71},"git init\n",[33,8542,8543],{"__ignoreMap":71},[75,8544,8545,8547],{"class":77,"line":78},[75,8546,714],{"class":208},[75,8548,8549],{"class":212}," init\n",[21,8551,8552,8553,8556],{},"This will create a ",[33,8554,8555],{},".git"," subdirectory that contains all the configuration and metadata for the new repository.",[21,8558,8559,8560,8563],{},"To clone an existing remote repo, use the ",[33,8561,8562],{},"git clone"," command along with the repository URL:",[66,8565,8567],{"className":199,"code":8566,"language":201,"meta":71,"style":71},"git clone https:\u002F\u002Fgithub.com\u002Fuser\u002Frepo.git \n",[33,8568,8569],{"__ignoreMap":71},[75,8570,8571,8573,8576],{"class":77,"line":78},[75,8572,714],{"class":208},[75,8574,8575],{"class":212}," clone",[75,8577,8578],{"class":212}," https:\u002F\u002Fgithub.com\u002Fuser\u002Frepo.git\n",[21,8580,8581],{},"This will create a local copy of the remote repository for you to work on.",[25,8583,8585],{"id":8584},"basic-git-workflow","Basic Git Workflow",[21,8587,8588],{},"The basic Git workflow involves making changes to your local repository, staging files to be committed, and committing changes with a commit message.",[21,8590,8591],{},"First, make some changes to your files in the Git project. Then, to review changes made since the last commit, run:",[66,8593,8595],{"className":199,"code":8594,"language":201,"meta":71,"style":71},"git status\n",[33,8596,8597],{"__ignoreMap":71},[75,8598,8599,8601],{"class":77,"line":78},[75,8600,714],{"class":208},[75,8602,8603],{"class":212}," status\n",[21,8605,8606],{},"Next, add any new or changed files to the staging area to be included in the next commit:",[66,8608,8610],{"className":199,"code":8609,"language":201,"meta":71,"style":71},"git add \u003Cfilename> \n",[33,8611,8612],{"__ignoreMap":71},[75,8613,8614,8616,8618,8621,8624,8627],{"class":77,"line":78},[75,8615,714],{"class":208},[75,8617,822],{"class":212},[75,8619,8620],{"class":85}," \u003C",[75,8622,8623],{"class":212},"filenam",[75,8625,8626],{"class":81},"e",[75,8628,8629],{"class":85},">\n",[21,8631,8632],{},"Or to add all unstaged files:",[66,8634,8636],{"className":199,"code":8635,"language":201,"meta":71,"style":71},"git add .\n",[33,8637,8638],{"__ignoreMap":71},[75,8639,8640,8642,8644],{"class":77,"line":78},[75,8641,714],{"class":208},[75,8643,822],{"class":212},[75,8645,8646],{"class":212}," .\n",[21,8648,8649],{},"Once your desired changes are staged, commit them with a descriptive message:",[66,8651,8653],{"className":199,"code":8652,"language":201,"meta":71,"style":71},"git commit -m \"Your commit message\"\n",[33,8654,8655],{"__ignoreMap":71},[75,8656,8657,8659,8661,8663],{"class":77,"line":78},[75,8658,714],{"class":208},[75,8660,831],{"class":212},[75,8662,251],{"class":224},[75,8664,8665],{"class":212}," \"Your commit message\"\n",[21,8667,8668],{},"Writing clear commit messages is important for documenting the changes you've made.",[25,8670,8672],{"id":8671},"branching-and-merging","Branching and Merging",[21,8674,8675],{},"Branching allows you to diverge from the main development timeline and work on features or ideas in isolation. Branches act as a workspace for introducing and developing new code before merging it into the main codebase.",[21,8677,8678],{},"Some common use cases for branches:",[49,8680,8681,8684,8687],{},[52,8682,8683],{},"Developing a new feature",[52,8685,8686],{},"Fixing a bug",[52,8688,8689],{},"Experimenting with a major code change",[21,8691,8692],{},"To create a new branch:",[66,8694,8696],{"className":199,"code":8695,"language":201,"meta":71,"style":71},"git branch new_branch\n",[33,8697,8698],{"__ignoreMap":71},[75,8699,8700,8702,8705],{"class":77,"line":78},[75,8701,714],{"class":208},[75,8703,8704],{"class":212}," branch",[75,8706,8707],{"class":212}," new_branch\n",[21,8709,8710,8711,8714],{},"This creates a new branch called ",[33,8712,8713],{},"new_branch"," based on the current HEAD commit.",[21,8716,8717],{},"To switch to an existing branch:",[66,8719,8721],{"className":199,"code":8720,"language":201,"meta":71,"style":71},"git checkout existing_branch\n",[33,8722,8723],{"__ignoreMap":71},[75,8724,8725,8727,8729],{"class":77,"line":78},[75,8726,714],{"class":208},[75,8728,1402],{"class":212},[75,8730,8731],{"class":212}," existing_branch\n",[21,8733,8734],{},"This changes the files in your working directory to match the snapshot stored for that branch.",[21,8736,8737,8738,8740,8741,8743],{},"You can now make commits on the ",[33,8739,8713],{}," independently. When the feature is complete, merge the branch into the main ",[33,8742,7339],{}," branch:",[66,8745,8747],{"className":199,"code":8746,"language":201,"meta":71,"style":71},"git checkout main\ngit merge new_branch\n",[33,8748,8749,8758],{"__ignoreMap":71},[75,8750,8751,8753,8755],{"class":77,"line":78},[75,8752,714],{"class":208},[75,8754,1402],{"class":212},[75,8756,8757],{"class":212}," main\n",[75,8759,8760,8762,8765],{"class":77,"line":98},[75,8761,714],{"class":208},[75,8763,8764],{"class":212}," merge",[75,8766,8707],{"class":212},[21,8768,8769,8770,8772],{},"This combines the commit history from ",[33,8771,8713],{}," into the main project timeline.",[21,8774,8775],{},"If there are any conflicting changes between branches, Git will flag the files and halt the merge. You'll need to manually resolve these conflicts before completing the merge.",[21,8777,8778],{},"It's a good practice to delete local branches after merging to keep things clean:",[66,8780,8782],{"className":199,"code":8781,"language":201,"meta":71,"style":71},"git branch -d new_branch\n",[33,8783,8784],{"__ignoreMap":71},[75,8785,8786,8788,8790,8792],{"class":77,"line":78},[75,8787,714],{"class":208},[75,8789,8704],{"class":212},[75,8791,1288],{"class":224},[75,8793,8707],{"class":212},[21,8795,8796],{},"Branching enables isolated development and safe experimentation. By merging branches when work is complete, you can ensure your main branch remains stable and up-to-date.",[25,8798,8800],{"id":8799},"remotes-and-pushingpulling","Remotes and Pushing\u002FPulling",[21,8802,8803],{},"To share your local repository and collaborate with others, you'll need to push your commits to a remote repository like GitHub:",[21,8805,8806],{},"First, add the remote:",[66,8808,8810],{"className":199,"code":8809,"language":201,"meta":71,"style":71},"git remote add origin https:\u002F\u002Fgithub.com\u002Fuser\u002Frepo.git\n",[33,8811,8812],{"__ignoreMap":71},[75,8813,8814,8816,8819,8821,8823],{"class":77,"line":78},[75,8815,714],{"class":208},[75,8817,8818],{"class":212}," remote",[75,8820,822],{"class":212},[75,8822,1216],{"class":212},[75,8824,8578],{"class":212},[21,8826,8827],{},"Verify the remote was added:",[66,8829,8831],{"className":199,"code":8830,"language":201,"meta":71,"style":71},"git remote -v\n",[33,8832,8833],{"__ignoreMap":71},[75,8834,8835,8837,8839],{"class":77,"line":78},[75,8836,714],{"class":208},[75,8838,8818],{"class":212},[75,8840,8841],{"class":224}," -v\n",[21,8843,8844],{},"Then push commits to the remote repository:",[66,8846,8848],{"className":199,"code":8847,"language":201,"meta":71,"style":71},"git push origin main\n",[33,8849,8850],{"__ignoreMap":71},[75,8851,8852,8854,8856,8858],{"class":77,"line":78},[75,8853,714],{"class":208},[75,8855,1213],{"class":212},[75,8857,1216],{"class":212},[75,8859,8757],{"class":212},[21,8861,8862],{},"To pull any new commits others have pushed to the remote repository:",[66,8864,8866],{"className":199,"code":8865,"language":201,"meta":71,"style":71},"git pull origin main \n",[33,8867,8868],{"__ignoreMap":71},[75,8869,8870,8872,8875,8877],{"class":77,"line":78},[75,8871,714],{"class":208},[75,8873,8874],{"class":212}," pull",[75,8876,1216],{"class":212},[75,8878,8757],{"class":212},[21,8880,8881],{},"This will fetch and merge remote changes into your local repository.",[21,8883,8884,8885,555,8888,555,8891,555,8894,8897],{},"Git is a versatile tool that enables efficient collaboration and version control. Following the main workflow of ",[33,8886,8887],{},"add",[33,8889,8890],{},"commit",[33,8892,8893],{},"push",[33,8895,8896],{},"pull"," allows you to share code changes with your team and contribute to projects seamlessly. There are many more advanced Git features to explore, but understanding the basics lets you reap the benefits of version control and streamline your development process.",[561,8899,8900],{},"html pre.shiki code .sYRkA, html code.shiki .sYRkA{--shiki-default:#6F42C1;--shiki-dark:#FFA657}html pre.shiki code .sNbzA, html code.shiki .sNbzA{--shiki-default:#032F62;--shiki-dark:#A5D6FF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sDgYj, html code.shiki .sDgYj{--shiki-default:#D73A49;--shiki-dark:#FF7B72}html pre.shiki code .szmH1, html code.shiki .szmH1{--shiki-default:#24292E;--shiki-dark:#E6EDF3}html pre.shiki code .sPgVT, html code.shiki .sPgVT{--shiki-default:#005CC5;--shiki-dark:#79C0FF}",{"title":71,"searchDepth":98,"depth":98,"links":8902},[8903,8904,8905,8906,8907],{"id":8523,"depth":98,"text":8524},{"id":8530,"depth":98,"text":8531},{"id":8584,"depth":98,"text":8585},{"id":8671,"depth":98,"text":8672},{"id":8799,"depth":98,"text":8800},"2022-09-14",{"type":579,"layout":580},"\u002Fblog\u002F2022-09-14-getting-started-with-git",{"title":8514,"description":8520},"blog\u002F2022-09-14-getting-started-with-git",[587,585,586,2942,714],"N0MkiFZ4TPew6mEGbJHV8Bsps-YL3IwGVHKRPiKLZWs",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":8916},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m12 19l-7-7l7-7m7 7H5\"\u002F>",1783616162275]