{"componentChunkName":"component---src-templates-post-js","path":"/posts/20260428-i-dont-know-the-invisible-crisis-3383/","result":{"pageContext":{"url":"/posts/20260428-i-dont-know-the-invisible-crisis-3383/","relativePath":"posts/20260428-i-dont-know-the-invisible-crisis-3383.md","relativeDir":"posts","base":"20260428-i-dont-know-the-invisible-crisis-3383.md","name":"20260428-i-dont-know-the-invisible-crisis-3383","frontmatter":{"title":"'I don't know' — The invisible crisis","template":"post","date":"2026-04-28T09:25:00Z","excerpt":"When 'I don't know' means the ground you're being told to step on doesn't exist","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23he0dl88a6t5s1x5ds5.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23he0dl88a6t5s1x5ds5.png","canonical_url":"https://dev.to/raphink/i-dont-know-the-invisible-crisis-3383","devto_url":"https://dev.to/raphink/i-dont-know-the-invisible-crisis-3383"},"html":"<p>This is the thirteenth post in my autism awareness month series.</p>\n<p>When someone asks me what I want to eat, or which color I prefer, or what I should work on next, something happens at times that's hard to describe to people who don't experience it.</p>\n<p>From the outside, my response might look like indifference. I might say \"I don't know\" and smile quietly. To the person asking, this can read as not caring, not being engaged, not valuing their question or the interaction.</p>\n<p>But inside, something very different is happening.</p>\n<p>Most people think \"I don't know\" just needs an answer, any answer. But to the autistic brain, the question creates a demand for the right answer, and there might not be enough data to determine what that is.</p>\n<p>It's not psychological anxiety. I'm not worried about choosing wrong or disappointing someone. It's more fundamental than that. The question creates a void, an abyss with no foothold. Imagine someone climbing a wall, and another person telling them to put their foot in a hole that should be right there — but the climber cannot see or feel that hole anywhere. It's not that they're afraid to step. The ground they're being told to step on simply doesn't exist for them.</p>\n<p>\"What do you want to eat?\" sounds like a simple preference question. But my processing system receives it as: given all parameters — nutritional needs, energy levels, what I last ate, what's available, time constraints, current sensory state — what is the optimal answer? If those parameters aren't fully specified, or if I can't access my internal state clearly enough to know what my body needs, the equation can't be solved. There's no answer to give because the question, as received, is computationally incomplete.</p>\n<p>And here's where the social trap closes: while experiencing this groundlessness, this inability to locate the answer, I also don't know how to adapt my social behavior to signal what's happening. I might smile (a learned safe default) or keep a blank face (because I have no spare processing capacity for managing expressions while searching for ground that isn't there).</p>\n<p>This gets interpreted as exactly the opposite of what's happening. The person asking sees indifference. But the \"I don't know\" isn't about not caring. It might signal the opposite — needing the right answer rather than just any answer, needing sufficient information rather than being able to approximate casually.</p>\n<p>The person asking has no idea their simple question created this effect. They think they asked for a preference and got apathy in return.</p>\n<p>This is part of why the \"you can't know what you don't know\" principle matters so much. Until I understood that for neurotypical people, \"I don't know what I want to eat\" often just means \"I have no strong preference,\" I didn't realize my processing was different. I thought everyone experienced that demand for the right answer. This will be the topic of the last post tomorrow.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7454821938780123136-ukMM/\">on LinkedIn on 2026-04-28</a>.</em></p>","pages":[{"url":"/","relativePath":"index.md","relativeDir":"","base":"index.md","name":"index","frontmatter":{"title":"Home","template":"home"},"html":""},{"url":"/contact/","relativePath":"contact.md","relativeDir":"","base":"contact.md","name":"contact","frontmatter":{"title":"Get in Touch","img_path":"images/contact.jpg","form_id":"contactForm","form_fields":[{"type":"text","name":"name","label":"Name","default_value":"Your name","is_required":true},{"type":"email","name":"email","label":"Email","default_value":"Your email address","is_required":true},{"type":"select","name":"subject","label":"Subject","default_value":"Please select","options":["Error on the site","Sponsorship","Other"]},{"type":"textarea","name":"message","label":"Message","default_value":"Your message"},{"type":"checkbox","name":"consent","label":"I understand that this form is storing my submitted information so I can be contacted."}],"submit_label":"Send Message","template":"contact"},"html":"<p>To get in touch fill the form below.</p>"},{"url":"/posts/20041122-blogspot-bookcrossing/","relativePath":"posts/20041122-blogspot-bookcrossing.md","relativeDir":"posts","base":"20041122-blogspot-bookcrossing.md","name":"20041122-blogspot-bookcrossing","frontmatter":{"title":"BookCrossing","template":"post","date":"2004-11-22T15:46:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/bookcrossing.html","blogspot_url":"https://raphink.blogspot.com/2004/11/bookcrossing.html","tags":["english","multiply","restored","review","website"]},"html":"<p>Category:</p>\n<p>Other</p>\n<p><a href=\"http://web.archive.org/web/20041207171041/http://www.bookcrossing.com/\" title=\"Hey don&#x27;t do this too much, it tickles!\"><img src=\"http://web.archive.org/web/20041207171041/http://bookcrossingfrance.apinc.org/images/RunningBook33.gif\"></a> <a href=\"http://web.archive.org/web/20041207171041/http://www.bookcrossing.com/\"><strong>B</strong>ookCrossing</a> is another great site among so many... The idea is to turn the world into a giant library, by releasing books in the wild. There are online forums aswell, and users are used to meeting each other from time to time and share books they liked. Here is <a href=\"http://web.archive.org/web/20041207171041/http://raphink.bookcrossing.com/\">my BookCrossing profile</a>.</p>"},{"url":"/posts/20041122-blogspot-my-first-day-on-multiply/","relativePath":"posts/20041122-blogspot-my-first-day-on-multiply.md","relativeDir":"posts","base":"20041122-blogspot-my-first-day-on-multiply.md","name":"20041122-blogspot-my-first-day-on-multiply","frontmatter":{"title":"My first day on multiply","template":"post","date":"2004-11-22T15:52:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/my-first-day-on-multiply.html","blogspot_url":"https://raphink.blogspot.com/2004/11/my-first-day-on-multiply.html","tags":["english","journal","multiply","restored"]},"html":"<p><strong>H</strong>ey guys!  </p>\n<p>Might not be soooo exciting, but it's my first day on multiply, and so far this is great! Somehow a bit more powerful than orkut (which is much better that friendster imho), a bit less crowded, but without the constant server failures. Thanks to Meg for her link on her orkut profile that led me here :)</p>"},{"url":"/posts/20041122-blogspot-mylanguageexchange/","relativePath":"posts/20041122-blogspot-mylanguageexchange.md","relativeDir":"posts","base":"20041122-blogspot-mylanguageexchange.md","name":"20041122-blogspot-mylanguageexchange","frontmatter":{"title":"MyLanguageExchange","template":"post","date":"2004-11-22T15:46:00.001+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/mylanguageexchange.html","blogspot_url":"https://raphink.blogspot.com/2004/11/mylanguageexchange.html","tags":["book","english","multiply","restored","review","website"]},"html":"<p>Category:</p>\n<p>Other</p>\n<p><a href=\"http://web.archive.org/web/20041207171041/http://www.mylanguageexchange.com/\"><strong>M</strong>yLanguageExchange</a> is a great web site for anyone wanting to learn languages on the net. The idea is to get people teach their own language to each other, using the internet. You can either send a private message to users, or use the <a href=\"http://web.archive.org/web/20041207171041/http://www.mylanguageexchange.com/textchat.asp\">text chat</a> system of the site. I've been using it a lot and I really advise it to any people who desire to learn a language.</p>"},{"url":"/posts/20041122-blogspot-shakespeare-and-co/","relativePath":"posts/20041122-blogspot-shakespeare-and-co.md","relativeDir":"posts","base":"20041122-blogspot-shakespeare-and-co.md","name":"20041122-blogspot-shakespeare-and-co","frontmatter":{"title":"Shakespeare and Co","template":"post","date":"2004-11-22T15:45:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/shakespeare-and-co.html","blogspot_url":"https://raphink.blogspot.com/2004/11/shakespeare-and-co.html","tags":["english","multiply","place","restored","review"]},"html":"<p>Category:</p>\n<p>Other</p>\n<p><a href=\"http://web.archive.org/web/20041207171041/http://www.shakespeareco.org/\"><img src=\"http://web.archive.org/web/20041207171041/http://www.shakespeareco.org/logo_stamp.gif\"></a></p>\n<p><a href=\"http://web.archive.org/web/20041207171041/http://www.shakespeareco.org/\"><strong>S</strong>hakespeare and Co</a> is a wonderful and extraordinary place in Paris!<br>\nIf you love books and think of going to Paris, this the place you have to go for sure, rather than to the Eiffel Tower or Notre Dame (which is very close anyway).<br>\nYou can get a virtual visit of this magical place by clicking on the \"welcome\" link on <a href=\"http://web.archive.org/web/20041207171041/http://thinkparis.com/guides/shakespeare.cfm\">this page</a>.</p>"},{"url":"/posts/20041124-blogspot-special-day-today/","relativePath":"posts/20041124-blogspot-special-day-today.md","relativeDir":"posts","base":"20041124-blogspot-special-day-today.md","name":"20041124-blogspot-special-day-today","frontmatter":{"title":"A special day today","template":"post","date":"2004-11-24T02:59:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/special-day-today.html","blogspot_url":"https://raphink.blogspot.com/2004/11/special-day-today.html","tags":["bible","english","god","journal","multiply","restored"]},"html":"<p><strong>T</strong>ruly a special day!<br>\nWell, everyday is a special day when it is lived in communion with God. But this one meant a lot to me. As this morning I couldn't get up (errr yeah that's pretty bad I know) and I went to the uni late :s While leaving, I put on this mp3 CD of bible teachings from J. Courson that is very nice, and I knew that I couldn't listen to it all in the short lap of time I had between my house and the uni. But ... that was without trusting that the Lord wanted me to listen to it entirely lol. Yeah... there was the police everywhere on the way, and after spending 20 minutes in the jam, I decided to take another way, and then, following a sloooooooooooow car and then a slooooooooooower truck, I got to uni after 50 minutes, just the time needed to listen almost entirely to the study. Great job, Lord, great timing!<br>\nWell very tiring day too... and a lot of work to do... but finally I could afford to go to the reunion at the temple that I wanted to go to, about liturgy, and to talk with the pastor, who is really a great woman, about my thoughts of being a pastor myself, and that was great!<br>\nSo yes, that was a special and great day :) Hope there can more days like this :D</p>"},{"url":"/posts/20041124-blogspot-alabaster-box/","relativePath":"posts/20041124-blogspot-alabaster-box.md","relativeDir":"posts","base":"20041124-blogspot-alabaster-box.md","name":"20041124-blogspot-alabaster-box","frontmatter":{"title":"Alabaster Box","template":"post","date":"2004-11-24T15:43:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/alabaster-box.html","blogspot_url":"https://raphink.blogspot.com/2004/11/alabaster-box.html","tags":["christian","english","multiply","music","restored","review"]},"html":"<p>Category:</p>\n<p>Music</p>\n<p>Genre:</p>\n<p>Christian &#x26; Gospel</p>\n<p>Artist:</p>\n<p>CeCe Winans</p>\n<p><a href=\"http://web.archive.org/web/20041207171041/http://www.samma.nl/shop/cd/8517112.html\"><img src=\"http://web.archive.org/web/20041207171041/http://www.samma.nl/shop/cd/image/8517112.jpg\"></a></p>\n<p><strong>I</strong> have been listening to this song again and again this last week, and I really love it. This is so beautiful and I just love the <a href=\"http://web.archive.org/web/20041207171041/http://www.clarion-call.org/yeshua/alabastr/song.htm\">lyrics</a> too!</p>"},{"url":"/posts/20041125-blogspot-my-profile-has-new-look-and-new/","relativePath":"posts/20041125-blogspot-my-profile-has-new-look-and-new.md","relativeDir":"posts","base":"20041125-blogspot-my-profile-has-new-look-and-new.md","name":"20041125-blogspot-my-profile-has-new-look-and-new","frontmatter":{"title":"My profile has a new look and new features!","template":"post","date":"2004-11-25T22:24:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/my-profile-has-new-look-and-new.html","blogspot_url":"https://raphink.blogspot.com/2004/11/my-profile-has-new-look-and-new.html","tags":["english","journal","multiply","restored","style"]},"html":"<p>[<strong>H</strong>i guys!  </p>\n<p>Some new colours and features on my homepage it seems ;)<br>\nWell as I discovered the](<a href=\"http://web.archive.org/web/20041207084152/http://raphink.multiply.com/journal/item/3\">http://web.archive.org/web/20041207084152/http://raphink.multiply.com/journal/item/3</a>) <a href=\"http://web.archive.org/web/20041207084152/http://modify.multiply.com/\">\"Modify your Multiple profile\" group</a> yesterday, I decided to go back to my programmation skills and try to use their advice. So I spent some hours working hard on my profile, and here is the result.  </p>\n<p>First of all, I guess you noticed that you are called by your name when you come to my page now, and that a photo of you is shown with the welcome message, thanks to mango's advice. I just had to work on an improvement of this system, so that people who are not registered on the site are not welcomed with something like : \"Welcome to my homepage, QaYZVgoKCjoAAGVMWEU!\", which is what happens on other pages built with this code. Now they are welcome as guests.  </p>\n<p>The second thing concerns the color scheme of the page. Thanks to mango again, because I didn't only used his advice, but also the entire css he built for his very nice blue scheme, though I have begun adding some personal stuff in it. But the new thing (or at least I have seen it nowhere) is this select box you get on the upper right corner of the screen when you visit my homepage, and that allows you to change the scheme you prefer to see my page. More schemes to come, guys!  </p>\n<p>Note : the color scheme stuff seems to not work under Internet Explorer. I have spent several hours trying to understand this, changing the code and so on, but it definitely tells me there is something wrong in my Javascript programming, though the Javascript Console of Mozilla tells me nothing about it. So well... if you want to see it work, and get the best browser at the same time, whatever your OS be (Windows, Mac OS X or Linux), I just have one thing to tell you => <a href=\"http://web.archive.org/web/20041207084152/http://www.mozilla.org/products/firefox/\"><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/getfirefox.gif\"></a> !!!!</p>"},{"url":"/posts/20041213-blogspot-more-about-smilies/","relativePath":"posts/20041213-blogspot-more-about-smilies.md","relativeDir":"posts","base":"20041213-blogspot-more-about-smilies.md","name":"20041213-blogspot-more-about-smilies","frontmatter":{"title":"More about smilies","template":"post","date":"2004-12-13T22:59:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/12/more-about-smilies.html","blogspot_url":"https://raphink.blogspot.com/2004/12/more-about-smilies.html","tags":["blog","english","javascript","multiply","open-source","restored"]},"html":"<p>I posted a reply to my old post about new features on my profile a bit earlier, but I would like to be more precise about smilies and their use here. I made it very easy to use smilies on my home page now.  </p>\n<p>Indeed, all you have to do to insert a smiley in a text is to put this code : smiley('SmileyName'); at the place you want to put the smiley, and replacing SmileyName with the name of the smiley, from the list below (note: this will only work on the pages of my home site, not on every multiply pages).<br>\n[2thumbsup]<br>\n2thumbsup [Mr-T]<br>\nMr-T [_blank]<br>\n_blank [afro]<br>\nafro [alien]<br>\nalien [angel]<br>\nangel<br>\n[angry]<br>\nangry [annoyed]<br>\nannoyed [antlers]<br>\nantlers [anxious]<br>\nanxious [argue]<br>\nargue [army]<br>\narmy<br>\n[artist]<br>\nartist [baby]<br>\nbaby [balloon]<br>\nballoon [balloon2]<br>\nballoon2 [balloon3]<br>\nballoon3 [bandana]<br>\nbandana<br>\n[batman]<br>\nbatman [beadyeyes]<br>\nbeadyeyes [beadyeyes2]<br>\nbeadyeyes2 [beam]<br>\nbeam [beatnik]<br>\nbeatnik [beatnik2]<br>\nbeatnik2<br>\n[behead]<br>\nbehead [behead2]<br>\nbehead2 [bigcry]<br>\nbigcry [biker]<br>\nbiker [blank]<br>\nblank [blush]<br>\nblush<br>\n[bobby]<br>\nbobby [bobby2]<br>\nbobby2 [bomb]<br>\nbomb [bomb2]<br>\nbomb2 [book]<br>\nbook [book2]<br>\nbook2<br>\n[bow]<br>\nbow [brood]<br>\nbrood [bucktooth]<br>\nbucktooth [builder]<br>\nbuilder [builder2]<br>\nbuilder2 [bulb]<br>\nbulb<br>\n[bulb2]<br>\nbulb2 [charming]<br>\ncharming [cheesy]<br>\ncheesy [chef]<br>\nchef [chinese]<br>\nchinese [clown]<br>\nclown<br>\n[computer]<br>\ncomputer [confused]<br>\nconfused [cool]<br>\ncool [cool2]<br>\ncool2 [cool3]<br>\ncool3 [cool4]<br>\ncool4<br>\n[cowboy]<br>\ncowboy [crown]<br>\ncrown [crowngrin]<br>\ncrowngrin [cry]<br>\ncry [cry2]<br>\ncry2 [curtain]<br>\ncurtain<br>\n[cyclist]<br>\ncyclist [daisy]<br>\ndaisy [dead]<br>\ndead [deal]<br>\ndeal [deal2]<br>\ndeal2 [devil]<br>\ndevil<br>\n[devilish]<br>\ndevilish [disappointed]<br>\ndisappointed [disguise]<br>\ndisguise [dizzy]<br>\ndizzy [dizzy2]<br>\ndizzy2 [dozey]<br>\ndozey<br>\n[drummer]<br>\ndrummer [drunk]<br>\ndrunk [dunce]<br>\ndunce [dunce2]<br>\ndunce2 [earmuffs]<br>\nearmuffs [ears]<br>\nears<br>\n[egypt]<br>\negypt [elf]<br>\nelf [elvis]<br>\nelvis [embarassed]<br>\nembarassed [end]<br>\nend [evil]<br>\nevil<br>\n[evil2]<br>\nevil2 [evil3]<br>\nevil3 [evilgrin]<br>\nevilgrin [fireman]<br>\nfireman [freak]<br>\nfreak [furious]<br>\nfurious<br>\n[furious2]<br>\nfurious2 [furious3]<br>\nfurious3 [glasses]<br>\nglasses [glasses2]<br>\nglasses2 [goofy]<br>\ngoofy [gorgeous]<br>\ngorgeous<br>\n[gossip]<br>\ngossip [greedy]<br>\ngreedy [grin]<br>\ngrin [grin2]<br>\ngrin2 [grin3]<br>\ngrin3 [guitarist]<br>\nguitarist<br>\n[hair]<br>\nhair [hair2]<br>\nhair2 [hanged]<br>\nhanged [happy]<br>\nhappy [happy2]<br>\nhappy2 [hat]<br>\nhat<br>\n[hat2]<br>\nhat2 [heart]<br>\nheart [helmet]<br>\nhelmet [help]<br>\nhelp [hippy]<br>\nhippy [huh]<br>\nhuh<br>\n[huh2]<br>\nhuh2 [idea]<br>\nidea [idea2]<br>\nidea2 [idea3]<br>\nidea3 [iloveyou]<br>\niloveyou [indian_brave]<br>\nindian_brave<br>\n[indian_chief]<br>\nindian_chief [inquisitive]<br>\ninquisitive [jester]<br>\njester [joker]<br>\njoker [juggle]<br>\njuggle [juggle2]<br>\njuggle2<br>\n[karate]<br>\nkarate [kid]<br>\nkid [kiss]<br>\nkiss [kiss2]<br>\nkiss2 [klingon]<br>\nklingon [knife]<br>\nknife<br>\n[laugh]<br>\nlaugh [laugh2]<br>\nlaugh2 [laugh3]<br>\nlaugh3 [laugh4]<br>\nlaugh4 [leer]<br>\nleer [lips]<br>\nlips<br>\n[lips2]<br>\nlips2 [lipsrsealed]<br>\nlipsrsealed [lipsrsealed2]<br>\nlipsrsealed2 [lost]<br>\nlost [love]<br>\nlove [mad]<br>\nmad<br>\n[mask]<br>\nmask [mean]<br>\nmean [mellow]<br>\nmellow [mickey]<br>\nmickey [moustache]<br>\nmoustache [nice]<br>\nnice<br>\n[no]<br>\nno [oops]<br>\noops [operator]<br>\noperator [party]<br>\nparty [party2]<br>\nparty2 [party3]<br>\nparty3<br>\n[pimp]<br>\npimp [pimp2]<br>\npimp2 [pirate]<br>\npirate [pleased]<br>\npleased [policeman]<br>\npoliceman [pumpkin]<br>\npumpkin<br>\n[punk]<br>\npunk [rifle]<br>\nrifle [rockstar]<br>\nrockstar [rolleyes]<br>\nrolleyes [rolleyes2]<br>\nrolleyes2 [rolleyes3]<br>\nrolleyes3<br>\n[rolleyes4]<br>\nrolleyes4 [rolleyes5]<br>\nrolleyes5 [sad]<br>\nsad [sad2]<br>\nsad2 [sad3]<br>\nsad3 [santa]<br>\nsanta<br>\n[santa2]<br>\nsanta2 [santa3]<br>\nsanta3 [scholar]<br>\nscholar [shame]<br>\nshame [shifty]<br>\nshifty [shocked]<br>\nshocked<br>\n[shocked2]<br>\nshocked2 [shocked3]<br>\nshocked3 [shout]<br>\nshout [shy]<br>\nshy [sick]<br>\nsick [sick2]<br>\nsick2<br>\n[singer]<br>\nsinger [skull]<br>\nskull [sleep]<br>\nsleep [sleeping]<br>\nsleeping [sleepy]<br>\nsleepy [smart]<br>\nsmart<br>\n[smartass]<br>\nsmartass [smartass2]<br>\nsmartass2 [smash]<br>\nsmash [smile]<br>\nsmile [smiley]<br>\nsmiley [smiley2]<br>\nsmiley2<br>\n[smitten]<br>\nsmitten [smoking]<br>\nsmoking [smug]<br>\nsmug [smug2]<br>\nsmug2 [sneaky]<br>\nsneaky [snobby]<br>\nsnobby<br>\n[snore]<br>\nsnore [sombrero]<br>\nsombrero [speechless]<br>\nspeechless [square]<br>\nsquare [stare]<br>\nstare [stars]<br>\nstars<br>\n[stooge_curly]<br>\nstooge_curly [stooge_larry]<br>\nstooge_larry [stooge_moe]<br>\nstooge_moe [stop]<br>\nstop [stunned]<br>\nstunned [stupid]<br>\nstupid<br>\n[sultan]<br>\nsultan [sunny]<br>\nsunny [surprised]<br>\nsurprised [sweatdrop]<br>\nsweatdrop [sweetheart]<br>\nsweetheart [thinking2]<br>\nthinking2  </p>\n<p>Remarks (for advances JavaScript users):  </p>\n<p>* The smiley() function will do a document.write() of the image url. If you wish to include the url into a JavaScript string, use the second argument 'get', as in smiley('SmileyName','get'). The function will then return the url as a string.<br>\n* I added a third option (which requires the second value to be set, either to 'write' or to 'get') that allows to choose the style of the smiley among some (default being 'standard'), that are listed here:<br>\n[_blank]<br>\nstandard [_blank]<br>\nsquare [_blank]<br>\nboardmod<br>\nThe calling to the function is then something like smiley('egypt','write','square') which will be [egypt] .  </p>\n<p>ENJOY!!!<br>\nThanks to Jason's smilies great site of smilies collections.</p>"},{"url":"/posts/20041213-blogspot-it-takes-17-years-to-dog-to-recogine/","relativePath":"posts/20041213-blogspot-it-takes-17-years-to-dog-to-recogine.md","relativeDir":"posts","base":"20041213-blogspot-it-takes-17-years-to-dog-to-recogine.md","name":"20041213-blogspot-it-takes-17-years-to-dog-to-recogine","frontmatter":{"title":"It takes 17 years to a dog to recogine himself in a mirror","template":"post","date":"2004-12-13T03:29:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/12/it-takes-17-years-to-dog-to-recogine.html","blogspot_url":"https://raphink.blogspot.com/2004/12/it-takes-17-years-to-dog-to-recogine.html","tags":["blog","english","fun","multiply","restored"]},"html":"<p>This my conclusion of having seen my 17-yo dog looking strangely at himself in a mirror today, and then attacking his reflect! I had to come to the conclusion that he has arrived to a wise enough state of mind to be aware of his reflect in the mirror... this is the only explanation to me...</p>"},{"url":"/posts/20041130-blogspot-languages/","relativePath":"posts/20041130-blogspot-languages.md","relativeDir":"posts","base":"20041130-blogspot-languages.md","name":"20041130-blogspot-languages","frontmatter":{"title":"Languages","template":"post","date":"2004-11-30T10:49:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/languages.html","blogspot_url":"https://raphink.blogspot.com/2004/11/languages.html","tags":["english","journal","language","multiply","restored"]},"html":"<p><strong>I</strong> posted some time ago a review about the <a href=\"http://web.archive.org/web/20041207084152/http://raphink.multiply.com/reviews/item/1\">MyLanguageExchange</a> website, and I would like to write a few lines about languages here, mostly because people from MLE are redirected here, and it could be nice that they know what languages I know and want to study... So here it is...  </p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/fr.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/be.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ch.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ca.gif\"> <strong>French : my mother tongue</strong><br>\n<strong>I</strong>'m happy to say this one is not much of a problem to me, so feel free to ask for help with it!</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/de.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/at.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ch.gif\"> <strong>German : a waste of study time</strong><br>\n<strong>I</strong> began studying German in school when I was 11, and studied it for 7 years... Wow, 7 years, for such a horrible level, that's a lot... yeah... ich kann nicht Deutsch gut :( aber ich möchte lernen :)</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/sa.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/af.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/bh.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/jo.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/kw.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/qa.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ae.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/eg.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/lb.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ly.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ma.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/sy.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/tn.gif\"> <strong>Arabic : a calligraphy confusion</strong><br>\n<strong>A</strong>t this time of my youth, I was very interested in calligraphy (French one, indeed), and a teacher from my school proposed to teach arabic calligraphy. I thought it would be very nice, but had forgetten that to learn arabic calligraphy, you need to learn arabic first lol So I began learning, at least the alphabet... I stopped after a few time, as I didn't like the way it was taught : the teacher would have us recognize the letters in the Kuran, when I think there are so many texts written in Arabic... (Now try to to understand why Arabs are considered a non-developped nation when they used to be the most advanced scientist one once :S).</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/va.gif\"> <strong>Latin : a definitely dead language to me</strong><br>\n<strong>T</strong>hen I went on with Latin, 2 years later. I stopped Latin after two years, cause my grades in it were going lower and lower, and it was the critical point where I had to stop before it began to be too bad lol Still, it's a very useful basis to learn other languages, esp. romance languages, and to understand ethymology better.</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/gb.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/us.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ca.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/au.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ie.gif\"> <strong>English : hey that's better</strong><br>\n<strong>I</strong> started learning English at the same time than Latin. I wouldn't say it is taught any better than German or Latin indeed, but I was lucky enough to get some help with it lately. I spent 6 years learning nothing in school about it (just what everybody does), till the day I got to chat on the internet and really improved it. Now some people tell me I'm fluent, though I know I miss a lot of vocabulary and slang. I would be glad to keep improving!</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ru.gif\"> <strong>Russian : trip to Moskva</strong><br>\n<strong>A</strong>fter my fail with Latin, I still wanted to study a third language in High School, and I wanted to go to a specific High School, so choosing Russian as a third living language was a way to get there, and at the same time I was interested in this language, too. I learned it for 3 years, and had the opportunity to go to Moscow on an exchange trip. That was great :) Now I really need to practice again, though it's not very easy on the internet, because of the typing stuff :(...</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/pl.gif\"> <strong>Polish : who needs a reason to learn?</strong><br>\n<strong>W</strong>ell, back in time a bit... Just before learning Russian, I got to meet some girl I liked and that had the only fault of speaking Polish only... that might have helped with my choice of learning Russian, as I couldn't choose Polish lol Anyway, my Russian teacher also loved Polish and helped me learning it for a while... I have the basis, but I can't really put 5 words together to get a correct sentence. Still, I like this language very much :)</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/se.gif\"> <strong>Swedish</strong><br>\n<strong>W</strong>ell the year after I began, I got a reason to study Swedish... You always find reasons to study languages with the people you meet and want to communicate with, even though they do speak English very well :p So I began to study it alone, as I had no teacher for it. I might say that I was able to say a few things and understand basic texts quite well at a certain point. It might not be the case anymore, but I can still understand written Swedish pretty well.</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/il.gif\"> <strong>Hebrew : family and religion</strong><br>\n<strong>T</strong>his might begin with the fact that I have Jewish roots, and got to search for <a href=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/genealogie.html\">my genealogy</a>, and it has been useful to read signatures in hebraic alphabet and some yiddish stuff. I can't remember how I got to begin though... Anyway, this has also to do with the fact that I am a Christian and began to study the Bible more at this time, and thought that it would be very useful to be able to go back to the original text... That was without considering that Hebrew is not as easy as Swedish to me, and learning it alone with a book is quite difficult. I gave up, for now... but I really wish to go back to it as soon as possible, and to begin ancient Greek too.</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/eo.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/qy-io.gif\"> <strong>Esperanto and Ido : how about Utopia?</strong><br>\n<strong>A</strong>t a certain point, I thought that it was nice to study a lot of languages, but that many people couldn't do it, and that having a common language to communicate would be great, and that would have to be different from English for some reason. I learned the basis of Esperanto in a few days, and then discovered Ido, and learned the basis in a few hours (that is, I was able to write a whole message to introduce me after 2 days). I programmed personal sites about Ido, such as <a href=\"http://web.archive.org/web/20041207084152/http://idolinguo.free.fr/\">my idolinguo page</a> (in French), <a href=\"http://web.archive.org/web/20041207084152/http://idofrancais.free.fr/\">The international French-speaking Ido community site</a> (in French) or <a href=\"http://web.archive.org/web/20041207084152/http://dicionarii.free.fr/\">a page of online translation dictionaries</a> between Ido and a few other languages... That was really a passion at a time lol. I used to be a bit active in some mailing lists about choosing a common language (but not a unique one) for Europe, but that was some years ago...</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/it.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ch.gif\"> <strong>Italian : journey to Venice</strong><br>\n<strong>W</strong>e got once to form the project to go to Venice together with some friends of mine. I thought it was nice, but one of my principles about travelling had become to learn the language of the country first... So I studied Italian a bit, just enough to be able to talk and understand. I might say it was pretty useful when we were lost in the country sometimes. I didn't study much, and I might improve it later on, though it's not one of my priorities.</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/an.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/aw.gif\"> <strong>Papiamento : wazza?</strong><br>\n<strong>P</strong>apiamento is the creole language spoken in the Netherland Antilles, close to Venezuela, that is to say in Aruba, Curaçao, Bonaire and St. Martin. It has around 200.000 speakers. I had the opportunity to begin studying, for personal reasons, though finding speakers on the internet was a bit difficult I might confess. Papiamento is a very interesting language, based on Spanish, French, Dutch, English, african languages and Arawak Indian. It has thus a very simple grammar, and very \"international\" (in the European way) vocabulary. At a point, I found it was somehow very close to Ido and Esperanto... and came to think that it would definitely be a good common language for the European Union, for a lot of reasons...</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/es.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ar.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/bo.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/cl.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/co.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/mx.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/pe.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ve.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/bz.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/cr.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ec.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/sv.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/gt.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/hn.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/ni.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/pa.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/py.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/uy.gif\"> <strong>Spanish : that had to be...</strong><br>\n<strong>I</strong> have always thought that Spanish was easy enough when you know French, and that I would learn it when I would need it. So as lately I had this need, I just began learning it 2 years ago, and now take courses of it in uni. This is very useful, when you consider the huge number of speakers in the world.</p>\n<p><img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/nl.gif\"> <img src=\"http://web.archive.org/web/20041207084152/http://r.pinson.free.fr/multiply/flags/be.gif\"> <strong>Dutch : a plan of studies</strong><br>\n<strong>I</strong> have formed the project of going to study in the Netherlands pretty soon for some personal reason, and thus it is very logical to me to study Dutch, although most Dutch people are fluent in English, if not in French and German... So I'm just beginning, don't put a lot of time in it, but I can understand it basically...</p>\n<p><strong>N</strong>ow I think you know quite all about this subject! Feel free to contact me!!</p>"},{"url":"/posts/20041215-blogspot-searchlight-with-pastor-jon-courson/","relativePath":"posts/20041215-blogspot-searchlight-with-pastor-jon-courson.md","relativeDir":"posts","base":"20041215-blogspot-searchlight-with-pastor-jon-courson.md","name":"20041215-blogspot-searchlight-with-pastor-jon-courson","frontmatter":{"title":"Searchlight with Pastor Jon Courson","template":"post","date":"2004-12-15T23:15:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/12/searchlight-with-pastor-jon-courson.html","blogspot_url":"https://raphink.blogspot.com/2004/12/searchlight-with-pastor-jon-courson.html","tags":["bible","christian","commentary","english","link","multiply","restored","website"]},"html":"<p><strong>Link: <a href=\"http://web.archive.org/web/20050306003904/http://www.joncourson.com/\">joncourson.com</a></strong>  </p>\n<p>Jon Courson is a great pastor from the ecumenical church of Calvary Chapel in Costa Mesa, CA. This site contains lots of teachings on his that you can listen online, about all the books of the Bible</p>"},{"url":"/posts/20041228-blogspot-mozilla-firefox-10-internet-browser/","relativePath":"posts/20041228-blogspot-mozilla-firefox-10-internet-browser.md","relativeDir":"posts","base":"20041228-blogspot-mozilla-firefox-10-internet-browser.md","name":"20041228-blogspot-mozilla-firefox-10-internet-browser","frontmatter":{"title":"Mozilla Firefox 1.0 Internet Browser","template":"post","date":"2004-12-28T20:17:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/12/mozilla-firefox-10-internet-browser.html","blogspot_url":"https://raphink.blogspot.com/2004/12/mozilla-firefox-10-internet-browser.html","tags":["english","multiply","open-source","restored","review"]},"html":"<p>Category:</p>\n<p>Other</p>\n<p>I know that a lot of people already know about Firefox, but this is such a great program that I have to write a review about it really, cause I know the only people who do not use it are people who do not know about it.  </p>\n<p>I've been using Firefox ever since its version 0.4 2 years ago, and though I have tried other browsers I never switched back to anything else. So if you are still using Internet Explorer or Opera, just give a try to Firefox, you just won't believe it...  </p>\n<p>Just go to the <a href=\"http://web.archive.org/web/20050308232548/http://www.mozilla.org/\">Mozilla Project Page</a> to download it.  </p>\n<p>That's all Folks</p>"},{"url":"/posts/20050114-blogspot-nice-story-i-just-read/","relativePath":"posts/20050114-blogspot-nice-story-i-just-read.md","relativeDir":"posts","base":"20050114-blogspot-nice-story-i-just-read.md","name":"20050114-blogspot-nice-story-i-just-read","frontmatter":{"title":"A nice story I just read...","template":"post","date":"2005-01-14T16:12:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/01/nice-story-i-just-read.html","blogspot_url":"https://raphink.blogspot.com/2005/01/nice-story-i-just-read.html","tags":["blog","christian","english","multiply","restored"]},"html":"<p>A young man got into a store.<br>\nHe asked to the angel that was here, behind the cass : ``What do you sell here, my good angel?''<br>\nThe angel answered him with a large smile ``We have everything you want''.<br>\n``Then I would like the wars to stop, a better life for marginal people, work for unemployed guys, the end of...'' The angels stopped him ``I'm sorry, young man, but it seems you did not read the panel on the door. Here we don't sell fruits, only seeds. Did you think about buying seeds of peace, of generosity, of joy, of courage? Also don't be worried, as the store will still be opened after Christmas''.</p>"},{"url":"/posts/20050119-blogspot-ubuntu-gnulinux-warty-ppc/","relativePath":"posts/20050119-blogspot-ubuntu-gnulinux-warty-ppc.md","relativeDir":"posts","base":"20050119-blogspot-ubuntu-gnulinux-warty-ppc.md","name":"20050119-blogspot-ubuntu-gnulinux-warty-ppc","frontmatter":{"title":"Ubuntu GNU/Linux Warty PPC","template":"post","date":"2005-01-19T13:56:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/01/ubuntu-gnulinux-warty-ppc.html","blogspot_url":"https://raphink.blogspot.com/2005/01/ubuntu-gnulinux-warty-ppc.html","tags":["english","linux","multiply","restored","review","ubuntu"]},"html":"<p>Category:</p>\n<p>Other</p>\n<p>I am just testing Ubuntu Linux for PPC as I've seen it seems to be a good new distrib.  </p>\n<p>Well I'll begin with good points :  </p>\n<ul>\n<li>it is based on Debian => rock solid</li>\n<li>it installed without any pb on my parents' eMac, and this is wow! This is the first distrib of linux PPC that I manage to install on this computer without having to hack anything around...</li>\n<li>the installation process is very well done and automatic</li>\n<li>my internal material was all recognized automatically. Well it's not exactly the case of my printer and scanner though...</li>\n</ul>\n<p>Bad points now :  </p>\n<ul>\n<li>where is KDE?? This distrib comes with Gnome only smiley('cry'); and I couldn't even install it with the universe non-official packages or the KDE cvs!</li>\n<li>there are still very few packages available (not even talking of XWindows ...)</li>\n<li>after 2 hours, the graphical package manager crashed, so I have to use apt-get in a console</li>\n<li>I don't like the default graphics ... can't help it</li>\n</ul>\n<p>I think this is a good starting point for a new distrib (esp. as I tested it on a G4 PPC), but it still has quite a lot to go before I can say I prefer it to Mandrake ... Let us hope Kubuntu (Ubuntu with native KDE project) will develop soon smiley('grin2');  </p>\n<p>For more infos about Ubuntu Linux, see <a href=\"http://web.archive.org/web/20050308232548/http://www.ubuntulinux.org/\">the official Ubuntu Linux website</a>.<br>\n<img src=\"http://web.archive.org/web/20050308232548/http://images.multiply.com/common/dot_clear.gif\"></p>"},{"url":"/posts/20050125-blogspot-using-quotes-in-posts-on-my-page/","relativePath":"posts/20050125-blogspot-using-quotes-in-posts-on-my-page.md","relativeDir":"posts","base":"20050125-blogspot-using-quotes-in-posts-on-my-page.md","name":"20050125-blogspot-using-quotes-in-posts-on-my-page","frontmatter":{"title":"Using quotes in posts on my page...","template":"post","date":"2005-01-25T23:55:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/01/using-quotes-in-posts-on-my-page.html","blogspot_url":"https://raphink.blogspot.com/2005/01/using-quotes-in-posts-on-my-page.html","tags":["blog","english","javascript","multiply","restored"]},"html":"<p>Just as for smilies, I just added a JavaScript function that lets you quote anything on my site the way Multiply does it!  </p>\n<p>This function is simply called quote(), and takes 3 arguments :  </p>\n<p>* text : the text to be quoted<br>\n* author : the author of the quote<br>\n* option : value 'get' or 'post', just as for the smiley() function, for advanced users who want to include a quote in a JavaScript code  </p>\n<p>To call it, simple use  </p>\n<p>quote(\"text\",\"author\");  </p>\n<p>on any post on my Multiply site.  </p>\n<p>For example, quote(\"Hello, this is a quote!\",\"the tester\"); will get you the following result :<br>\nHello, this is a quote!<br>\n- the tester  </p>\n<p>Enjoy!</p>"},{"url":"/posts/20050131-blogspot-christian-forums/","relativePath":"posts/20050131-blogspot-christian-forums.md","relativeDir":"posts","base":"20050131-blogspot-christian-forums.md","name":"20050131-blogspot-christian-forums","frontmatter":{"title":"Christian Forums","template":"post","date":"2005-01-31T17:09:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/01/christian-forums.html","blogspot_url":"https://raphink.blogspot.com/2005/01/christian-forums.html","tags":["christian","english","link","multiply","restored","website"]},"html":"<p><strong>Link: <a href=\"http://web.archive.org/web/20050306004508/http://www.christianforums.com/\">christianforums.com</a></strong>  </p>\n<p>This is a great place for both Christians and non Christians! There are tons of forums on many many subjects :D<br>\nCome and join!</p>"},{"url":"/posts/20050127-blogspot-ive-released-my-javascript-file/","relativePath":"posts/20050127-blogspot-ive-released-my-javascript-file.md","relativeDir":"posts","base":"20050127-blogspot-ive-released-my-javascript-file.md","name":"20050127-blogspot-ive-released-my-javascript-file","frontmatter":{"title":"I've released my JavaScript file","template":"post","date":"2005-01-27T01:31:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/01/ive-released-my-javascript-file.html","blogspot_url":"https://raphink.blogspot.com/2005/01/ive-released-my-javascript-file.html","tags":["blog","english","javascript","multiply","open-source","release","restored"]},"html":"<p><strong>W</strong>ell <a href=\"http://web.archive.org/web/20050306013005/http://modify.multiply.com/notes/item/41\">I posted on this subject on the Modify group</a> already, as they are the ones probably the most interested in it...  </p>\n<p>I have released my JavaScript file, that contains almost all the functions I use on my Multiply site : smilies, quotes, Bible quotes, info boxes, scheme menu, etc...  </p>\n<p>You can find out how to get the file and use it on the <a href=\"http://web.archive.org/web/20050306013005/http://r.pinson.free.fr/multiply/howto.html\">HOWTO file</a> I have made for the purpose.  </p>\n<p>Enjoy, and send me your comments!!</p>"},{"url":"/posts/20050202-blogspot-good-joke-from-m/","relativePath":"posts/20050202-blogspot-good-joke-from-m.md","relativeDir":"posts","base":"20050202-blogspot-good-joke-from-m.md","name":"20050202-blogspot-good-joke-from-m","frontmatter":{"title":"A good joke from M$","template":"post","date":"2005-02-02T14:17:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/02/good-joke-from-m.html","blogspot_url":"https://raphink.blogspot.com/2005/02/good-joke-from-m.html","tags":["blog","english","fun","microsoft","multiply","restored","website"]},"html":"<p><strong>I</strong>f you wanna have fun with how bad Microsoft programs their softs, try this little test ....  </p>\n<p>Go to <a href=\"http://web.archive.org/web/20050306013005/http://mappoint.msn.com/%28lnghob55v2whdmzju5zxcu55%29/DirectionsFind.aspx\">MapPoint</a> and ask for the way to get from Haugesund, Norway to Trondheim, Norway...  </p>\n<p>Well done M$!!  </p>\n<p>It's even better when you choose the 'shortest route' option instead of 'quickest'!</p>"},{"url":"/posts/20050225-blogspot-what-is-god-like/","relativePath":"posts/20050225-blogspot-what-is-god-like.md","relativeDir":"posts","base":"20050225-blogspot-what-is-god-like.md","name":"20050225-blogspot-what-is-god-like","frontmatter":{"title":"What is God like?","template":"post","date":"2005-02-25T16:33:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/02/what-is-god-like.html","blogspot_url":"https://raphink.blogspot.com/2005/02/what-is-god-like.html","tags":["book","english","god","multiply","restored","review"]},"html":"<p>Category:</p>\n<p>Books</p>\n<p>Genre:</p>\n<p>Religion &#x26; Spirituality</p>\n<p>Author:</p>\n<p>Marie-Agnes Gaudrat</p>\n<p>Too bad I can't put more than 5 stars here ...  </p>\n<p>This is one of the greatest books I've every found talking about God to children. I found it (in french) at a comics festival in Augoulème (France) and just fell in love with this book :)  </p>\n<p>I advised it to an online buddy of mine on Christian Forums, who bought it for her 6-yo girl, and since then her little wants to be read it every evening ;)  </p>\n<p>A really great book for children.</p>"},{"url":"/posts/20050210-blogspot-multiple-automatic-translation/","relativePath":"posts/20050210-blogspot-multiple-automatic-translation.md","relativeDir":"posts","base":"20050210-blogspot-multiple-automatic-translation.md","name":"20050210-blogspot-multiple-automatic-translation","frontmatter":{"title":"Multiple automatic translation","template":"post","date":"2005-02-10T04:06:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/02/multiple-automatic-translation.html","blogspot_url":"https://raphink.blogspot.com/2005/02/multiple-automatic-translation.html","tags":["blog","english","fun","multiply","restored"]},"html":"<p><strong>W</strong>hat happens when you take a text and have it translated a certain number of times? It gets nuts...<br>\nI tried it with the Google automatic translator, as I was trying to explain my Multplext project to an only portuguese-speaking guy. Just for fun, I had it translated to portuguese, then english again, then french, german, spanish and some others... After 10 pass or so, this is what I got in english...  </p>\n<p>box(\"After 10 pass of automatic translator\",\"The plan of Multiplext was developed in the base of the group of the changes which multiplies the profile. It is not sharp of substituiz, but, so that a kind provides with adapted extremely the profile relating to the customer requirements, without having, in order to learn the CSS just and the Javascript and at a zone of the leather belt of of will fotorreceptora, in order to repair the iron of friction. This plan is a plan \\\"source of opening\\\". That means that it of A can there begin the sources and the copy of code in such a manner around it of changing and around him distributes during much hour, whereas they maintain the trade of the author of the code. If it will have to be moltiplicatlaa inside if a customer of and will interest in order to employ this plan you can have more information of website inside of Multiplext. If he will be to you a colleague and that required in order to connect the Community in order to think freely in the order, because being connected and being required, in the manner marks of one this operation here:)\");</p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/ltc.gif\"></p>\n<p>After 10 pass of automatic translator</p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/rtc.gif\"></p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/common/dot_clear.gif\"><br>\nThe plan of Multiplext was developed in the base of the group of the changes which multiplies the profile. It is not sharp of substituiz, but, so that a kind provides with adapted extremely the profile relating to the customer requirements, without having, in order to learn the CSS just and the Javascript and at a zone of the leather belt of of will fotorreceptora, in order to repair the iron of friction. This plan is a plan \"source of opening\". That means that it of A can there begin the sources and the copy of code in such a manner around it of changing and around him distributes during much hour, whereas they maintain the trade of the author of the code. If it will have to be moltiplicatlaa inside if a customer of and will interest in order to employ this plan you can have more information of website inside of Multiplext. If he will be to you a colleague and that required in order to connect the Community in order to think freely in the order, because being connected and being required, in the manner marks of one this operation here:)</p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/lbc.gif\"></p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/rbc.gif\"></p>\n<p>when the original text to be translated was ...  </p>\n<p>box(\"The original text\",\"The Multiplext project was built on the basis of the Modify your Multiply profile group. It is not aimed to replace it, but to provide a way to greatly customize your profile without having to learn css and javascript and to own a web space to host files.<br /><br />This project is an open-source project. That means anybody can get the code sources and copy, modify and distribute them, as long as they keep the authorship of the code.<br /><br />If you are a Multiply user and are interested in using this project, you can have more informations on the Multiplext website.<br /><br />If you are a developer and wish to join the community, feel free to join and ask for a task to do here :)\");</p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/ltc.gif\"></p>\n<p>The original text</p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/rtc.gif\"></p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/common/dot_clear.gif\"><br>\nThe Multiplext project was built on the basis of the Modify your Multiply profile group. It is not aimed to replace it, but to provide a way to greatly customize your profile without having to learn css and javascript and to own a web space to host files.  </p>\n<p>This project is an open-source project. That means anybody can get the code sources and copy, modify and distribute them, as long as they keep the authorship of the code.  </p>\n<p>If you are a Multiply user and are interested in using this project, you can have more informations on the Multiplext website.  </p>\n<p>If you are a developer and wish to join the community, feel free to join and ask for a task to do here :)</p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/lbc.gif\"></p>\n<p><img src=\"http://web.archive.org/web/20050306013005/http://images.multiply.com/multiply/sidebox/rbc.gif\"></p>\n<p>LOL</p>"},{"url":"/posts/20050303-blogspot-knoppix-37/","relativePath":"posts/20050303-blogspot-knoppix-37.md","relativeDir":"posts","base":"20050303-blogspot-knoppix-37.md","name":"20050303-blogspot-knoppix-37","frontmatter":{"title":"Knoppix 3.7","template":"post","date":"2005-03-03T18:46:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/03/knoppix-37.html","blogspot_url":"https://raphink.blogspot.com/2005/03/knoppix-37.html","tags":["debian","english","knoppix","linux","multiply","restored","review"]},"html":"<p>Category:</p>\n<p>Other</p>\n<p>Well I've been using knoppix for a long time now, but I thought I would write a review on it though.  </p>\n<p>You have heard of linux and wonder about what it is? You would like to try it but don't want to take any risk with your windows box?  </p>\n<p>Then you need to use a live CD, and knoppix is the best you can try imho :)  </p>\n<p>All you have to do is download a CD image of Knoppix and burn it to a CD, and then reboot your computer on the CD.  </p>\n<p>You can find sources to download the CD image of Knoppix on <a href=\"http://web.archive.org/web/20050308232548/http://www.knoppix.net/get.php\">the knoppix site</a>.  </p>\n<p>Enjoy!</p>"},{"url":"/posts/20050303-blogspot-logiciels-libres-pour-lenseignement/","relativePath":"posts/20050303-blogspot-logiciels-libres-pour-lenseignement.md","relativeDir":"posts","base":"20050303-blogspot-logiciels-libres-pour-lenseignement.md","name":"20050303-blogspot-logiciels-libres-pour-lenseignement","frontmatter":{"title":"Logiciels libres pour l'enseignement","template":"post","date":"2005-03-03T15:03:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/03/logiciels-libres-pour-lenseignement.html","blogspot_url":"https://raphink.blogspot.com/2005/03/logiciels-libres-pour-lenseignement.html","tags":["english","france","link","multiply","open-source","restored","website"]},"html":"<p><strong>Link: <a href=\"http://web.archive.org/web/20050306005226/http://logiciels-libres-cndp.ac-versailles.fr/\">logiciels-libres-cndp.ac-versailles.fr</a></strong>  </p>\n<p>Un site de l'académie de Versailles listant des logiciels libres utiles pour l'enseignement.  </p>\n<p>A website with lists of open-source programs useful for education (website in French, programs in most languages).</p>"},{"url":"/posts/20050303-blogspot-learning-languages-bilingualism-vs/","relativePath":"posts/20050303-blogspot-learning-languages-bilingualism-vs.md","relativeDir":"posts","base":"20050303-blogspot-learning-languages-bilingualism-vs.md","name":"20050303-blogspot-learning-languages-bilingualism-vs","frontmatter":{"title":"Learning languages : bilingualism vs. translation","template":"post","date":"2005-03-03T22:46:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/03/learning-languages-bilingualism-vs.html","blogspot_url":"https://raphink.blogspot.com/2005/03/learning-languages-bilingualism-vs.html","tags":["blog","english","language","learning","multiply","restored"]},"html":"<p><strong>W</strong>hy is it so difficult for most people to learn languages and get to speak fluently? Why do children have to learn long lists of vocabulary and huge grammar rules when we did not need them to speak our mother tongue?  </p>\n<p>I would like to write a short article on bilingualism vs. translation in learning languages, although I'm far from being a specialist in neuro-linguistics. I'll try to point out the basic bad and good points of each way.  </p>\n<p>Let us first remember the basic theory of speech : our brain links concepts to words, directly or not (that's very basic, but enough for what I want to say). We will consider that when you speak in your mother tongue, the concept is directly linked to the word you pronounce.  </p>\n<p><strong>Translation</strong><br>\n<strong>I</strong> will begin with translation, because it is the usual way of learning languages in school. Most people learn vocabulary and grammar and translate the thoughts they have in their language to the language they want to speak.  </p>\n<p>With this method, you will first think in your mother tongue, get the word that is linked to the concept you want to express, and then use this word as a new concept to link it to another word in the language in which you want to express yourself.  </p>\n<p>The only good point I see about this way of learning is that you can translate things quite well... and I'm not even sure this is very true...  </p>\n<p>Now there are a lot of bad points about it.<br>\nFirst of all, it's obvious that it takes you longer to express yourself, as there are more steps. If you have to do this when speaking <strong>and</strong> listening, your brain is going to hurt very soon...<br>\nSecondly, when you speak a second language this way, you cannot express all you want, because the basic concepts used by different languages are not the same. For example, there is no equivalent to the verb ``may'' in french, so that many french people might not use it where it should be used.  </p>\n<p><strong>Bilingualism</strong><br>\n<strong>B</strong>ilingualism is often considered a chance that few people have, and an utopia for people who never had this chance. I think this point of view is not very exact though.<br>\nAlthough it is true that people who were raised up in two or more language till they were children are quite lucky, it is not a fate to not have this opportunity, and there are still ways to get to be fluent having not had it.  </p>\n<p>To me, the key to bilinguism is to get to think in the language. Practically it's not as difficult as it seems. The main issue is a psychological one : if you <strong>decide</strong> to think in a language you do not speak fluently, it obviously means that your thoughts are going to be much more basic than they are when you think in your mother tongue! Once you accept this fact and <strong>take the decision</strong> to think in the foreign language you are willing to learn, this fact will indeed ``pull you forward'', in the sense that you will not need to speak fluently only to communicate with people, but also to <strong>improve your own thoughts</strong>, and you will learn vocabulary very naturally and fast, as you would in your mother tongue!  </p>\n<p>Why is bilingualism better?<br>\nOne obvious point is that you can express yourself much more easily in the language than if you were translating, because you think in the language.<br>\nAnother very important point to me, is that by getting to think in another language, you link more concepts to words in your mind, including concepts that cannot be easily expressed in your mother tongue. So you basically improve your ``thinking ability'' by enlarging the basis of the concepts you can use to think.<br>\nThis also leads to another important point : you understand better people who speak the other language, not only because you speak the same language as in speaking aloud, but also because you can share the same concepts with them, and grasp their mind much more easily.  </p>\n<p><strong>Now practically?</strong>  </p>\n<p><strong>H</strong>ow do I <strong>decide to think</strong> in a second language? It doesn't seem that easy for sure... Well as I said before, the only important obstacle to it is psychological : you might be afraid to have very basic thought when you have few vocabulary... Once you have passed this step, what I do (hoping it can help) is that I take times in the day when I force myself to think in the language, but as it's very conceptual and difficult, I <strong>talk aloud to myself</strong>. I comment my life and actions in the language I want to learn. This way, knowing enough of the language, I can correct myself on both syntax, vocabulary and pronounciation most of the time. This way I force myself to link directly images and feelings to words, without translating, and talking aloud to myself allows me to hear my accent and try to correct it from how I know it should be.  </p>\n<p>I've been using this method for a few years now, and I cal tell it's very efficient on me at least. Researchers tend to prove that a second language should be taught before the age of 7, because bilinguism is very hard to aquire after this age. This is mostly true about pronounciation, but to think in a language can be achieved at almost any age in my opinion.  </p>\n<p>That's all folks! Let me know your opinion :D</p>"},{"url":"/posts/20051216-blogspot-taiz-community/","relativePath":"posts/20051216-blogspot-taiz-community.md","relativeDir":"posts","base":"20051216-blogspot-taiz-community.md","name":"20051216-blogspot-taiz-community","frontmatter":{"title":"Taizé Community","template":"post","date":"2005-12-16T16:35:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2005/12/taiz-community.html","blogspot_url":"https://raphink.blogspot.com/2005/12/taiz-community.html","tags":["christian","english","multiply","restored","review"]},"html":"<p>Category:</p>\n<p>Other</p>\n<p>Taizé is an ecumenical Christian community in Bourgogne, France. It has been founded by brother Roger, and gathers now more than a hundred brothers. Taizé welcomes every year thousands of young people from all over the world and the community organizes meetings of tens of thousands people in European great cities.  </p>\n<p>You can find out much more about Taizé on <a href=\"http://web.archive.org/web/20050308232548/http://www.taize.fr/\">their official site</a>.  </p>\n<p>You can also check the <a href=\"http://web.archive.org/web/20050308232548/http://taize.multiply.com/\">Taizé Community</a> on Multiply.<br>\n<img src=\"http://web.archive.org/web/20050308232548/http://images.multiply.com/common/dot_clear.gif\"></p>"},{"url":"/posts/20050518-blogspot-beware-dangerous-hackers-around/","relativePath":"posts/20050518-blogspot-beware-dangerous-hackers-around.md","relativeDir":"posts","base":"20050518-blogspot-beware-dangerous-hackers-around.md","name":"20050518-blogspot-beware-dangerous-hackers-around","frontmatter":{"title":"Beware, dangerous hackers around...","template":"post","date":"2005-05-18T01:59:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2005/05/beware-dangerous-hackers-around.html","blogspot_url":"https://raphink.blogspot.com/2005/05/beware-dangerous-hackers-around.html","tags":["blog","english","fun","geek","multiply","restored"]},"html":"<p>The story starts on the IRC channel. Most people there believed it was rather funny, but it got even more funny. For information: The dangerous hacker is called *****checker and the one being hacked is known as Elch. 127.0.0.1 is always the IP-adress of the computer you're currently using (duh), any request there will return to your computer.  </p>\n<p>* *****checker (~java@euirc-a97f9137.dip.t-dialin.net) Quit (Ping timeout#)<br>\n* *****checker (~java@euirc-61a2169c.dip.t-dialin.net) has joined #stopHipHop<br>\n&#x3C;*****checker> why do you kick me<br>\n&#x3C;*****checker> can't you discus normally<br>\n&#x3C;*****checker> answer!<br>\nwe didn't kick you<br>\nyou had a ping timeout: * *****checker (~java@euirc-a97f9137.dip.t-dialin.net) Quit (Ping timeout#)<br>\n&#x3C;*****checker> what ping man<br>\n&#x3C;*****checker> the timing of my pc is right<br>\n&#x3C;*****checker> i even have dst<br>\n&#x3C;*****checker> you banned me<br>\n&#x3C;*****checker> amit it you son of a *****<br>\nLOL<br>\n**** you're stupid, DST^^<br>\n&#x3C;*****checker> shut your mouth WE HAVE DST!<br>\n&#x3C;*****checker> for two weaks already<br>\n&#x3C;*****checker> when you start your pc there is a message from windows that DST is applied.<br>\nYou're a real computer expert<br>\n&#x3C;*****checker> shut up i hack you<br>\nok, i'm quiet, hope you don't show us how good a hacker you are ^^<br>\n&#x3C;*****checker> tell me your network number man then you're dead<br>\nEh, it's 129.0.0.1<br>\nor maybe 127.0.0.1<br>\nyes exactly that's it: 127.0.0.1 I'm waiting for you great attack<br>\n&#x3C;*****checker> in five minutes your hard drive is deleted<br>\nNow I'm frightened<br>\n&#x3C;*****checker> shut up you'll be gone<br>\n&#x3C;*****checker> i have a program where i enter your ip and you're dead<br>\n&#x3C;*****checker> say goodbye<br>\nto whom?<br>\n&#x3C;*****checker> to you man<br>\n&#x3C;*****checker> buy buy<br>\nI'm shivering thinking about such great Hack0rs like you<br>\n* *****checker (~java@euirc-61a2169c.dip.t-dialin.net) Quit (Ping timeout#)  </p>\n<p>What happened is clear: That guy entered his own IP-Adress in his mighty Hack-Tool and crashed his own PC. This way, the attack on my PC was a failure. I was already starting to think that I did not have to worry, but a good hacker never calls it a day. Two minutes later he returned.  </p>\n<p>* *****checker (~java@euirc-b5cd558e.dip.t-dialin.net) has joined #stopHipHop<br>\n&#x3C;*****checker> dude be happy my pc crashed otherwise you'd be gone<br>\nlol<br>\n*****checker: Then try hacking me again... I still have the same IP: 127.0.0.1<br>\n&#x3C;*****checker> you're so stupid man<br>\n&#x3C;*****checker> say buy buy<br>\nah, [Please control your cussing] off<br>\n&#x3C;*****checker> buy buy elch<br>\n* *****checker (~java@euirc-b5cd558e.dip.t-dialin.net) Quit (Ping timeout#)  </p>\n<p>There was a tension in the room... Would he manage, after these two failures, to crash my PC? I waited. Nothing happened. I felt relieve... Six minutes p***ed by until he prepared the next wave of attack. Being a Hacker, who usually cracks whole data centers, he knew what his problem was now.  </p>\n<p>* *****checker (~java@euirc-9ff3c180.dip.t-dialin.net) has joined #stopHipHop<br>\n&#x3C;*****checker> elch you son of a *****<br>\n*****checker how old are you?<br>\nWhat's up *****checker?<br>\n&#x3C;*****checker> you have a frie wal<br>\n&#x3C;*****checker> fire wall<br>\nmaybe, i don't know<br>\n&#x3C;*****checker> i'm 26<br>\nsuch behaviour with 26?<br>\nhow did you find out that I have a firewall?<br>\ntststs this is not very nice missy<br>\n&#x3C;*****checker> because your gay fire wall directed my turn off signal back to me<br>\n&#x3C;*****checker> be a man turn that **** off<br>\ncool, didn't know this was possible.<br>\n&#x3C;*****checker> thn my virus destroys your pc man<br>\nare you hacking yourselves?<br>\nyes *****checker is trying to hack me<br>\nhe *****checker if you're a hacker you have to get around a firewall even i can do that<br>\n&#x3C;*****checker> yes man i hack the elch but the sucker has a fire wall the<br>\nwhat firewall do you have?<br>\n&#x3C;*****checker> like a girl<br>\nfirewall is normal a normal hacker has to be able to get past it...you girl^^<br>\n***** give yourself a jackson and chill you're letting them provoce you and give those little girls new material all the time<br>\n&#x3C;*****checker> turn the firewall off then i send you a virus [Please control your cussing]er<br>\nNoo<br>\nhe *****checker why turn it off, you should turn it off<br>\n&#x3C;*****checker> you're afraid<br>\n&#x3C;*****checker> i don't wanna hack like this if he hides like a girl behind a fire wall<br>\n&#x3C;*****checker> elch turn off your **** wall!<br>\ni wanted to say something about this, do you know the definition of hacking??? if he turns of the firewall that's an invitation and that has nothing to do with hacking<br>\n&#x3C;*****checker> shut up<br>\nlol<br>\n&#x3C;*****checker> my grandma surfs with fire wall<br>\n&#x3C;*****checker> and you suckers think you're cool and don't dare going into the internet without a fire wall  </p>\n<p>He calls me girly and says only his grandma would use a firewall. I know that elder people are much more intelligent then younger, but I couldn't let that rest. To see whether he really is a good hacker I lie and let everything as it is. I don't have a firewall at all, only my router.  </p>\n<p>*****checker, a collegue showed me how to turn the firewall off. Now you can try again<br>\n*****hacker can't hack<br>\n> nice play on words ^^<br>\n&#x3C;*****checker> wort man<br>\n*****checker: I'm still waiting for your attack!<br>\nhow many times again he is no hacker<br>\n&#x3C;*****checker> man do you want a virus<br>\n&#x3C;*****checker> tell me your ip and it deletes your hard drive<br>\nlol ne give it up i'm a hacker myself and i know how hackers behave and i can tell you 100.00% you're no hacker..^^<br>\n127.0.0.1<br>\nit's easy<br>\n&#x3C;*****checker> lolololol you so stupid man you'll be gone<br>\n&#x3C;*****checker> and are the first files being deleted<br>\nmom...<br>\ni'll take a look  </p>\n<p>In panic I started the Windows Explorer, my heart beating faster. Had I under-estimated him?  </p>\n<p>&#x3C;*****checker> don't need to rescue you can't son of a *****<br>\nthat's bad<br>\n&#x3C;*****checker> elch you idiout your hard drive g: is deleted<br>\nyes, there's nothing i can do about it<br>\n&#x3C;*****checker> and in 20 seconds f: is gone  </p>\n<p>Yes, true, G: and F: were gone. Did I ever have them? Doesn't matter, I did not have time to think, I was scared. *****checker was comforting me with a music tip.  </p>\n<p>&#x3C;*****checker> tupac rules<br>\n&#x3C;*****checker> elch you son of a ***** your f: is gone and e: too  </p>\n<p>Drive E:? Oh my god... All the games are there! And the vacation pictures! I instantly take a look. Everything still there. But the hacker said it was deleted....  </p>\n<p>Or isn't it happening on my computer?  </p>\n<p>&#x3C;*****checker> and d: is at 45% you idiot lolololol<br>\nwhy doesn't meta say anything<br>\nhe's probably rolling on the floor laughing<br>\n> ^^<br>\n&#x3C;*****checker> your d: is gone<br>\ngo on *****  </p>\n<p>The guy is good: My CD-drive is allegedly deleted! *****checker turned my ancient disk sucker into a burner! But how did he do this? I'll have to ask him. Some encourage him. He himself is giving advice how to avoid the disaster on my hard drives.  </p>\n<p>&#x3C;*****checker> elch man you're so stupid never give your ip on the internet<br>\n&#x3C;*****checker> i'm already at c: 30 percent  </p>\n<p>Should I tell him he's not attacking my computer?  </p>\n<p>* *****checker (~java@euirc-9ff3c180.dip.t-dialin.net) Quit (Ping timeout#)  </p>\n<p>Too late... It's 20:22 when we get the last message of our hacker with the alias \"*****checker\". We see that he has a \"Ping timeout\". We haven't seen him since then... must be the Daylight Saving Time.</p>"},{"url":"/posts/20060305-blogspot-finding-good-open-source-blogging/","relativePath":"posts/20060305-blogspot-finding-good-open-source-blogging.md","relativeDir":"posts","base":"20060305-blogspot-finding-good-open-source-blogging.md","name":"20060305-blogspot-finding-good-open-source-blogging","frontmatter":{"title":"Finding a good open-source blogging program","template":"post","date":"2006-03-05T15:22:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/finding-good-open-source-blogging.html","blogspot_url":"https://raphink.blogspot.com/2006/03/finding-good-open-source-blogging.html","tags":["blog","english","kde","open-source","restored","wordpress"]},"html":"<p>I’ve been trying to find a program to post to blogger directly from the systray without having to use an internet browser.</p>\n<p>Since I’m on KDE, my first try was <a href=\"http://web.archive.org/web/20060806182544/http://kde-apps.org/content/show.php?content=29552\">K-Blogger</a>, a new KDE app that was recently added to Dapper. I didn’t get it work so far, and since I get no trace of what it does, I doubt I can easily find out what’s wrong with it.</p>\n<p>Now I’m writing this blog from within <a href=\"http://web.archive.org/web/20060806182544/http://www.gnome.org/%7Eseth/gnome-blog/\">GNOME-Blog</a>. Well I’m not a GNOME fan, but at least this app seems to work with blogger. I allows to set the font &#x26; add links, and detects your blogs automatically. GNOME-Blog seems to get the post title inside the<br>\npost though, strangely enough …</p>\n<p>We’ll see how K-Blogger evolves in the near future <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p>Edit : now I’ve switched to Wordpress and Kblogger works fine. Just had to enter the path to my xmlrpc.php in the preferences and use the MetaWebLog option. I used my blog’s name as blog-id although I’m not sure it does something. It works <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_biggrin.gif\" alt=\":D\"></p>"},{"url":"/posts/20060305-blogspot-god-is-amazing/","relativePath":"posts/20060305-blogspot-god-is-amazing.md","relativeDir":"posts","base":"20060305-blogspot-god-is-amazing.md","name":"20060305-blogspot-god-is-amazing","frontmatter":{"title":"God is amazing!","template":"post","date":"2006-03-05T15:00:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/god-is-amazing.html","blogspot_url":"https://raphink.blogspot.com/2006/03/god-is-amazing.html","tags":["bible","english","god","restored"]},"html":"<p>God is amazing… He’s so patient when He wants to tell us things…</p>\n<p>A bit more than a year ago, as I was in Texas, I woke up one night thinking that I had to open my Bible and read Jeremiah 6:10. I did and was blown up, because that sentence made sense. It says :</p>\n<blockquote>\n<p>To whom can I speak and give warning?<br>\nWho will listen to me?<br>\nTheir ears are closed<br>\nso they cannot hear.<br>\nThe word of the LORD is offensive to them;<br>\nthey find no pleasure in it.</p>\n</blockquote>\n<p>Take a random sentence in the Bible and see if it makes sense alone. This one did, and it made sense to me. It became important to me, but after a while I wasn’t focused on it and on understanding it really.</p>\n<p>When yesterday evening, I opened my Bible randomly, and my eyes fell exactly on Jeremiah 6:10 again. Now you could say that this Bible of mine opens more easily on this page than on others, but it was actually another Bible than the one I was using a year ago…</p>\n<p>This time I feel this is really aimed to me directly, and I am the one who needs to open his ears more to the Word. So here I pray that He opens my ears and my eyes <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20060307-blogspot-debugging-kde-351/","relativePath":"posts/20060307-blogspot-debugging-kde-351.md","relativeDir":"posts","base":"20060307-blogspot-debugging-kde-351.md","name":"20060307-blogspot-debugging-kde-351","frontmatter":{"title":"Debugging KDE 3.5.1","template":"post","date":"2006-03-07T15:21:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/debugging-kde-351.html","blogspot_url":"https://raphink.blogspot.com/2006/03/debugging-kde-351.html","tags":["english","kde","kubuntu","restored"]},"html":"<p>I have been worried about KDE 3.5.1 in Kubuntu Dapper lately. It still has a lot of (major) bugs, like not being able to print or having Kontact lose all the contacts from kaddressbook when closing the app with a mail opened.</p>\n<p>Now lately we have been fixing important bugs so I’m less worried. There is still some work to be done to make Kubuntu Dapper a nice and usable distro, but at least it feels this can be done in the month that is left <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p>I actually hope we don’t get to integrate KDE 3.5.2 in Dapper 3 weeks before release, otherwise I’m afraid we’re going to face a hard bugfixing time.</p>"},{"url":"/posts/20060313-blogspot-commandments-to-free/","relativePath":"posts/20060313-blogspot-commandments-to-free.md","relativeDir":"posts","base":"20060313-blogspot-commandments-to-free.md","name":"20060313-blogspot-commandments-to-free","frontmatter":{"title":"Commandments to free!","template":"post","date":"2006-03-13T15:16:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/commandments-to-free.html","blogspot_url":"https://raphink.blogspot.com/2006/03/commandments-to-free.html","tags":["bible","english","god","restored"]},"html":"<p>I had a very nice time at the church this morning <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p>I went to this church I love (well I’ve kind of grown in it, spent my first real times in a living Christian community there…) and there was a service with children today, organized by the new pastor. This sermon was focused on commandments : the 10 ones, given my God to Moses on Mount Sinai.</p>\n<p><a href=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-content/uploads/2006/03/otp6.jpg\" title=\"otp6.jpg\"><img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-content/uploads/2006/03/otp6.jpg\" alt=\"otp6.jpg\"></a><br>\nNow we usually think of God’s commandments as a law, as a set of rules to prevent us from sinning. But today the point was very different from this. This pastor showed us how the 10 commandments are to free us from salvation.</p>\n<p>First of all, let’s have a look at the text, which is located at Exodus 20 (quoting from <a href=\"http://web.archive.org/web/20060806182544/http://www.biblegateway.com/\">BibleGateway.com</a>) :</p>\n<h4>Exodus 20</h4>\n<h5>The Ten Commandments</h5>\n<p>1 And God spoke all these words:</p>\n<p>2 “I am the LORD your God, who brought you out of Egypt, out of the land of slavery.</p>\n<p>3 “You shall have no other gods before [<a href=\"http://web.archive.org/web/20060806182544/http://www.biblegateway.com/passage/?search=exodus%2020&#x26;version=31#fen-NIV-2055a\" title=\"See footnote a\">a</a>] me.</p>\n<p>4 “You shall not make for yourself an idol in the form of anything in heaven above or on the earth beneath or in the waters below. 5 You shall not bow down to them or worship them; for I, the LORD your God, am a jealous God, punishing the children for the sin of the fathers to the third and fourth generation of those who hate me, 6 but showing love to a thousand {generations} of those who love me and keep my commandments.</p>\n<p>7 “You shall not misuse the name of the LORD your God, for the LORD will not hold anyone guiltless who misuses his name.</p>\n<p>8 “Remember the Sabbath day by keeping it holy. 9 Six days you shall labor and do all your work, 10 but the seventh day is a Sabbath to the LORD your God. On it you shall not do any work, neither you, nor your son or daughter, nor your manservant or maidservant, nor your animals, nor the alien within your gates. 11 For in six days the LORD made the heavens and the earth, the sea, and all that is in them, but he rested on the seventh day. Therefore the LORD blessed the Sabbath day and made it holy.</p>\n<p>12 “Honor your father and your mother, so that you may live long in the land the LORD your God is giving you.</p>\n<p>13 “You shall not murder.</p>\n<p>14 “You shall not commit adultery.</p>\n<p>15 “You shall not steal.</p>\n<p>16 “You shall not give false testimony against your neighbor.</p>\n<p>17 “You shall not covet your neighbor’s house. You shall not covet your neighbor’s wife, or his manservant or maidservant, his ox or donkey, or anything that belongs to your neighbor.”</p>\n<p>18 When the people saw the thunder and lightning and heard the trumpet and saw the mountain in smoke, they trembled with fear. They stayed at a distance 19 and said to Moses, “Speak to us yourself and we will listen. But do not have God speak to us or we will die.”</p>\n<p>20 Moses said to the people, “Do not be afraid. God has come to test you, so that the fear of God will be with you to keep you from sinning.”</p>\n<p>21 The people remained at a distance, while Moses approached the thick darkness where God was.</p>\n<p>Now let’s try and overgo the classic reading of these as rules, and see it as promisses, as the achievement of God freeing the Hebrews from their slavery in Egypt. These promisses will be fulfilled for the ones laying their lives on Him.<br>\nWhen God says “You shall have no other gods before me.”, He might mean “I promiss you will not be a slave of other gods anymore. I will free you from the gods you made before yourself: vanity, pride, etc.”</p>\n<p>When He says “You shall not murder.”, He tells us “I promiss you will not be a slave of your will to kill or do harm anymore. I will free you from the temptation of killing.”</p>\n<p>“You shall not commit adultery.” might mean for Him “I promiss I will free you from being a slave of your body.”</p>\n<p>And we can go on with the other ones, to discover that these commandments that seem to us like rules are actually promisses to free us from slavery, a key given to us for our happiness if we choose to lay our lives on God.</p>\n<p>All these commandments, after all, have been summed up the greatest way by Jesus who, quoting the Scriptures, remembered us that the greatest commandment among all is to love our God with all our self, and to love our neighbour as ourselves.</p>"},{"url":"/posts/20060311-blogspot-sharing-partition-with-macos/","relativePath":"posts/20060311-blogspot-sharing-partition-with-macos.md","relativeDir":"posts","base":"20060311-blogspot-sharing-partition-with-macos.md","name":"20060311-blogspot-sharing-partition-with-macos","frontmatter":{"title":"Sharing partition with MacOS","template":"post","date":"2006-03-11T15:19:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/sharing-partition-with-macos.html","blogspot_url":"https://raphink.blogspot.com/2006/03/sharing-partition-with-macos.html","tags":["english","linux","mac","restored"]},"html":"<p>Since I’ve just installed a double boot with MacOS X Panther and Kubuntu Dapper, I’ve been wondering how to share datas between both OSes.</p>\n<p>I found answers on various forums. I tried a few things, and these are my conclusions (summing up forums on some points):</p>\n<ul>\n<li>Although MacOS claims to support Unix FS, it is a bad idea to use it, cause it doesn’t use the standard (FreeBSD) implementation of it, so Linux won’t recognize it.</li>\n<li>Using FAT would do, but would be weird to share between two Unix systems…</li>\n<li>MacOS doesn’t support ext2 natively. That is very bad for a Unix system, but that’s how it is. There is an <a href=\"http://web.archive.org/web/20060806182544/http://sourceforge.net/projects/ext2fsx/\">ext2 for MacOS project on sourceforge</a> but it doesn’t seem to me like the best option…</li>\n<li>Linux has been supporting hfs+ since quite a lot of time, and Ubuntu’s kernel uses it without a slight problem, so that seems like the best idea. However, recently, the support for writing on hfs+ <strong>journalised</strong> FS has been disabled by the maintainer because it caused too many bugs. Therefore the best option seems to use <strong>not journalised hfs+</strong> for a common partition.</li>\n</ul>\n<p>This is what I have in my /etc/fstab to set this partition :</p>\n<p>/dev/hda4       /home/medias    hfsplus user,rw,umask=022       0       0</p>\n<p>Enjoy!</p>"},{"url":"/posts/20060310-blogspot-to-go-for-main-or-not-to-go-for-main/","relativePath":"posts/20060310-blogspot-to-go-for-main-or-not-to-go-for-main.md","relativeDir":"posts","base":"20060310-blogspot-to-go-for-main-or-not-to-go-for-main.md","name":"20060310-blogspot-to-go-for-main-or-not-to-go-for-main","frontmatter":{"title":"To go for main or not to go for main… that is the question!","template":"post","date":"2006-03-10T15:20:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/to-go-for-main-or-not-to-go-for-main.html","blogspot_url":"https://raphink.blogspot.com/2006/03/to-go-for-main-or-not-to-go-for-main.html","tags":["english","kde","linux","restored","ubuntu"]},"html":"<p>Almost a month from Dapper release, we upload quite a lot of packages to main, especially to fix bugs, and it’s important that the fixes are uploaded fast enough so we can move on to other bugs and release Dapper as stable as can be.</p>\n<p>Now as of today the Kubuntu team has only one core-dev to upload packages to main… I don’t blame anybody for this, this is just a matter of fact, and I believe Jonathan Riddell has better to focus on than checking our fixes all day to upload them. So we need at least one more core-dev to commit these fixes so Jonathan can focus on his goals for Kubuntu. Just as, a few months ago, I was pushed to go for MOTU to help uploading to universe as there was a lack of KDE MOTUs, we find ourselves with a lack of core-devs today, so I’m thinking of applying for core-dev. This is a special situation: I don’t really have a great reason to apply for core-dev but to help Jonathan focus on his goals, removing some burden from his back, at least for the last month before release.</p>\n<p>Next Technical Board meeting is next tuesday, so I’ve still got a few days to think about it. I think it doesn’t cost much to apply and see what happens, so I might very likely go for it… Even being ridiculous if I’m asked technical questions I can’t answer won’t kill me <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20060313-blogspot-revu-tools/","relativePath":"posts/20060313-blogspot-revu-tools.md","relativeDir":"posts","base":"20060313-blogspot-revu-tools.md","name":"20060313-blogspot-revu-tools","frontmatter":{"title":"REVU-Tools","template":"post","date":"2006-03-13T15:18:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/revu-tools.html","blogspot_url":"https://raphink.blogspot.com/2006/03/revu-tools.html","tags":["linux","pbuilder","restored"]},"html":"<p>I’ve just uploaded a version 0.6 of REVU-Tools to Dapper.</p>\n<p>Thanks to Fathi Boudra, it now supports cascading settings in /etc/revu-tools.conf, /usr/share/revu-tools/revu-tools.conf and ~/.revu-tools.conf. Note that the package only creates /etc/revu-tools.conf so far.</p>\n<p>Note that it is usually a good idea to set at least PBUILDERNAME (most users want it set to “sudo pbuilder”) before running the tools, and REVUDIR if you plan on using revu-review.</p>\n<p>For more infos on REVU-Tools, you can check <a href=\"http://web.archive.org/web/20060806182544/https://wiki.ubuntu.com/MOTU/Packages/REVU/REVU-Tools\">the wiki page</a>.</p>"},{"url":"/posts/20060313-blogspot-do-as-you-want-commandments-part-2/","relativePath":"posts/20060313-blogspot-do-as-you-want-commandments-part-2.md","relativeDir":"posts","base":"20060313-blogspot-do-as-you-want-commandments-part-2.md","name":"20060313-blogspot-do-as-you-want-commandments-part-2","frontmatter":{"title":"Do as you want… (commandments part 2)","template":"post","date":"2006-03-13T17:15:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/do-as-you-want-commandments-part-2.html","blogspot_url":"https://raphink.blogspot.com/2006/03/do-as-you-want-commandments-part-2.html","tags":["bible","english","god","jew","restored"]},"html":"<p>Here is a nice story that was told this morning as part of the sermon on the commandments. I want to tell it apart, so has not to mix it with the already long previous post. This is a story told among Jews ; don’t try to find it in the Bible, it’s not in it.</p>\n<blockquote>\n<p>Moses was on mount Sinai, receiving the commandments from God. God told him : “You shall not cook an animal’s meat into its mother’s milk”.<br>\nMoses answered : “Ok Lord, I get that. Let me rephrase : I shall not eat meat and milk together, right?”<br>\nThe Lord is patient and kind, so he said : “No, that is not it, I mean to say you shall not cook an animal’s meat into its mother’s milk”<br>\nMoses said : “Oooh ok! So correct me if I’m wrong : I shall keep different plates for milk and for meat, so I don’t have to mix them when I eat, right?”<br>\nThe Lord once again said : “Listen Moses, what I have to tell you is very simple : you shall not cook an animal’s meat into its mother’s milk, alright?”<br>\nMoses thought a bit, then answered : “Ok I think I got it this time! So you mean I shall use different plates for milk and for meat so I don’t mix them, and wait 6 hours after I eat any of them before eating the other, so they don’t get mixed in my stomach, is that better?”<br>\nAnd the Lord said : “You know what? Do as you want…”</p>\n</blockquote>\n<p>Now, even though this story is not biblical, I find it very nice and very true. Very often, the message of God is clear and simple. It’s clearer message is : “Love your God with all your self, and love your neighbour as youserlf”. Couldn’t be more simple. Yet it’s so difficult to apply it, that we want to change it so it fits our will, not His.</p>\n<p>Thus many believers have wondered : “Who is my neighbour?”. Well maybe my neighbour is only the one who lives close to me, and I don’t have to love others… Or maybe my neighbour is the one who looks like me, so if I’m white, I don’t have to love black people… Or maybe my neighbour is the one who has the same religion as me, so I don’t have to love people who do not have the same religion… Or maybe it’s not even about religions, but churches, so if I’m a catholic I don’t have to love protestants… Or maybe … [1]</p>\n<p>Some even go wondering “What is love anyway?”</p>\n<p>Yet the message is so clear…</p>\n<p>I loved this story because I for one believe most commandments given in the Bible were given only because men where like Moses in this story : they didn’t want to hear what God wanted them to do, so they tried to change the rules as much as possible, to get practical, easy to do rules for everyday life. It’s much easier to respect tons of small commandments than to dedicate your whole self to God and to love your neighbour as yourself!</p>\n<p>The will of God for us is simple and straight, and is aimed to our happiness. May His will be done!</p>\n<p>Amen.</p>\n<p>[1] note : the term that is used for “neighbour” in Hebrew actually means someone we don’t know yet. It can be your brother, your dad, your king, or a total stranger. It’s someone that will come and you don’t know who it is untill he/she comes.</p>"},{"url":"/posts/20060316-blogspot-adding-icons-to-debian-packages/","relativePath":"posts/20060316-blogspot-adding-icons-to-debian-packages.md","relativeDir":"posts","base":"20060316-blogspot-adding-icons-to-debian-packages.md","name":"20060316-blogspot-adding-icons-to-debian-packages","frontmatter":{"title":"Adding icons to Debian packages","template":"post","date":"2006-03-16T15:14:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/adding-icons-to-debian-packages.html","blogspot_url":"https://raphink.blogspot.com/2006/03/adding-icons-to-debian-packages.html","tags":["debian","english","packaging","restored"]},"html":"<h3>Rationale</h3>\n<p>So you packaged a nice app for Debian/Ubuntu… but there’s no icon, or the icon is horrible, or it’s missing some sizes…</p>\n<p>In short, you are in a situation in which you need to add icons to your package…</p>\n<p>Well the easy way would be to add png files directly in debian/ and dh_install them in /usr/share/icons/hicolor/fooxfoo/bar . Unfortunately, your package is not a Debian-native and the build fails because it can’t create the diff since it contains binary stuff! Too bad…</p>\n<p>Now let’s think about it… The idea in Debian package is that they are <strong>source</strong> packages. So just the way we now try to provide docbook/sgml manpages and build them in debian/rules, install the binary then clean it, why not do this with icons too?</p>\n<h3>What do you gain from doing so ?</h3>\n<ul>\n<li>You package is easier to maintain: just change the source and the binaries are generated from it at build and cleaned</li>\n<li>You don’t add binary stuff to your package</li>\n<li>You don’t have to use xpm icons</li>\n<li>You don’t have to add pngs directly in the source or make your package a Debian-native …</li>\n</ul>\n<h3>Now what is the option then?</h3>\n<p>Well looking around I found librsvg-bin (no idea why it’s named this way since it’s an exec, not a lib) which is a tool in main, allowing to convert svg files to png. There we are. Just as we provide docbook/sgml for manpages, let’s provide svg (which is xml, too) for icons!</p>\n<p>So we’re adding librsvg-bin to Build-Depends, putting our svg as debian/myapp.svg, and completing debian/rules. Now the problem I have is that I’d like to use a for loop to generate all icons but make won’t let me use bash script… So I’ll create a script file that I’ll name debian/buildicons.sh, with these contents (don’t forget to give it a chmod +x):</p>\n<h1>!/bin/bash</h1>\n<p>svgname=\"$1\"<br>\n[[ -z \"$2\" ]] &#x26;&#x26; section=\"apps\" || section=\"$2\"<br>\n[[ -z \"$3\" ]] &#x26;&#x26; maxres=\"128\" || maxres=\"$3\"  </p>\n<p>pngname=\"`basename ${svgname} .svg`.png\"  </p>\n<p>for resol in 16 22 32 48 64 128<br>\ndo<br>\nif [[ \"$resol\" -le \"$maxres\" ]]<br>\nthen<br>\nicondir=\"debian/icons/hicolor/${resol}x${resol}/${section}\"<br>\nmkdir -p \"$icondir\"<br>\nrsvg-convert -h \"$resol\" -w \"$resol\"  \"debian/${svgname}\" -o \"${icondir}/${pngname}\"<br>\nfi<br>\ndone<br>\nicondir=”debian/icons/hicolor/scalable/${section}”<br>\nmkdir -p “$icondir”<br>\ngzip -9 \"debian/${svgname}\" -c > “${icondir}/${svgname}z”  </p>\n<p>We’re using an argument in this script for the program name so it can be ported and used in other packages.<br>\nNote the last lines, that are aimed to gzipping and installing the svg file in /usr/share/icons/hicolor/scalable/.</p>\n<p>We’ll call this script from within debian/rules with the following:</p>\n<p>build/mypackage::<br>\ndebian/buildicons.sh myapp.svg apps  </p>\n<p>Then we’ll install the icons with:</p>\n<p>install/mypackage::<br>\ndh_install debian/icons/* usr/share/icons</p>\n<p>Then finally clean the build:</p>\n<p>clean::<br>\nrm -rf debian/icons</p>\n<p>Note: I reckon it’s kind of dirty to externalize debian/buildicons.sh as I propose to do. If anyone has a proposal to make it part of debian/rules cleanly, I’ll be happy to hear it <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_biggrin.gif\" alt=\":D\"></p>"},{"url":"/posts/20060325-blogspot-3760-2006-yhwh/","relativePath":"posts/20060325-blogspot-3760-2006-yhwh.md","relativeDir":"posts","base":"20060325-blogspot-3760-2006-yhwh.md","name":"20060325-blogspot-3760-2006-yhwh","frontmatter":{"title":"© -3760 - 2006 YHWH","template":"post","date":"2006-03-25T15:12:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/3760-2006-yhwh.html","blogspot_url":"https://raphink.blogspot.com/2006/03/3760-2006-yhwh.html","tags":["bible","english","god","licenses","restored"]},"html":"<p>I have been wanting to provide new French Bibles to the <a href=\"http://web.archive.org/web/20060806044342/http://crosswire.org/sword\">Sword Project</a> lately. The main versions used in France currently are the TOB (Traduction Oecuménique de la Bible), the NSB (Nouvelle Bible de Segond) and the BJ (Bible de Jérusalem). But all these versions are … copyrighted!<br>\n<img src=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-content/uploads/2006/03/Copyright_symbol_3.gif\" alt=\"Copyright_symbol_3.gif\"><br>\nYes, even the Word of God gets to be copyrighted now, and can’t be quoted or shared freely. Some copyrights are even a bit weird. For example, the BDS (Bible du Semeur) can be used just as long as the program doesn’t allow the user to see or extract more than 500 verses at a time. I believe this is the same for the NIV, too.</p>\n<p>Don’t get me wrong though, I really think the work of translators and publishers should be rewarded, but I do not think that implies preventing people from sharing the Word of God freely.</p>\n<p>Some programs manage to distribute these versions of the Bible, following the restrictions given by the publishers. Some even get to distribute versions with a key that has to be bought to decode the module… and I doubt Peter is the one selling the key <img src=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-includes/images/smilies/icon_wink.gif\" alt=\";)\"></p>\n<p>If any translator and/or editor reads this blog, <strong>I urge them to remember that the Word of God is a gift from Him, and to free it from these copyrights</strong>.</p>\n<p><em>“Freely you have received, freely give” (Matt 10:8)</em></p>"},{"url":"/posts/20060325-blogspot-need-e9n-on-some-a10n/","relativePath":"posts/20060325-blogspot-need-e9n-on-some-a10n.md","relativeDir":"posts","base":"20060325-blogspot-need-e9n-on-some-a10n.md","name":"20060325-blogspot-need-e9n-on-some-a10n","frontmatter":{"title":"Need an e9n on some a10n ?","template":"post","date":"2006-03-25T15:10:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/need-e9n-on-some-a10n.html","blogspot_url":"https://raphink.blogspot.com/2006/03/need-e9n-on-some-a10n.html","tags":["computers","english","restored"]},"html":"<p>Ever wondered why we use i18n and l10n and what’s the difference ? Well I have been wondering this many times…</p>\n<p>Maybe it would be some kind of geek code, like the first letter would be a version number and the number that follows would be something like :</p>\n<p>number=(-8/3)*letter_number + 42  </p>\n<p>and then finishing with an n just for fun. So versions j and k would have disappeared in outer space - probably cause they were not exact… Who would like a j15.333333333333333…n and a k12.66666666666666…n ? - and everything would end up with a z(-54)n ….</p>\n<p>Very unlikely, heh? So I was wondering … Till yesterday I got the answer, which is obviously much simpler…</p>\n<p>Long words are abbreviated by keeping the first and the last letter, and inserting in the middle the number of letters between these two extreme ones. So very simply :</p>\n<p>i18n = i[nternationalizatio]n, with 18 letters between the i and the n<br>\nl10n = l[ocalizatio]n, with 10 letters between the l and the n<br>\nv11n = v[ersificatio]n, with 11 letters between the v and the n</p>\n<p>Easy and efficient… As Marry Poppins used to say, it’s just s30o!</p>"},{"url":"/posts/20060327-blogspot-testing-kde-352-in-dapper/","relativePath":"posts/20060327-blogspot-testing-kde-352-in-dapper.md","relativeDir":"posts","base":"20060327-blogspot-testing-kde-352-in-dapper.md","name":"20060327-blogspot-testing-kde-352-in-dapper","frontmatter":{"title":"Testing KDE 3.5.2 in Dapper","template":"post","date":"2006-03-27T16:09:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/03/testing-kde-352-in-dapper.html","blogspot_url":"https://raphink.blogspot.com/2006/03/testing-kde-352-in-dapper.html","tags":["beta","english","kde","kubuntu","restored","test"]},"html":"<p><img src=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-content/uploads/2006/03/49100_436138_big.gif\" alt=\"KDE Logo\">KDE 3.5.2 is (almost) out <img src=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"> But Dapper is in UVF… so getting it in is not so easy, although it’s mostly a bugfix.</p>\n<p>Therefore, Jonathan Riddell has packaged it and put on kubuntu.org for both Breezy and Dapper. In order to install it for Dapper, add :</p>\n<p>deb <a href=\"http://kubuntu.org/packages/kde352\">http://kubuntu.org/packages/kde352</a> dapper main<br>\ndeb-src <a href=\"http://kubuntu.org/packages/kde352\">http://kubuntu.org/packages/kde352</a> dapper main  </p>\n<p>to your sources.list, run `sudo apt-get update &#x26;&#x26; sudo apt-get dist-upgrade` and restart KDE.</p>\n<p>I strongly encourage Kubuntu Dapper users to test it and report bugs. If it really fixes lots of bugs and is stable enough, we might get it in Dapper and finish polishing Dapper with KDE 3.5.2, the 6 weeks delay given us enough time to include it properly.</p>"},{"url":"/posts/20060330-blogspot-konqui-kubuntu-artwork/","relativePath":"posts/20060330-blogspot-konqui-kubuntu-artwork.md","relativeDir":"posts","base":"20060330-blogspot-konqui-kubuntu-artwork.md","name":"20060330-blogspot-konqui-kubuntu-artwork","frontmatter":{"title":"Konqui & Kubuntu artwork","template":"post","date":"2006-03-30T16:09:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/03/konqui-kubuntu-artwork.html","blogspot_url":"https://raphink.blogspot.com/2006/03/konqui-kubuntu-artwork.html","tags":["artwork","english","kde","konqui","kubuntu","restored"]},"html":"<p><a href=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-content/uploads/2006/03/konqi_kubudoc_hires.jpg\" title=\"Konqui reading\"><img src=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-content/uploads/2006/03/kq_kub_doc_5.jpg\" alt=\"Konqui reading\"></a><br>\nAs I’ve been searching for Konqui on Google Images and it wasn’t giving me many results, I finally landed on a nice website featuring Konqui art… and even short animation movies!</p>\n<p>Great work! Enjoy : <a href=\"http://web.archive.org/web/20060806044342/http://www.kulma.org/linux/kde/\">http://www.kulma.org/linux/kde/</a> !</p>\n<p>Edit : As I’ve found this very cute Konqui reading a Kubuntu book, I’m wondering if it couldn’t be nice to include it for the help section, or for Ktip … Kwwii what’s your opinion if you read this blog entry?</p>"},{"url":"/posts/20060402-blogspot-windows-hasta-la-vista/","relativePath":"posts/20060402-blogspot-windows-hasta-la-vista.md","relativeDir":"posts","base":"20060402-blogspot-windows-hasta-la-vista.md","name":"20060402-blogspot-windows-hasta-la-vista","frontmatter":{"title":"Windows Hasta La Vista!","template":"post","date":"2006-04-02T14:58:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/04/windows-hasta-la-vista.html","blogspot_url":"https://raphink.blogspot.com/2006/04/windows-hasta-la-vista.html","tags":["restored","windows"]},"html":"<p>One day late, but still enjoying this nice story from DistroWatch : <a href=\"http://web.archive.org/web/20060806044342/http://distrowatch.com/dwres.php?resource=review-winvista\">Hasta La Vista, Baby!</a></p>"},{"url":"/posts/20060331-blogspot-changing-styles/","relativePath":"posts/20060331-blogspot-changing-styles.md","relativeDir":"posts","base":"20060331-blogspot-changing-styles.md","name":"20060331-blogspot-changing-styles","frontmatter":{"title":"Changing styles","template":"post","date":"2006-03-31T16:08:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/03/changing-styles.html","blogspot_url":"https://raphink.blogspot.com/2006/03/changing-styles.html","tags":["english","joke","kubuntu","restored","style"]},"html":"<p>Kubuntu Dapper dist-upgraders have probably noticed that <a href=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-content/uploads/2006/03/capture2.jpg\" title=\"capture2.jpg\">our Kubuntu style</a> is progressively changing to fit Ubuntu’s wonderful orange theme. Yes, we’ve decided this was more beautiful after all, and Konqui couldn’t help but to begin painting the wallpaper already!</p>\n<p>Happy April Fool’s Day!</p>"},{"url":"/posts/20060401-blogspot-kubuntu-dapper-flight-6-is-out/","relativePath":"posts/20060401-blogspot-kubuntu-dapper-flight-6-is-out.md","relativeDir":"posts","base":"20060401-blogspot-kubuntu-dapper-flight-6-is-out.md","name":"20060401-blogspot-kubuntu-dapper-flight-6-is-out","frontmatter":{"title":"Kubuntu Dapper Flight 6 is out!","template":"post","date":"2006-04-01T16:07:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/04/kubuntu-dapper-flight-6-is-out.html","blogspot_url":"https://raphink.blogspot.com/2006/04/kubuntu-dapper-flight-6-is-out.html","tags":["beta","english","kubuntu","linux","release","restored","ubuntu"]},"html":"<p>Thanks to Mithrandir, Kubuntu Dapper Flight 6 has been released, at the same time as the Ubuntu version.</p>\n<p>It features KDE 3.5.2 &#x26; the new Crystal look. Currently in universe, getting their way to main are knetworkmanager (an app using network-manager to help dealing with multiple networks), kpowersave (a great replacement for klaptop), kerry (a beagle client for KDE) and more to come <img src=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p>If you are an advanced user and still on Breezy, this is yet another chance for you to take the step to Dapper and help testing and making Kubuntu 6.06 rock hard!</p>\n<p>You can dowload Kubuntu Dapper Flight 6 <a href=\"http://web.archive.org/web/20060806044342/http://cdimage.ubuntu.com/kubuntu/releases/dapper/flight-6/\">on this page</a>.</p>"},{"url":"/posts/20060401-blogspot-pure-nerd/","relativePath":"posts/20060401-blogspot-pure-nerd.md","relativeDir":"posts","base":"20060401-blogspot-pure-nerd.md","name":"20060401-blogspot-pure-nerd","frontmatter":{"title":"Pure nerd!","template":"post","date":"2006-04-01T16:04:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/04/pure-nerd.html","blogspot_url":"https://raphink.blogspot.com/2006/04/pure-nerd.html","tags":["english","geek nerd","restored","test"]},"html":"<p>Oh what a surprise <img src=\"http://web.archive.org/web/20060806044342/http://raphink.info/blog/wp-includes/images/smilies/icon_wink.gif\" alt=\";)\"></p>\n<p>Just having fun taking the test jpatrick mentionned <a href=\"http://web.archive.org/web/20060806044342/http://jpatrick.wordpress.com/2006/02/23/the-nerd-geek-or-dork-test/\">on his blog</a>. My results are :</p>\n<h3>Pure Nerd</h3>\n<p>78 % Nerd, 34% Geek, 47% Dork</p>\n<p>For The Record:</p>\n<p>A Nerd is someone who is passionate about learning/being smart/academia.<br>\nA Geek is someone who is passionate about some particular area or subject, often an obscure or difficult one.<br>\nA Dork is someone who has difficulty with common social expectations/interactions.<br>\nYou scored better than half in Nerd, earning you the title of: Pure Nerd.</p>\n<p>The times, they are a-changing. It used to be that being exceptionally smart led to being unpopular, which would ultimately lead to picking up all of the traits and tendences associated with the “dork.” No-longer. Being smart isn’t as socially crippling as it once was, and even more so as you get older: eventually being a Pure Nerd will likely be replaced with the following label: Purely Successful.</p>\n<p>Congratulations!</p>\n<p>What are you? <a href=\"http://web.archive.org/web/20060806044342/http://www.okcupid.com/tests/take?testid=9935030990046738815\">Take the test!</a></p>"},{"url":"/posts/20060402-blogspot-wonderful-work-lord/","relativePath":"posts/20060402-blogspot-wonderful-work-lord.md","relativeDir":"posts","base":"20060402-blogspot-wonderful-work-lord.md","name":"20060402-blogspot-wonderful-work-lord","frontmatter":{"title":"Wonderful Work, Lord","template":"post","date":"2006-04-02T14:59:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/04/wonderful-work-lord.html","blogspot_url":"https://raphink.blogspot.com/2006/04/wonderful-work-lord.html","tags":["beauty","english","god","restored"]},"html":"<p>As I was driving back to Paris tonight, after seeing my grand-mother on the way back, I saw a very nice rainbow on the side. It was a wide and beautiful one, although not complete. Taking its root in a little wood in the middle of the country, somewhere to the right of the highway, it was growing in a slight veil of rain, then dying in a black long cloud that was over the forest. Going up, the cloud turned whiter and whiter, finishing with nice round shapes, and a bright blue sky!</p>\n<p>I kept looking at it as I drove, amazed with its beauty. By the time I could not see it, I turned my head to the left and there was a beautiful sunset that was turning the whole sky to a bright orange, with powerful rays trying to go through the little clouds here and there, behind which the sun was hiding.</p>\n<p>Great work Lord, I’ll be back to your drive-in for more thrilling episodes!</p>"},{"url":"/posts/20060412-blogspot-reactos-gnus-not-windows-either/","relativePath":"posts/20060412-blogspot-reactos-gnus-not-windows-either.md","relativeDir":"posts","base":"20060412-blogspot-reactos-gnus-not-windows-either.md","name":"20060412-blogspot-reactos-gnus-not-windows-either","frontmatter":{"title":"ReactOS : GNU’s not Windows either…","template":"post","date":"2006-04-12T14:57:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/04/reactos-gnus-not-windows-either.html","blogspot_url":"https://raphink.blogspot.com/2006/04/reactos-gnus-not-windows-either.html","tags":["linux","open-source","restored","windows"]},"html":"<p>Although I have known the <a href=\"http://web.archive.org/web/20060806044342/http://www.reactos.org/\">ReactOS</a> project for quite a time, I have never really considered it greatly so far. Actually, I agree very much with <a href=\"http://web.archive.org/web/20060806044342/http://aseigo.blogspot.com/2004/12/how-to-kill-open-source-on-desktop.html\">Aaron Seigo</a> on the fact that porting more and more great FLOSS to Windows serves Microsoft more than the FLOSS movement.</p>\n<p>But then something hit me: when GNU was first created, the aim was to replace the UNIX system piece by piece, so as to end up with a fully open-source OS. I don’t think it would have really worked if RMS had begun by replacing things on the top of UNIX instead of beginning with the core: a C compiler and an editor.</p>\n<p>This is why I have come to wonder if ReactOS might be a way to go after all, allowing people to run both FLOSS and proprietary software for Windows natively, on an open-source OS, replacing the core of Windows with an open-source one. I don’t think I would use it, because I’m very happy using only FLOSS on top of GNU/Linux, but it could be part of a transition towards open-source for many users, something of that taste:</p>\n<ul>\n<li>Step 1 : a user on Windows with proprietary software</li>\n<li>Step 2 : a user on Windows with his favourite proprietary software and a few FLOSS, like Firefox, OOo, etc.</li>\n<li>Step 3 : the user switches to ReactOS, keeps using his favourite proprietary software and gets access to more FLOSS</li>\n<li>Step 4 : the user switches to a GNU/${your_favourite_kernel_here} and uses FLOSS only</li>\n</ul>\n<p>This would make the Windows-to-GNU transition much smoother, just as long as there are not too many great FLOSS on Windows that would keep them using it, as Aaron rightly pointed out.</p>"},{"url":"/posts/20060512-blogspot-linuxtag-2006/","relativePath":"posts/20060512-blogspot-linuxtag-2006.md","relativeDir":"posts","base":"20060512-blogspot-linuxtag-2006.md","name":"20060512-blogspot-linuxtag-2006","frontmatter":{"title":"LinuxTag 2006","template":"post","date":"2006-05-12T14:53:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/05/linuxtag-2006.html","blogspot_url":"https://raphink.blogspot.com/2006/05/linuxtag-2006.html","tags":["conference","english","germany","kubuntu","linux","restored","talk","ubuntu"]},"html":"<p>Last week was LinuxTag in Wiesbaden, Germany. This was a great opportunity to get to meet developers, talk about Ubuntu/Kubuntu, get feedback on our work, discover new technologies that we might want to get in Ubuntu, and just spend a nice time.</p>\n<p>Most of the main Kubuntu contributors were present: Jonathan Riddell who came on Saturday with Mark (sabdfl), Andreas Mueller (amu), Stephan Hermann (\\sh), Anthony Mercatante (Tonio_) who came on Saturday aswell,Kenneth Wimer (kwwii), Mirjam Wäckerlin (Zerlinna).</p>\n<p>It was very nice to be able to meet after so much time working together online, and we also enjoyed spending evenings together in restaurants and at social events (LinuxTag and KDE social events).</p>\n<p>I have to say I was very impressed by the Ubuntu presence there. There was an official Canonical booth, where I was able to meet Jeff Bailey (jbailey), Daniel Holbach (dholbach), Michael Vogt (mvo) and Oliver Grawert (ogra). There also was a community booth for Ubuntu, Edubuntu and Kubuntu, where we were staying. But in addition to this, most booths had either books on Ubuntu or computers running Ubuntu or Kubuntu. The KDE booth was running Kubuntu, VServer was running Ubuntu and Kubuntu, and most book stores had Ubuntu books as best sellers <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p>Mark Shuttleworth (sabdfl) arrived on Saturday, for the Ubuntu Love Day, and we began this day with a meeting gathering Kubuntu and KDE people. The goal was to try and find solutions to better work together, improve the communications between KDE and Kubuntu developers, link the Bug Tracking Systems, share our ideas of future developments. It was decided that the KDE community would appoint 4 or 5 people to come and participate in the Edgy meeting in Paris in the end of June, when the a Kubuntu Technical Board will be elected.</p>\n<p>After this meeting, Mark gave a talk on Ubuntu, and confirmed his commitment to Kubuntu, by wearing a KDE t-shirt <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"> The room was full, and most people had to stand to fit in for the talk, although two rooms had been gathered by removing the wall between them.</p>\n<p>Right after that, I gave a <a href=\"http://web.archive.org/web/20060805074200/http://www.raphink.info/talks/2006/05-LinuxTag\">talk on Kubuntu</a>, focusing on the new features in Dapper.</p>\n<p>Pictures of the LinuxTag 2006 taken by several Kubuntu contributors can be found on <a href=\"http://web.archive.org/web/20060805074200/http://gallery.raphink.info/main.php?g2_itemId=5046\">my gallery</a>. There is also a video made by Ken <a href=\"http://web.archive.org/web/20060805074200/http://www.raphink.info/files/videos/lt.ogg\">here</a>.</p>\n<p>This time in Wiesbaden ended in a cute restaurant in the center of Wiesbaden.<br>\nLooking forward to the meeting in Paris!</p>"},{"url":"/posts/20060418-blogspot-feet-in-water-wind-in-face-eggs-in/","relativePath":"posts/20060418-blogspot-feet-in-water-wind-in-face-eggs-in.md","relativeDir":"posts","base":"20060418-blogspot-feet-in-water-wind-in-face-eggs-in.md","name":"20060418-blogspot-feet-in-water-wind-in-face-eggs-in","frontmatter":{"title":"Feet in the water, Wind in the face, & Eggs in the pockets","template":"post","date":"2006-04-18T14:55:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/04/feet-in-water-wind-in-face-eggs-in.html","blogspot_url":"https://raphink.blogspot.com/2006/04/feet-in-water-wind-in-face-eggs-in.html","tags":["english","france","life","restored"]},"html":"<p>We had a wonderful Easter weekend with my family! We went to visit some friends of us in Normandy that we hadn’t seen for a long time, and it was really great! We don’t often spend time altogether with my parents and sister, and it’s very nice when we get to do it.<br>\n<a href=\"http://web.archive.org/web/20060805074200/http://gallery.raphink.info/main.php?g2_itemId=35\" title=\"Ahhhhhhhh! Wiiiiiiiiind!\"><img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-content/uploads/2006/04/web_HPIM3160.thumbnail.JPG\" alt=\"Ahhhhhhhh! Wiiiiiiiiind!\"></a><br>\nWe got there with my parents on Friday evening, and my sister Delphine joined us on Saturday evening. The first thing we did when Delphine got there was to go to the sea and enjoy the fury of the wind mixed with the a cold rain full of salt. We could hardly stand up with the wind, and the rain was getting everywhere. We had to scream to get heard, but it was really great, and we got enough fresh air for a few days. I’m closing my eyes on this pic, but it wouldn’t change much since all the drops on my glasses prevented me from seeing anyway <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_wink.gif\" alt=\";)\"><br>\n<a href=\"http://web.archive.org/web/20060805074200/http://gallery.raphink.info/main.php?g2_itemId=155\" title=\"Under the waterfall\"><img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-content/uploads/2006/04/web_HPIM3230.thumbnail.JPG\" alt=\"Under the waterfall\"></a><br>\nOn Sunday, other friends joined us. After tracking eggs in the house for quite a time - Delphine had prepared a game to search for them all around -, we went hiking to a nice place with waterfalls. We had a lot of fun there, climbing, chatting &#x26; singing. My parents knew these friends in a choir, years ago, and music is one of the most important things we have in common <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"><br>\n<a href=\"http://web.archive.org/web/20060805074200/http://gallery.raphink.info/main.php?g2_itemId=362\" title=\"Shapes dancing in the bay\"><img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-content/uploads/2006/04/web_HPIM3320.thumbnail.JPG\" alt=\"Shapes dancing in the bay\"></a><br>\nOn Monday, our last day there, we went for a long hike to the <a href=\"http://web.archive.org/web/20060805074200/http://en.wikipedia.org/wiki/Mont_Saint_Michel\">Mont St Michel</a>. This is a very touristic place in the middle of a bay, and full of people in Easter. Now what is great is to go there on foot, crossing the bay with your bare feet, walking in the mud and sand for 7 km and back, crossing cold rivers up to the hips, jumping on moving sands as on a trampoline. We had a very nice and funny guide to cross the bay, and the weather was really great although still a bit cold, so we enjoyed our day to the fullest.</p>\n<p>Overall, it was really a great time, and a nice breath of fresh air before diving into real life again <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20060614-blogspot-towards-new-life-from-poitiers-to/","relativePath":"posts/20060614-blogspot-towards-new-life-from-poitiers-to.md","relativeDir":"posts","base":"20060614-blogspot-towards-new-life-from-poitiers-to.md","name":"20060614-blogspot-towards-new-life-from-poitiers-to","frontmatter":{"title":"Towards a new life: from Poitiers to Vallauris","template":"post","date":"2006-06-14T14:51:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/06/towards-new-life-from-poitiers-to.html","blogspot_url":"https://raphink.blogspot.com/2006/06/towards-new-life-from-poitiers-to.html","tags":["english","god","job","life","restored"]},"html":"<p>The last couple of weeks have been very busy for me, as lots of things have happened, and now it’s time to sit and give a few news on what’s going on I guess…</p>\n<p>In the end of April, I went to an IT employment meeting. There I met a few companies interested in my profile, including a small services company (SSII) in Paris, which was really eager to hiring me for my Linux skills. During my week in Germany, I got a lot of phone calls for various jobs. Eventually, I got hired as a Linux System Engineer in Sophia Antipolis, on the French Riviera.</p>\n<p>I happen to have some family there : a cousin I had never met before. I got in touch with him and everything went smooth since. I found a little house to rent in Vallauris in no time, 10 minutes from Sophia Antipolis, 10 minutes from the sea…</p>\n<p>I also began to attend the Calvary Chapel community in Nice, and found there a very nice community to share and hang out with.</p>\n<p>The weather here is beautiful, and I enjoyed the past weeks I had, when I didn’t have to work yet, and went hiking and took pictures all around. This place is really great because it’s close to both the sea and the moutains. I really love hiking in the moutains and I look forward to taking a few days off to go hike with my bag on my back <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_wink.gif\" alt=\";)\"></p>\n<p>Eventualy, I’m beginning to work tomorrow, and trusting everything will be fine, as it’s all in God’s hands <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20060614-blogspot-christianforums-irc-is-back-online/","relativePath":"posts/20060614-blogspot-christianforums-irc-is-back-online.md","relativeDir":"posts","base":"20060614-blogspot-christianforums-irc-is-back-online.md","name":"20060614-blogspot-christianforums-irc-is-back-online","frontmatter":{"title":"Christianforums IRC is back online :)","template":"post","date":"2006-06-14T14:52:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/06/christianforums-irc-is-back-online.html","blogspot_url":"https://raphink.blogspot.com/2006/06/christianforums-irc-is-back-online.html","tags":["christian","english","god","irc","restored"]},"html":"<p>After several month, the Christianforums (<a href=\"http://web.archive.org/web/20060805074200/http://www.christianforums.com/\">http://www.christianforums.com</a>) IRC chat server is back online. You can connect to it with the following data :</p>\n<p>server : irc.christianforums.com<br>\nport : 7777<br>\nmain channel : #christianforums</p>\n<p>Join us there <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"><br>\n&#x3C;>&#x3C;</p>"},{"url":"/posts/20060512-blogspot-paris-capitale-du-libre/","relativePath":"posts/20060512-blogspot-paris-capitale-du-libre.md","relativeDir":"posts","base":"20060512-blogspot-paris-capitale-du-libre.md","name":"20060512-blogspot-paris-capitale-du-libre","frontmatter":{"title":"Paris, capitale du Libre","template":"post","date":"2006-05-12T14:54:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/05/paris-capitale-du-libre.html","blogspot_url":"https://raphink.blogspot.com/2006/05/paris-capitale-du-libre.html","tags":["conference","english","français","libre","linux","open-source","paris","restored"]},"html":"<p><em>Paris, capital city of the Libre</em></p>\n<p>C’est le titre bien pompeux que la Mairie de Paris s’auto-attribuera le 26 juin 2006. Les réactions vont bon train sur les forums depuis plusieurs jours à ce sujet, alors que cet événement partage la communauté française du Logiciel Libre.</p>\n<p><em>This is the title that Paris will elect for itself on the 26 of June, 2006. Many reactions can be read on the forums since a few days, as this event splits the French community of Free Software.</em></p>\n<p>Au-delà du <a href=\"http://web.archive.org/web/20060805074200/http://linuxfr.org/2006/05/09/20792.html\">mécontentement</a> causé par le choix fort peu judicieux de cette appellation abusive, je suis particulièrement choqué par le caractère fermé de cette manifestation, qui a pourtant pour ambition de devenir un rendez-vous incontournable du Logiciel Libre et de ses ministres en France.</p>\n<p><em>Beyond the unhappiness caused by the bad choice of this name, I am particularly shocked by the close aspect of this event, that claims to become a major one for Libre Software and its followers in France.</em></p>\n<p>On lit sur le site web aux accents de réunion d’anciens combatants (<a href=\"http://web.archive.org/web/20060805074200/http://www.paris-libre.org/\">http://www.paris-libre.org</a>) que cet événement franco-français verra se réunir <strong>les</strong> 100 personnalités du Logiciel Libre (sic!) autour d’une petite sauterie dînatoire entre gens de bonne compagnie. Je suis donc particulièrement étonné d’apprendre qu’il y a en tout et pour tout 100 personnalités du Logiciel Libre et qu’elles se trouvent être toutes francophones, sinon françaises.</p>\n<p><em>It can be read on the web page, sounding like an old fighter meeting (Free Paris), that this very French event will gather <strong>the</strong> 100 characters of Libre Software (sic!) for a nice diner among good-company people. I am thus very estonished to learn that there are 100 characters of Libre Software in total, and that they happen to all be french-speaking, if not French at all.</em></p>\n<p>Ne partant pas vaincu pour autant et souhaitant donner sa chance à une manifestation qui somme toute participe de la démocratisation - si une telle chose est concevable - du LL en France, j’ai pensé qu’il serait souhaitable qu’Ubuntu soit représenté à cette occasion. J’ai donc été jeter un coup d’œil aux possibilités de présence des associations à cette manifestation. Mal m’en a pris! On apprend sur <a href=\"http://web.archive.org/web/20060805074200/http://www.paris-libre.org/index.php?option=com_content&#x26;task=view&#x26;id=20&#x26;Itemid=46\">la page dédiée au partenariats</a> qu’un stand simple de 6m2 pour la journée revient en location à 2500 € HT. Bien entendu, la Mairie de Paris a parfaitement conscience que ces tarifs exhorbitants sont totalement prohibitifs pour les associations et propose donc une réduction exceptionnelle de 50% pour les membres ASS2L et les associations, soit 1250€ HT la journée pour un stand de 6m2 ! Vous l’aurez compris, les communautés du Libre sont <em>de facto</em> exclues de cette journée réunissant les acteurs principaux du LL en France.</p>\n<p><em>I still wanted to gave it a chance and thought Ubuntu could well be present for this event. So I had a look at the possibilities for associations to have a booth there, and learned that a 6m2 booth would cost 2500€ for the day. Of course, the organizators are perfectly aware that this price is far too expensive for any association and proposes an exceptionnal reduction of 50% for them, so that the booth would only cost 1250€ for the day! This is obvious: communities of Libre Software are excluded from this event that is meant to gather the main actors of the Libre Software in France.</em></p>\n<p>Cette journée sera par ailleurs couronnée par la remise de trophées, habilement nommés “Linus d’Or” - car, c’est bien connu, Linus Torvalds est la figure de référence du Libre -. Ces Linus d’Or récompenseront des projets, des développeurs, des entreprises qui auront su se démarquer dans leur participation au Libre. Ils ne seront bien entendu décernés qu’aux projets qui auront au préalable rempli un dossier de candidature, intégralement rédigé en français comme il se doit. Autant dire que nombre de projets majeurs seront oubliés par le seul fait de ce critère linguistique arbitraire.</p>\n<p><em>This day will be finished by a competition, named “Linus d’Or” (Golden Linus) - for, this is a well-known fact, Linus Torvalds is a main figure in Libre Software -. These Linus d’Or will elect projects, developers, companies that will have brought interesting contributions to Libre Software. Of course these people will have to file a folder to apply, entirely writting in French, so that most major projects won’t be taken in consideration thanks to this linguistic criteria.</em></p>\n<p>Mon principal regret quand à cet événement est donc l’incompréhension évidente du monde du Libre par ses organisateurs, alors même que cette journée semble dédiée à la démystification des Logiciels Libres. Je m’attriste que ces gens puissent penser servir le Logiciel Libre en organisant des cocktails entre ministres et délégués de la FSF, sans même s’inquiéter d’inviter les principaux acteurs du LL : les développeurs et les associations d’utilisateurs.</p>\n<p><em>My main concern about this event is that the organizator seem to not understand at all the world of Libre Software, eventhough they dedicate this day to understanding it. I am sad that these people might think of serving the Libre Software by organizing cocktails among ministers and delegates from the FSF, without even thinking of inviting the main actors of the Libre Software: developers and user associations.</em></p>\n<p>Si vous avez la chance de vous rendre à cet événement de haute volée, je vous souhaite donc une agréable journée dans la capitale virtuelle d’un Libre instrumentalisé !</p>\n<p><em>If you get to go to this event, I wish you a nice day in the virtual city of an instrumentalized Libre!</em></p>"},{"url":"/posts/20060707-blogspot-avertissement-cet-article-contient-des/","relativePath":"posts/20060707-blogspot-avertissement-cet-article-contient-des.md","relativeDir":"posts","base":"20060707-blogspot-avertissement-cet-article-contient-des.md","name":"20060707-blogspot-avertissement-cet-article-contient-des","frontmatter":{"title":"Dogville : un Dieu gangster ?","template":"post","date":"2006-07-07T14:24:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/07/avertissement-cet-article-contient-des.html","blogspot_url":"https://raphink.blogspot.com/2006/07/avertissement-cet-article-contient-des.html","tags":["chrétien","film","français","restored"]},"html":"<p>Avertissement:</p>\n<p>Cet article contient des révélations sur l’intrigue du film Dogville.</p>\n<p>Ce film contient des scènes de violence et de nudité qui peuvent heurter. Lors des projections publiques, des personnes ont quitté les salles.</p>\n<p><img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-content/uploads/2006/07/dogville.jpg\" alt=\"Affiche\"></p>\n<p>Renversant !</p>\n<p>Lars von Trier nous livre ici une interprétation très personnelle de l’Évangile, qui interroge et qui choque.</p>\n<p>Quelle ne fut pas ma surprise pourtant quand, cherchant des critiques de ce film, je ne trouvai aucune mention faite au christianisme. Les <a href=\"http://web.archive.org/web/20060805074200/http://pserve.club.fr/Dogville.html\">critiques francophones</a> de ce film se contentent d’y voir une mise en boite de l’Amérique rurale, dans la continuité de Dancer in the Dark, préfigurant selon ces auteurs le début d’une série d’oeuvres centrées sur les États-Unis. Ils y voient également l’influence de Berthold Brecht dans la nudité des décors et la simplicité de la mise en scène. S’il est vrai que le film a été entièrement tourné dans un hangar, avec des décors principalement tracés à la craie sur le sol, c’est visiblement pour mieux mettre le spectateur dans une position omnisciente, divine, que l’auteur fait ce choix. Et malgré tous les indices flagrant d’une inspiration chrétienne, aucun rapprochement à la foi n’est fait dans ces commentaires.</p>\n<p>Pourtant nul n’est besoin de chercher loin pour trouver les références à la Bible dans ce film, puisque même les noms des personnages s’y réfèrent : l’héroïne, figure christique par excellence, se nomme Grâce, et le chien du village, Moïse.</p>\n<p>C’est finalement sur un site anglophone, <a href=\"http://web.archive.org/web/20060805074200/http://www.christianitytoday.com/movies/reviews/dogville.html\">Christianity Today</a>, que je trouvai une critique de ce film avec des yeux ouverts sur son message, bien que je ne partage pas totalement l’avis du critique, notamment en ce qui concerne la séparation qu’il fait entre le Dieu juge de l’Ancien Testament et le Dieu amour du Nouveau Testament.</p>\n<p>À l’apogée de cette passion, Lars von Trier enchaîne sur une apocalypse revisitée, où la figure christique de Grâce est taxée d’arrogance, et où ce personnage souffrant par et pour tous se transforme en juge sanguinaire, héritant tous pouvoirs sur la vie de son père gangster. La scène finale a lieu dans une voiture close, rideaux tirés, seul lieu du décor fermé aux regards, conciliabule divin après la trahison et la mise à mort de Grâce, avant la passation des pouvoirs omnipotents de son père sur le peuple qui l’a oppressée.</p>\n<p>Le film se finit sur l’image du chien Moïse. Ce personnage, tracé à la craie dans le décor depuis le début du film, et qui a vécu passivement les événements de l’intrigue sans jamais broncher, est épargné par Grâce, prenant soudain vie au milieu du village en flammes, seul à mériter le pardon divin.</p>\n<p>Une oeuvre magistrale, qui révèle beaucoup sur les croyances de son auteur.</p>"},{"url":"/posts/20060719-blogspot-christian-projects-on-sourceforge-1/","relativePath":"posts/20060719-blogspot-christian-projects-on-sourceforge-1.md","relativeDir":"posts","base":"20060719-blogspot-christian-projects-on-sourceforge-1.md","name":"20060719-blogspot-christian-projects-on-sourceforge-1","frontmatter":{"title":"Christian projects on Sourceforge #1","template":"post","date":"2006-07-19T14:22:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/07/christian-projects-on-sourceforge-1.html","blogspot_url":"https://raphink.blogspot.com/2006/07/christian-projects-on-sourceforge-1.html","tags":["christian","linux","open-source","restored","sourceforge"]},"html":"<p>Today I am having a look at the Christian open-source projects listed on <a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/\">Sourceforge</a>.<br>\nI’ll try to list them here, taking them in order of appearance when looking at the “Religion and Philosophy” section. I won’t be listing the programs that are obviously for Windows only, since I cannot test them.</p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/bibletime\">BibleTime</a></strong><br>\nThere’s no need to introduce BibleTime I believe. This great Bible study program for KDE is part of the Sword project, hosted by the <a href=\"http://web.archive.org/web/20060805074200/http://www.crosswire.org/\">Crosswire Bible Society</a>. Version 1.6 is currently in development, and we’ll be happy to update it in Ubuntu when it’s ready.</p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/openlp\">openlp.org</a></strong><br>\nI couldn’t find in what language this program is made… but surely not in any I know how to use on Linux, and the only binary that is given is for Windows, so I couldn’t try it… Any comments welcome.</p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/bibledit\">Bibledit</a></strong><br>\nThis piece of software was just added in Debian not long ago, and synced in Ubuntu very lately. It is aimed at editing Bible files, mainly to allow translating the Bible. I couldn’t really test it because it segfaults in Ubuntu Edgy. I’ll have to see if anything can be done for that.</p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/lds\">Lyricue</a></strong><br>\nMore and more churches use video-projectors to show the lyrics of songs on a screen. This is why lyricue exists, to allow them to use an open-source software to achieve this task. Currently, Lyricue requires a mysql DB to be set, which makes it a bit uneasy to use. Lyricue is not in Debian or Ubuntu yet, but packages exist on the website. I recently contacted the upstream developer about getting Lyricue in Ubuntu, and he said he will do that. Looking forward to uploading it <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/gnomesword\">GnomeSword</a></strong><br>\nGnomeSword is to GNOME what BibleTime is to KDE: a must. Although I prefer KDE over GNOME in general, I reckon GnomeSword is a very nice Bible study software.</p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/gbible\">GNU Bible (GBible)</a></strong><br>\nGBible is a Java-based Bible study software developed using HSQLDB, Lucene and Swing. It was formelly designed to use db.linux, GTK+, Glade and C. The locale focus is Portuguese(Brazil), using “Almeida Revista e Atualizada” version. I am a bit disappointed by the fact that the Bible it uses seems somehow hardcoded… I can’t see readable source files where the Bible is read, nor a way to add another Bible version to this program. Which means : “Read the Almeida Revista e Atualizada” portuguese version of the Bible, or give up on using this program… Hope to see a broader focus and some translations on this project <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/churchinfo\">ChurchInfo</a></strong><br>\nFrom what I understand, ChurchInfo is the future of the PHP/MySQL version of <a href=\"http://web.archive.org/web/20060805074200/http://www.infocentral.org/\">InfoCentral</a>, a web-based program to manage church communities. I have played a bit with it, and have found it interesting. The only thing I’m really concerned with are the menus on the top of the window: they don’t appear on my new version of Firefox, and they are not clickable on Konqueror… which makes navigating in the program simply … impossible. Otherwise I’ve heard very nice things about this app, and I plan to package it as soon as I can understand how to get the menus to work…</p>\n<p><strong><a href=\"http://web.archive.org/web/20060805074200/http://sourceforge.net/projects/opensong\">OpenSong</a></strong><br>\nAccording to its author, this ruby program seems to be aimed at “managing chords and lyrics sheets (lead sheets), presenting lyrics (and custom slides) using a projector, and much more!”. However, the source tarball contains a .rb file and an xml file, and the .rb file fails to launch with ruby1.8. I didn’t try to go further. I’d be happy if the author chose to provide an installer for his program.</p>"},{"url":"/posts/20060703-blogspot-dadvsi-est-pass-un-auteur-exprime-ses/","relativePath":"posts/20060703-blogspot-dadvsi-est-pass-un-auteur-exprime-ses.md","relativeDir":"posts","base":"20060703-blogspot-dadvsi-est-pass-un-auteur-exprime-ses.md","name":"20060703-blogspot-dadvsi-est-pass-un-auteur-exprime-ses","frontmatter":{"title":"DADVSI est passé, un auteur exprime ses craintes sur le futur des droits d’auteurs","template":"post","date":"2006-07-03T14:50:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/07/dadvsi-est-pass-un-auteur-exprime-ses.html","blogspot_url":"https://raphink.blogspot.com/2006/07/dadvsi-est-pass-un-auteur-exprime-ses.html","tags":["français","linux","open-source","politique","restored"]},"html":"<p>Dans le monde du libre, nous avons tous suivi avec inquiétude le débat autour de l’adoption du texte de loi DADVSI. Quelques jours après l’adoption du projet, Michaël Golberg fait le tour de ses conséquences désastreuses, invitant les auteurs pour qui la liberté a encore de la valeur a réagir en tentant de sauver la diversité au risque de la rentabilité.</p>\n<p>Un texte a découvrir sur <a href=\"http://web.archive.org/web/20060805074200/http://michael.goldberg.free.fr/balivernes/index.php?2006/06/15/7-chere-amie-lectrice-cher-ami-lecteur\">cette page</a>.</p>"},{"url":"/posts/20060710-blogspot-openclipart/","relativePath":"posts/20060710-blogspot-openclipart.md","relativeDir":"posts","base":"20060710-blogspot-openclipart.md","name":"20060710-blogspot-openclipart","frontmatter":{"title":"OpenClipart","template":"post","date":"2006-07-10T14:23:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/07/openclipart.html","blogspot_url":"https://raphink.blogspot.com/2006/07/openclipart.html","tags":["clipart","licenses","open-source","restored"]},"html":"<p>I was searching for Christian open-source clipart today. Unfortunately I didn’t find any. All the free Christian clipart I found have restrictive - or even <strong>very</strong> restrictive - licenses <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_sad.gif\" alt=\":(\"></p>\n<p>However, I have found one open-source clipart website - although not Christian - with very good things on it <a href=\"http://web.archive.org/web/20060805074200/http://www.openclipart.org/\">http://www.openclipart.org</a>.</p>\n<p>I strongly encourage all graphic developers to contribute to this project <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20060729-blogspot-string-edition-in-bash-replacements/","relativePath":"posts/20060729-blogspot-string-edition-in-bash-replacements.md","relativeDir":"posts","base":"20060729-blogspot-string-edition-in-bash-replacements.md","name":"20060729-blogspot-string-edition-in-bash-replacements","frontmatter":{"title":"String edition in bash: replacements","template":"post","date":"2006-07-29T14:18:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/07/string-edition-in-bash-replacements.html","blogspot_url":"https://raphink.blogspot.com/2006/07/string-edition-in-bash-replacements.html","tags":["bash","linux","restored","sysadmin"]},"html":"<p>Many of us are used to using sed for pattern replacements in strings, but it is also possible to perform these replacements from within bash, using ${parameter#word}, ${parameter##word}, ${parameter%word}, ${parameter%%word}, ${parameter/pattern/string/}, and ${parameter//pattern/string}.</p>\n<p>All these forms are explained in “man bash” but no example is given. Since an example is often the best way to show how things work, here are a few examples for these features. I will only give examples here, considering you can lookup for these entries in “man bash” if you want the full theorical explanation, that I find confusing.</p>\n<p>Let us define a global variable we will be working on:</p>\n<p><code>$ FQDN=\"foo.bar.com\" # let's define a variable called FQDN for our examples $ echo ${FQDN} # just a check foo.bar.com</code></p>\n<p>Now let’s have a look at ${parameter#word} and ${parameter##word}, which act on the beginning of the string:</p>\n<p><code>$ echo ${FQDN#*.} # this removes the shortest pattern matching \"*.\" in the beginning of $FQDN, so it removes \"foo.\" bar.com $ echo ${FQDN##*.} # this removes the longest pattern matching \"*.\" in the beginning of $FQDN, so it removes \"foo.bar.\" com</code></p>\n<p>${parameter%word} and ${parameter%%word} achieve quite the same, but on the end of the string:</p>\n<p><code>$ echo ${FQDN%.*} # this removes the shortest pattern matching \".*\" in the end of $FQDN, so it removes \".com\" foo.bar $ echo ${FQDN%%.*} # this removes the longest pattern matching \".*\" in the end of FQDN, so it removes \".bar.com\" foo</code></p>\n<p>Now it’ll be fairly easy to understand what ${parameter/pattern/string} and ${parameter//pattern/string} do… They are direct replacements for sed, respectively with and without the g option:</p>\n<p>Let’s define a new string for this one:</p>\n<p><code>$ TEST_STRING=\"foo is long and bar is long\" $ echo ${TEST_STRING} # just a check foo is long and bar is long</code></p>\n<p>and now to see what these variables do:</p>\n<p><code>$ echo ${TEST_STRING/long/short} # replaces the first occurence of \"long\" by \"short\" in $TEST_STRING foo is short and bar is long $ echo ${TEST_STRING//long/short} # replaces all occurences of \"long\" by \"short\" in $TEST_STRING, same as using g in sed foo is short and bar is short</code></p>\n<p>Enjoy and feel free to provide more examples you’d find useful as comments <img src=\"http://web.archive.org/web/20060805074200/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20060812-blogspot-hackers-and-pirates-disambiguation/","relativePath":"posts/20060812-blogspot-hackers-and-pirates-disambiguation.md","relativeDir":"posts","base":"20060812-blogspot-hackers-and-pirates-disambiguation.md","name":"20060812-blogspot-hackers-and-pirates-disambiguation","frontmatter":{"title":"Hackers and pirates: a disambiguation","template":"post","date":"2006-08-12T15:30:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/08/hackers-and-pirates-disambiguation.html","blogspot_url":"https://raphink.blogspot.com/2006/08/hackers-and-pirates-disambiguation.html","tags":["hacker","linux","restored","wikipedia"]},"html":"<p>Pushed by the medias, more and more people use the term “hacker” to talk about pirates and generally bad-willing computer freaks. As I consider myself a hacker, I am willing to give here a small disambiguation of this term.</p>\n<h3>What is a hacker?</h3>\n<p>The Chambers Pocket Dictionary defines a hacker as <em>someone who uses computers to gain unauthorized access to data, other computers, etc.</em>. This use of the term is actually deprecated (see <a href=\"http://web.archive.org/web/20061230082703/http://en.wikipedia.org/wiki/Hacker_definition_controversy#Jargon_File_definition\">this link</a>).</p>\n<p>I’ll rather use the <a href=\"http://web.archive.org/web/20061230082703/http://en.wikipedia.org/wiki/Hacker\">definition of the Wikipedia encyclopedia</a> instead, as I believe it is much closer to reality.<br>\nWikipedia lists 3 meanings of the word “hacker”:</p>\n<blockquote>\n<p>In computer programming, a hacker is a programmer who hacks or reaches a goal by employing a series of modifications to exploit or extend existing code or resources. For some, hacker has a negative connotation and refers to a person who “hacks” or uses kludges to accomplish programming tasks that are ugly, inelegant, and inefficient. This negative form of the noun “hack” is even used among users of the positive sense of “hacker”.</p>\n<p>In computer security, a hacker is a person who specializes in work with the security mechanisms for computer and network systems. While including those who endeavor to strengthen such mechanisms, it more often is used (especially in the mass media) to refer to those who seek access despite them.</p>\n<p>In other technical fields, hacker is extended to mean a person who makes things work beyond perceived limits through their own technical skill, such as a hardware hacker, or reality hacker.</p>\n</blockquote>\n<h3>Hacker and pirates: open-source vs. proprietary software</h3>\n<p>Ok so I gave a definition of what a hacker is, but then why do so many people call pirates hackers?</p>\n<p>As you probably understand, a hacker is someone who enjoys modifying computer programs. How is this illegal? It is illegal when the code is not yours. If the license of a program doesn’t allow you to modify it, then it is illegal to do so.</p>\n<p>Try to picture a man who is good with cars. If this man repairs and improves his own car, who will complain? On the contrary, if this man modifies the car of his neighbour without asking him the permission to do so, then it is surely not a good idea.</p>\n<p>It is the same with computer programs: modifying proprietary software is illegal because their rights are owned by physical or moral persons who do not allow anybody to alter them, whereas modifying <a href=\"http://web.archive.org/web/20061230082703/http://en.wikipedia.org/wiki/Open_source_software\">open-source software</a> is totally legal and even encouraged.</p>\n<h3>To sum it up</h3>\n<p>Most pirates are hackers because they modify computer programs, but they are illegal because they modify programs whose license doesn’t allow them to.</p>\n<p>Hackers in general - and GNU/Linux hackers in general - are not illegal, because they hack computers programs they have the right to modify.</p>"},{"url":"/posts/20060828-blogspot-ichthux-609-beta5-is-out/","relativePath":"posts/20060828-blogspot-ichthux-609-beta5-is-out.md","relativeDir":"posts","base":"20060828-blogspot-ichthux-609-beta5-is-out.md","name":"20060828-blogspot-ichthux-609-beta5-is-out","frontmatter":{"title":"Ichthux 6.09 beta5 is out","template":"post","date":"2006-08-28T15:31:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/08/ichthux-609-beta5-is-out.html","blogspot_url":"https://raphink.blogspot.com/2006/08/ichthux-609-beta5-is-out.html","tags":["christian","english","ichthux","kubuntu","linux","release","restored"]},"html":"<p><a href=\"http://web.archive.org/web/20061230082703/http://www.ichthux.com/\" title=\"Ichthux_BubbleFish_Mod_small_transparent.png\"><img src=\"http://web.archive.org/web/20061230082703/http://raphink.info/blog/wp-content/uploads/2006/08/Ichthux_BubbleFish_Mod_small_transparent.png\" alt=\"Ichthux_BubbleFish_Mod_small_transparent.png\"></a></p>\n<p>The Ichthux team is proud to announce the release of <strong><a href=\"http://web.archive.org/web/20061230082703/http://www.ichthux.com/\">Ichthux 6.09 beta5</a></strong>.</p>\n<h3>Release Notes</h3>\n<p><strong>Remember:</strong><br>\nThis is beta software and even though it should be pretty stable (we are building from <a href=\"http://web.archive.org/web/20061230082703/http://www.kubuntu.org/\">Kubuntu’s</a> 6.06 release) be aware that it could have problems. What we would like, at this point, is to have as many people try Beta5 out so that we can fix any remaining bugs before we realease Ichthux 6.09 some time in September.</p>\n<h3>Ichthux 6.09 beta5 features include</h3>\n<ul>\n<li>all Kubuntu 6.06.1 “Dapper Drake” features and applications plus,</li>\n<li>BibleTime Bible reader</li>\n<li>kio-sword for Bible reader integration in Konqueror</li>\n<li>several Bibles installed by default for English (KJV), Spanish (SPARV), French (LSG), Hebrew (WLC), Greek (TR), Korean (Korean), Italian (Itadio) , Arabic (Arasvd), Russian (RST), and German (Gerlut1545)</li>\n<li>Christian console tools: verse, bible-kjv</li>\n<li>Christian emoticons theme for instant messaging in Kopete</li>\n<li>kdict (KDE Dictionary) with the Easton and Hitchcock Bible dictionaries</li>\n<li>content filter (willowng) for blocking undesired websites</li>\n<li>default artwork and settings designed with Christians in mind</li>\n</ul>\n<h3>Downloading</h3>\n<p>You can get Ichthux 6.09 beta5 from various servers:</p>\n<ul>\n<li><a href=\"http://web.archive.org/web/20061230082703/http://ichthux.pj2.org/6.09-beta5/\">Denmark (http://ichthux.pj2.org)</a></li>\n<li><a href=\"http://web.archive.org/web/20061230082703/http://matheteuo.org/ichthux/6.09-beta5/\">USA (http://matheteuo.org/)</a></li>\n<li><a href=\"http://web.archive.org/web/20061230082703/http://www.misterben.org.uk/ichthux/\">USA (http://www.misterben.org.uk)</a></li>\n<li><a href=\"http://web.archive.org/web/20061230082703/http://ichthux.free.fr/iso/6.09-beta5/\">France (http://ichthux.free.fr)</a></li>\n<li><a href=\"http://web.archive.org/web/20061230082703/http://fr.mirror.ichthux.com/6.09-beta5\">France (http://fr.mirror.ichthux.com)</a></li>\n</ul>\n<h3>Installing from Ubuntu</h3>\n<p>Whether you have Ubuntu or one of its derivatives on your computer, you can easily install Ichthux on your computer, by doing the following:</p>\n<p>Add the Ichthux repository to your sources.list:</p>\n<p><code>deb http://archive.ichthux.com/ichthux grace main deb-src http://archive.ichthux.com/ichthux grace main</code></p>\n<p>Type the following commands in a console to install Ichthux:</p>\n<p><code>wget http://archive.ichthux.com/ichthux.asc # This downloads the key to identify the Ichthux repository sudo apt-key add ichthux.asc sudo apt-get update sudo apt-get install ichthux-desktop</code></p>\n<p>The Ichthux CD also installs sword modules for your language when they are available. If you wish to have them, you can perform this installation by installing the sword-language-pack-* module corresponding to your language. For example, for French support:</p>\n<p><code>sudo apt-get install sword-language-pack-fr</code></p>\n<h3>General Usage Notes:</h3>\n<p>Beta5 is a LiveCD, which means it can be run from the CD without installing anything to your computer’s hard drive. When you boot up the CD it will take you to a “Kubuntu” boot screen, don’t worry, we are working on changing that to an Ichthux one. If you want to install Beta5 just click on the “Install” icon on the desktop when it is fully loaded.</p>\n<p>The willowng content filter needs to be turned on before it can be used. To do this go to the Kmenu and go to Settings -> Content Filter. You will need to add “bad” addresses which are the ones you want to block in the “Domain Filtering” tab. Then set up your browser to use URL “localhost” and port “8563″.</p>\n<h3>Bug Reporting:</h3>\n<p>Please use our Launchpad <a href=\"http://web.archive.org/web/20061230082703/https://launchpad.net/products/ichthux/+bugs\">Bug tracker</a> to file bugs on problems that are specific to Ichthux. If the problem seems to be with non-Ichthux specific programs like the kernel or Xorg please use the Kubuntu Launchpad <a href=\"http://web.archive.org/web/20061230082703/https://launchpad.net/distros/ubuntu/+bugs\">Bug tracker</a>.</p>\n<p>You can also contact us by the methods on the <a href=\"http://web.archive.org/web/20061230082703/http://www.ichthux.com/?q=node/2\">Contact</a> page.</p>"},{"url":"/posts/20070821-blogspot-europecv-latex-cv-class/","relativePath":"posts/20070821-blogspot-europecv-latex-cv-class.md","relativeDir":"posts","base":"20070821-blogspot-europecv-latex-cv-class.md","name":"20070821-blogspot-europecv-latex-cv-class","frontmatter":{"title":"EuropeCV : a LaTeX CV class","template":"post","date":"2007-08-21T15:34:00.002+02:00","canonical_url":"https://raphink.blogspot.com/2007/08/europecv-latex-cv-class.html","blogspot_url":"https://raphink.blogspot.com/2007/08/europecv-latex-cv-class.html","tags":["cv","latex"]},"html":"<p>I was recently searching for a CV LaTeX class. I finally found the EuropeCV class and I really like it.  </p>\n<h4>Installing on Debian/Ubuntu</h4>\n<p>apt-get install texlive-latex-recommended texlive-latex-base texlive-fonts-recommended texlive-latex-extra<br>\nI also recommend to use a LaTeX editor. My favourite is Kile.  </p>\n<h4>Using the class</h4>\n<p>The package provides examples in /usr/share/doc/texlive-latex-extra/latex/europecv/examples/. Choose the example that fits your needs and modify it. A full documentation is also installed in /usr/share/doc/texlive-latex-extra/latex/europecv/europecv.pdf.gz.  </p>\n<h4>Example</h4>\n<p>Nothing better than an example to make the point… Here is my <a href=\"http://r.pinson.free.fr/cv/old/europecv/RaphaelPinson.pdf\" title=\"My PDF CV\">PDF CV</a>, and the <a href=\"http://r.pinson.free.fr/cv/old/europecv/RaphaelPinson.tex\" title=\"LaTeX source of the CV\">LaTeX source</a>.  </p>\n<h4>Edit on 2011/02/24:</h4>\n<p>I have recently dropped EuropeCV in favor of the <a href=\"http://www.ctan.org/tex-archive/macros/latex/contrib/moderncv/\">moderncv</a> template, which I find much nicer. I also use the <a href=\"http://www.ctan.org/pkg/moderntimeline\">moderntimeline</a> package with it.</p>"},{"url":"/posts/20060829-blogspot-typing-biblical-hebrew/","relativePath":"posts/20060829-blogspot-typing-biblical-hebrew.md","relativeDir":"posts","base":"20060829-blogspot-typing-biblical-hebrew.md","name":"20060829-blogspot-typing-biblical-hebrew","frontmatter":{"title":"Typing Biblical Hebrew","template":"post","date":"2006-08-29T15:32:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2006/08/typing-biblical-hebrew.html","blogspot_url":"https://raphink.blogspot.com/2006/08/typing-biblical-hebrew.html","tags":["bible","english","hebrew","ichthux","kde","language","linux","openoffice","restored","typing"]},"html":"<p>As we released Ichthux 6.09 beta5, we began to work on Biblical Hebrew functionalities, to make sure Ichthux could provide a full Biblical Hebrew support for Bible studies.</p>\n<h3>Hebrew font for the WLC Bible</h3>\n<p>Kubuntu has fonts for almost every language by default. However, I included the culmus Hebrew fonts in Ichthux by default to be able to read the Bible in Hebrew. The WLC, along with other codecs of the Tanakh, doesn’t use a simple alphabet, but also has lots of signs to indicate the way to pronounce/vocalize the words and signs to indicate the middle of the verses, the middle of chapters, the middle of the Torah, or where to breathe when singing the text. All these signs cannot be displayed properly with a simple modern Hebrew font, which is why I provided culmus and used Frank Ruehl’s font by default.</p>\n<p>The following clearly shows why it is necessary to have a custom font to read WLC in BibleTime:</p>\n<p><img src=\"http://web.archive.org/web/20061230082703/http://ichthux.free.fr/snapshots/hebrew_font/hebrew_font_normal.jpg\" alt=\"WLC with default font\"><br>\nWLC with default font</p>\n<p><img src=\"http://web.archive.org/web/20061230082703/http://ichthux.free.fr/snapshots/hebrew_font/hebrew_font_frank_ruehl.jpg\" alt=\"WLC with Frank Ruehl&#x27;s font\"><br>\nWLC with Frank Ruehl’s font</p>\n<p>Note: the rules to write a kosher Tanakh are very strict, and even with a Frank Ruehl’s font, the rendering is far from being perfect. For example, in Number 25:12, the vav in SHaLOM is not broken as it should be:</p>\n<p><img src=\"http://web.archive.org/web/20061230082703/http://ichthux.free.fr/snapshots/hebrew_font/numbers_25_12.jpg\" alt=\"Numbers 25:12 in WLC\"><br>\nNumbers 25:12 in WLC</p>\n<p><img src=\"http://web.archive.org/web/20061230082703/http://ichthux.free.fr/snapshots/hebrew_font/broken-vav-num25-12.gif\" alt=\"The broken vav of SHaLOM\"><br>\nThe broken vav of SHaLOM</p>\n<p>The big vav in the middle of the Torah, along with many other pretty important font rendering in the Hebrew tradition don’t appear in the WLC.</p>\n<h3>Typing Biblical Hebrew</h3>\n<p>The next step was to make sure Biblical Hebrew could be typed in Ichthux. The first thing to do is to set the keyboard layout. You can do that easily in SystemSettings. Once you had added the Hebrew layout to the lsit of active layouts, you need to choose si1452 as the variant for it in order to activate the Biblical Hebrew keyboard.</p>\n<p>Once I had that set, I tried to write in Hebrew in various programs, using Frank Ruehl’s font. My two main attempts were in OpenOffice Writer and KWord.</p>\n<p>I have to confess I was very disappointed by OpenOffice there. I had to set the “Enabled for complex text layout (CTL)” in the Language tools, and then it wouldn’t write from right to left still, and didn’t keep the right font when I typed. I had to force it to write from right to left by changing the alignement, but then I couldn’t add spaces before the first word. Additionally, the vowels are not showing properly, but are somehow slightly switched under the next letter.</p>\n<p>On the contrary, KWord proved very easy to use. No additional settings to be done, and it wrote from right to left very nicely. The vowels are aligned very nicely, and the rendering looks great <img src=\"http://web.archive.org/web/20061230082703/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p>Below is the word BReSHiYT written in OpenOffice and KWord for comparison.</p>\n<p><img src=\"http://web.archive.org/web/20061230082703/http://ichthux.free.fr/snapshots/hebrew_font/breshit_ooo.jpg\" alt=\"Breshit in OpenOffice Writer\"><br>\nBreshit in OpenOffice Writer</p>\n<p><img src=\"http://web.archive.org/web/20061230082703/http://ichthux.free.fr/snapshots/hebrew_font/breshit_kword.jpg\" alt=\"Breshit in KWord\"><br>\nBreshit in KWord</p>\n<h3>Conclusion</h3>\n<p>There is still some work to do to get full support for Biblical Hebrew by default in Ichthux, but at least we know where we stand now <img src=\"http://web.archive.org/web/20061230082703/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20071103-blogspot-social-networking-and-opensource/","relativePath":"posts/20071103-blogspot-social-networking-and-opensource.md","relativeDir":"posts","base":"20071103-blogspot-social-networking-and-opensource.md","name":"20071103-blogspot-social-networking-and-opensource","frontmatter":{"title":"Social Networking and OpenSource","template":"post","date":"2007-11-03T14:36:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2007/11/social-networking-and-opensource.html","blogspot_url":"https://raphink.blogspot.com/2007/11/social-networking-and-opensource.html","tags":["blog","drupal","jabber","open-source","restored","wordpress"]},"html":"<p>I was reading about the Facebook vs. Google articles on Slashdot lately and thinking of a way to replace the Social Networking systems with OpenSource tools.</p>\n<p>Social Networking systems provide tools like blogs or pictures albums. We already have very good tools to do this (Wordpress/Drupal/Gallery/etc.). What Social Networking systems have is also a way to link profiles together.  </p>\n<p>So I was thinking about a way to implement this using already existing OpenSouce technology. And I thought that Jabber could do that!  </p>\n<p>Let’s have a closer look at it:</p>\n<ul>\n<li>Jabber is a decentralized system; you can even run your own Jabber server and still connect with other users;</li>\n<li>Jabber has a concept of unique IDs that can interconnect easily;</li>\n<li>Jabber can be used with tons of clients on lots of platforms;</li>\n<li>Jabber has a concept of friends list;</li>\n<li>Jabber can provide information about user (homepage, location, etc);</li>\n<li>Lots of people already have a Jabber ID, even if they don’t know it (gmail accounts can be used as Jabber IDs through the GTalk servers).</li>\n</ul>\n<p>So what if… we used Jabber to interconnect our websites? It could be a simple plugin for blogs/CMS that would connect to your Jabber server, log in, and retrieve your friends’ infos. The plugin could display a list of your friends using the avatars they provided. On hovering over the pictures, more info could be displayed, allowing you to go to their homepage, or even to contact them by IM!  </p>\n<p>I have searched a bit for such a plugin, but haven’t found much.  </p>\n<p>How would you like something like this?</p>"},{"url":"/posts/20071118-blogspot-ichthuxcom-has-new-look-too/","relativePath":"posts/20071118-blogspot-ichthuxcom-has-new-look-too.md","relativeDir":"posts","base":"20071118-blogspot-ichthuxcom-has-new-look-too.md","name":"20071118-blogspot-ichthuxcom-has-new-look-too","frontmatter":{"title":"Ichthux.com has a new look too!","template":"post","date":"2007-11-18T15:38:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2007/11/ichthuxcom-has-new-look-too.html","blogspot_url":"https://raphink.blogspot.com/2007/11/ichthuxcom-has-new-look-too.html","tags":["english","ichthux","linux","restored","style","ubuntu"]},"html":"<p>The ichthux.com website was broken for a long time. After I got a dedicated server, <a href=\"http://web.archive.org/web/20071124204542/http://raoulsnyman.co.za/\" title=\"who_da_fly&#x27;s blog\">Raoul</a> made a whole new website for Ichthux!</p>\n<p>Visit the new website at <a href=\"http://web.archive.org/web/20071124204542/http://www.ichthux.com/\" title=\"Ichthux\">http://www.ichthux.com</a>!</p>"},{"url":"/posts/20071118-blogspot-new-look/","relativePath":"posts/20071118-blogspot-new-look.md","relativeDir":"posts","base":"20071118-blogspot-new-look.md","name":"20071118-blogspot-new-look","frontmatter":{"title":"A new look","template":"post","date":"2007-11-18T15:37:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2007/11/new-look.html","blogspot_url":"https://raphink.blogspot.com/2007/11/new-look.html","tags":["blog","english","restored","style"]},"html":"<p>I’ve been wanting to do this for a long time… So here is my blog’s new look!</p>\n<p>This theme is kind of a mix between the blixed theme (the one I used before) and a 3-columns cutline. I hope to enhance it a bit if I have time this week.</p>\n<p>Comments are welcome <img src=\"http://web.archive.org/web/20071124204542/http://raphink.info/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>"},{"url":"/posts/20071122-blogspot-using-backports-safely-with/","relativePath":"posts/20071122-blogspot-using-backports-safely-with.md","relativeDir":"posts","base":"20071122-blogspot-using-backports-safely-with.md","name":"20071122-blogspot-using-backports-safely-with","frontmatter":{"title":"Using backports safely with apt/preferences","template":"post","date":"2007-11-22T15:39:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2007/11/using-backports-safely-with.html","blogspot_url":"https://raphink.blogspot.com/2007/11/using-backports-safely-with.html","tags":["debian","english","linux","package","restored","sysadmin","ubuntu"]},"html":"<p>Many Ubuntu users like to have the latest versions of softwares and use backports for that. But backports tend to break the stability of a system, and it can be quite boring to comment/uncomment the backports line in /etc/apt/sources.list everytime you want to install a backport. Fortunately, apt/preferences is there to help us!</p>\n<h2>The changes to make</h2>\n<p>First of all, add the backports line for your release of Ubuntu in your sources.list. For example, on my feisty server I added:<br>\n<code>deb http://archive.ubuntu.com/ubuntu feisty-backports main restricted universe multiverse</code></p>\n<p>Now to set the preferences. In my /etc/apt/preferences file, I have:<br>\n<code>Explanation: Backported packages have a lower priority Package: * Pin: release a=feisty-backports Pin-Priority: 100</code></p>\n<h2>Installing a package from the backports</h2>\n<p>Now, let’s see what happens on my system. Backports have higher version numbers than the packages in feisty, feisty-updates and feisty-security, but all packages in feisty-backports have a priority of 100 (the default priority being 500), so they are never chosen for installation.</p>\n<p>From then on, backported packages will not be installed by default. If I want to install a backported package, I have two ways of doing so:</p>\n<h4>Installing the package from backports but not its dependencies</h4>\n<p><code>apt-get install foo/feisty-backports</code></p>\n<p>This will install package <code>foo</code> from feisty-backports, but the possible dependencies will not be taken in feisty-backports. This is safer but will not necessarily work.</p>\n<p>For example:</p>\n<p><code>$ sudo apt-get install -s postfix-mysql/feisty-backports Lecture des listes de paquets... Fait Construction de l'arbre des dépendances Reading state information... Fait Version choisie 2.4.5-3~feisty1 (Ubuntu:7.04/feisty-backports) pour postfix-mysql Certains paquets ne peuvent être installés. Ceci peut signifier que vous avez demandé l'impossible, ou bien, si vous utilisez la distribution unstable, que certains paquets n'ont pas encore été créés ou ne sont pas sortis d'Incoming.</code></p>\n<p>Puisque vous n’avez demandé qu’une seule opération, le paquet n’est<br>\nprobablement pas installable et vous devriez envoyer un rapport de bogue.<br>\nL’information suivante devrait vous aider à résoudre la situation :</p>\n<p>Les paquets suivants contiennent des dépendances non satisfaites :<br>\npostfix-mysql: Dépend: postfix (= 2.4.5-3~feisty1) mais 2.3.8-2 devra être installé<br>\nE: Paquets défectueux<br>\nThe installation fails because the necessary dependency is only present in feisty-backports.</p>\n<h4>Installing the package and its dependencies from backports</h4>\n<p><code>apt-get install -t feisty-backports foo</code></p>\n<p>This will install package <code>foo</code> using the feisty-backports repository with a temporary priority of 990. Dependencies will be taken in feisty-backports aswell.</p>\n<p>For example:<br>\n<code>$ sudo apt-get install -s -t feisty-backports postfix-mysql Lecture des listes de paquets... Fait Construction de l'arbre des dépendances Reading state information... Fait Les paquets supplémentaires suivants seront installés : postfix Paquets suggérés : procmail postfix-pgsql postfix-ldap postfix-pcre sasl2-bin resolvconf postfix-cdb Les NOUVEAUX paquets suivants seront installés : postfix-mysql Les paquets suivants seront mis à jour : postfix 1 mis à jour, 1 nouvellement installés, 0 à enlever et 4 non mis à jour. Inst postfix [2.3.8-2] (2.4.5-3~feisty1 Ubuntu:7.04/feisty-backports) Inst postfix-mysql (2.4.5-3~feisty1 Ubuntu:7.04/feisty-backports) Conf postfix (2.4.5-3~feisty1 Ubuntu:7.04/feisty-backports) Conf postfix-mysql (2.4.5-3~feisty1 Ubuntu:7.04/feisty-backports)</code></p>\n<p>All the packages are taken from feisty-backport in this example.</p>\n<h2>Upgrading the system with all available backports</h2>\n<p>Let’s say I want to upgrade my system using all the available backports for the packages I use (which is not recommended). The command would be :<br>\n<code>apt-get -t feisty-backports dist-upgrade</code></p>"},{"url":"/posts/20080826-blogspot-tellicollecting-infos/","relativePath":"posts/20080826-blogspot-tellicollecting-infos.md","relativeDir":"posts","base":"20080826-blogspot-tellicollecting-infos.md","name":"20080826-blogspot-tellicollecting-infos","frontmatter":{"title":"Tellicollecting infos","template":"post","date":"2008-08-26T09:09:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2008/08/tellicollecting-infos.html","blogspot_url":"https://raphink.blogspot.com/2008/08/tellicollecting-infos.html","tags":["english","kde","linux","programs"]},"html":"<p>In the last few weeks, I've been using <a href=\"http://periapsis.org/tellico/\">Tellico</a> extensively. I first used it to manage a collection of boardgames for an association close to our house.  </p>\n<h2>A first collection: boardgames</h2>\n<p>Tellico makes it easy to create and manage collections of objects, files, or whatever you want to keep a track of. In the case of the boardgames collection, I added a 'contents' table to list all the pieces of each game and track if the game was complete or not.  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivHNIrPmpQvTlptS7FgpKhjMCvcYcTMvLQBhQTvLarHezso9z-BhFcIn9oD1wRlCMl7MBzT3pBxwNqVvAUFLeApo9Z09AwcAvm54b7q5ergdMXsv3pO-9MjV2pmBBnwlb-CwxNr_FmwoaN/s1600-h/web_tellico_main.jpg\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivHNIrPmpQvTlptS7FgpKhjMCvcYcTMvLQBhQTvLarHezso9z-BhFcIn9oD1wRlCMl7MBzT3pBxwNqVvAUFLeApo9Z09AwcAvm54b7q5ergdMXsv3pO-9MjV2pmBBnwlb-CwxNr_FmwoaN/s320/web_tellico_main.jpg\" alt=\"Tellico&#x27;s main view listing boardgames\" title=\"Tellico&#x27;s main view listing boardgames\"></a><br>\nI also added a 'reference' field automatically generated from the ID of the game in the database, to easily assign a unique reference to each object. The 'reference' field uses a \"Dependant\" type and its definition is \"JEU %{id}\" which appends the ID of the object to the string \"JEU\".<br>\nUsing the <a href=\"http://kde-files.org/content/show.php/Fancy+Report+%282+columns%29?content=56655\">fancy report</a> from <a href=\"http://kde-files.org/\">kde-files.org</a>, I was able to easily print labels for each game.  </p>\n<h2>DVDs, CDs, books, and many more...</h2>\n<p>After listing the boardgames, I began to make a list of our own DVDs and CDs. I found it very useful that Tellico allows to search for objects on a lot of web services, such as <a href=\"http://www.amazon.com/\">Amazon</a>, and can even search by barcode (or ISBN for books). I was able to enter most of my CDs using the barcodes with the <a href=\"http://www.amazon.fr/\">amazon.fr</a> plugin. Tellico even completes ISBN as you type. Since the last number of ISBN codes is a check, Tellico adds it automatically so you can check if the ISBN you typed is correct.  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_Lywo3izmk0O_LcPeKTeirtsj3ydqs5wBZVdSGTR35OlN7v0xnPGqCjB7NHbLN3_F5BbRqD5lDK8YpSL1bFtWVHfwsYbQNt8_Nyu6Te2ya-JnSrsEsaT0n-38E2quYbANiuM-KHsVTyJE/s1600-h/web_tellico_isbn_search.jpg\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_Lywo3izmk0O_LcPeKTeirtsj3ydqs5wBZVdSGTR35OlN7v0xnPGqCjB7NHbLN3_F5BbRqD5lDK8YpSL1bFtWVHfwsYbQNt8_Nyu6Te2ya-JnSrsEsaT0n-38E2quYbANiuM-KHsVTyJE/s320/web_tellico_isbn_search.jpg\" alt=\"Tellico search for a book by ISBN on amazon.fr\" title=\"Tellico search for a book by ISBN on amazon.fr\"></a><br>\nTellico also allows you to enter several ISBN/barcodes at a time, either within the interface or by importing them from a file. This opens the door to scanning the codes with a barcode scanner. Unfortunately, barcode scanners are quite expensive. While the most expensive ones come with a USB interface and emulate a keyboard, the few cheap ones you find usually send special codes that can be pretty hard to deal with.  </p>\n<p>Since version 1.3, Tellico includes a patch that allows to use a webcam to read barcodes. I tried to plug a webcam to my machine, fired up Tellico and opened the import window, but nothing special happened. I took a look at the <a href=\"http://tellico.dyndns.org/\">scripts that gave birth to the patch</a>. These scripts use a combination of the v4l driver with mplayer to capture images from the webcam and send them to the <a href=\"http://people.inf.ethz.ch/adelmanr/batoo/\">BaToo</a> Java classes to decode the barcode. Since mplayer was only showing a black screen from my webcam, I hacked the script a bit to use vgrabbj instead, and managed to import shots from the webcam and send them to the BaToo Java script, but the quality of the shots taken by the webcam didn't quite please BaToo... so I finally gave up and typed the ISBN codes manually.  </p>\n<h2>KDE integration</h2>\n<p>One thing I like very much with KDE program is integration. Since they share most of their resources, you can always expect them to easily cooperate. In the case of Tellico, I was very pleased to discover that the lending interface can use the contacts from KAddressBook (kabc) and inform KOrganizer about the planned date of return (although I couldn't get this to work for some reason).  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaknyCfRJ1tngxi-fDM7niWtBiFJ7f0F81ztVGCeKvjOKCtPWMxumZcmTaMCtCymrH0V9V8tjdXZX5LIXC_m1j4owoIJyQlJx_qOYTFNCqeY7Tw4hd-SHZ7s1W1gVE4zMpcdnVvnA7wfFV/s1600-h/web_tellico_isbn_lending.jpg\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaknyCfRJ1tngxi-fDM7niWtBiFJ7f0F81ztVGCeKvjOKCtPWMxumZcmTaMCtCymrH0V9V8tjdXZX5LIXC_m1j4owoIJyQlJx_qOYTFNCqeY7Tw4hd-SHZ7s1W1gVE4zMpcdnVvnA7wfFV/s320/web_tellico_isbn_lending.jpg\" alt=\"Lending a book to a kabc contact from Tellico\" title=\"Lending a book to a kabc contact from Tellico\"></a>  </p>\n<h2>And a reactive developer</h2>\n<p><a href=\"http://periapsis.org/\">Robby</a> was very reactive when I wrote to him concerning the possibility of importing boardgames infos from Amazon. Thank you Robby for reacting so promptly!</p>"},{"url":"/posts/20080825-blogspot-back-up/","relativePath":"posts/20080825-blogspot-back-up.md","relativeDir":"posts","base":"20080825-blogspot-back-up.md","name":"20080825-blogspot-back-up","frontmatter":{"title":"Back up","template":"post","date":"2008-08-25T15:32:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/08/back-up.html","blogspot_url":"https://raphink.blogspot.com/2008/08/back-up.html"},"html":"<p>After a few months offline, I've finally set up a new blog, and I'm back to blogger for the occasion.</p>"},{"url":"/posts/20080902-blogspot-old-posts-restored/","relativePath":"posts/20080902-blogspot-old-posts-restored.md","relativeDir":"posts","base":"20080902-blogspot-old-posts-restored.md","name":"20080902-blogspot-old-posts-restored","frontmatter":{"title":"Old posts restored","template":"post","date":"2008-09-02T20:41:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/old-posts-restored.html","blogspot_url":"https://raphink.blogspot.com/2008/09/old-posts-restored.html","tags":["blog","english","restored"]},"html":"<p>I took some time to go on archive.org and find old posts I had on my previous blog and on multiply, going back all the way to 2004. I added these posts to this new blog, so they can be seen again. Some posts may be broken, in case of missing images or javascript functions.</p>"},{"url":"/posts/20080903-blogspot-back-on-planet-ubuntu/","relativePath":"posts/20080903-blogspot-back-on-planet-ubuntu.md","relativeDir":"posts","base":"20080903-blogspot-back-on-planet-ubuntu.md","name":"20080903-blogspot-back-on-planet-ubuntu","frontmatter":{"title":"Back on Planet Ubuntu","template":"post","date":"2008-09-03T11:16:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/back-on-planet-ubuntu.html","blogspot_url":"https://raphink.blogspot.com/2008/09/back-on-planet-ubuntu.html","tags":["blog","english","ubuntu"]},"html":"<p>I just updated the feed URL to my new blog on Planet Ubuntu, so my posts are now shown again on it.  </p>\n<p>I just noticed something though... The very old posts I restored on my blog are appearing as new posts, even though I set their date to their original posting date (back in 2004, 2005 and so on), so there's some posts about testing Warty that appear on Planet. It will pass soon as other people post. I don't really know how to fix this right now. Sorry guys.</p>"},{"url":"/posts/20080905-blogspot-comme-un-grain-de-riz/","relativePath":"posts/20080905-blogspot-comme-un-grain-de-riz.md","relativeDir":"posts","base":"20080905-blogspot-comme-un-grain-de-riz.md","name":"20080905-blogspot-comme-un-grain-de-riz","frontmatter":{"title":"Comme un grain de riz...","template":"post","date":"2008-09-05T17:53:00.003+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/comme-un-grain-de-riz.html","blogspot_url":"https://raphink.blogspot.com/2008/09/comme-un-grain-de-riz.html","tags":["français","life"]},"html":"<p>Jeudi matin, Jimena et moi sommes allés chez le radiologue... C'était déjà notre deuxième rendez-vous médical cette semaine, le premier était chez le gynécologue. Et c'est ce jeudi matin que nous l'avons aperçu pour la première fois.  </p>\n<p>Grand comme un grain de riz, à peine 15 mm, l'embryon qui grandit pour devenir notre enfant a déjà le coeur qui bat à 158 battements par minute!  </p>\n<p><a href=\"http://r.pinson.free.fr/pregnancy/echographies/2008-09-04/echo1_1.jpg\"><img src=\"http://r.pinson.free.fr/pregnancy/echographies/2008-09-04/echo1_1.jpg\"></a><br>\nNous remercions Dieu de sa fidélité et lui remettons cette grossesse, puisqu'il sait mieux que nous les bénédictions qu'il réserve pour l'avenir.</p>"},{"url":"/posts/20080905-blogspot-augeas-031/","relativePath":"posts/20080905-blogspot-augeas-031.md","relativeDir":"posts","base":"20080905-blogspot-augeas-031.md","name":"20080905-blogspot-augeas-031","frontmatter":{"title":"Augeas 0.3.1","template":"post","date":"2008-09-05T17:53:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/augeas-031.html","blogspot_url":"https://raphink.blogspot.com/2008/09/augeas-031.html","tags":["augeas","conference","debian","english","open-source","release","ubuntu"]},"html":"<p><a href=\"http://augeas.net/\">Augeas</a> 0.3.1 is out! This is the announcement from the augeas-devel mailing-list:  </p>\n<blockquote>\n<p><br>\nI am pleased to announce the release of Augeas 0.3.1; it has been much<br>\nlonger than I'd like since the last release, and this release contains<br>\nmany more changes than is betrayed by the small change in version<br>\nnumbers.  </p>\n<p>There has been a tremendous amount of activity both in enhancing<br>\nexisting lenses and in writing new ones. I have tried to keep track of<br>\nall the contributors in the NEWS - if you sent a patch and didn't get<br>\ncredit for it, please remind me (gently ;) With that much activity in<br>\nlens-writing, I feel that we need to figure out a way to indicate which<br>\nlenses we consider 'finished' and which ones we consider 'experimental',<br>\nso that users know where changes in the tree are likely.  </p>\n<p>The release can be downloaded from:  </p>\n<p>Tarball: <a href=\"http://augeas.net/download/augeas-0.3.1.tar.gz\">http://augeas.net/download/augeas-0.3.1.tar.gz</a><br>\nFedora RPM's are making their way through the build system  </p>\n<p>Detailed NEWS:  </p>\n<p>- Major performance improvement when processing huge files, reducing<br>\nsome O(n^2) behavior to O(n) behavior. It's now entirely feasible<br>\nto manipulate for example /etc/hosts files with 65k lines<br>\n- Handle character escapes '\\x' in regular expressions in compliance<br>\nwith Posix ERE<br>\n- aug_mv: fix bug when moving at the root level<br>\n- Fix endless loop when using a mixed-case module name like<br>\nMyMod.lns<br>\n- Typecheck del lens: for 'del RE STR', STR must match RE<br>\n- Properly typecheck the '?' operator, especially the atype; also<br>\nallow '?' to be applied to lenses that contain only 'store', and<br>\ndo not produce tree nodes.<br>\n- Many new/improved lenses<br>\n* many lenses now map comments as '#comment' nodes instead of just<br>\ndeleting them<br>\n* Sudoers: added (Raphael Pinson)<br>\n* Hosts: map comments into tree, handle whitespace and comments<br>\nat the end of a line (Kjetil Homme)<br>\n* Xinetd: allow indented comments and spaces around \"}\" (Raphael Pinson)<br>\n* Pam: allow comments at the end of lines and leading spaces<br>\n(Raphael Pinson)<br>\n* Fstab: map comments and support empty lines (Raphael Pinson)<br>\n* Inifile: major revamp (Raphael Pinson)<br>\n* Puppet: new lens for /etc/puppet.conf (Raphael Pinson)<br>\n* Shellvars: handle quoted strings and arrays (Nahum Shalman)<br>\n* Php: map entries outside of sections to a '.anon' section<br>\n(Raphael Pinson)<br>\n* Ldap: new lens for /etc/ldap.conf (Free Ekanayaka)<br>\n* Dput: add allowed_distributions entry (Free Ekanayaka)<br>\n* OpenVPN: new lens for /etc/openvpn/{client,server}.</p>\n<p>conf (Raphael Pinson)<br>\n* Dhclient: new lens for /etc/dhcp3/dhclient.conf (Free Ekanayaka)<br>\n* Samba: new lens for /etc/samba/smb.conf (Free Ekanayaka)<br>\n* Dnsmasq: new lens for /etc/dnsmasq.conf (Free Ekanayaka)<br>\n* Slapd: new lens for /etc/ldap/slapd.conf (Free Ekanayaka)<br>\n* Sysctl: new lens for /etc/sysctl.conf (Sean Millichamp)  </p>\n<p>David  </p>\n</blockquote>\n<p>An updated package is already in Debian, thanks to Free, and Nicolas (aka nxvl) will try to get an exception to include it in Intrepid before it's too late.  </p>\n<p>In other news, I'm likely to give a talk on Augeas at the <a href=\"http://jm2l.linux-azur.org/\">JM2l</a> in Sophia Antipolis.</p>"},{"url":"/posts/20080906-blogspot-look-ma-no-mouse/","relativePath":"posts/20080906-blogspot-look-ma-no-mouse.md","relativeDir":"posts","base":"20080906-blogspot-look-ma-no-mouse.md","name":"20080906-blogspot-look-ma-no-mouse","frontmatter":{"title":"Look Ma, no mouse!","template":"post","date":"2008-09-06T19:31:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/look-ma-no-mouse.html","blogspot_url":"https://raphink.blogspot.com/2008/09/look-ma-no-mouse.html","tags":["edubuntu","english","ltsp","ubuntu"]},"html":"<p>I have recently set up a small <a href=\"http://www.ltsp.org/\">LTSP</a> network for an association in my street. Some days ago, I was asked if it was possible to display photos on all of the LTSP machines at the same time to show the activities of the association. This is of course very easy to set up, but I didn't want to leave keyboards or mice on the computers, to make sure nobody would exit the photo program or try to reboot the machine.  </p>\n<p>The quickest solution I found to do that easily was to use <a href=\"http://synergy2.sourceforge.net/\">synergy</a> and wireless keyboard and mouse. Synergy allows to share keyboards and mice between several computers, running Windows, MacOS or Linux. It actually goes further than this, since you can also copy and paste contents (text, images, etc) from one computer to another. The down side of it is that it's not secure, but that was not a problem in my case at all.  </p>\n<p>So in my case, I just started synergys on the main machine (the LTSP server) having set virtual screens in synergy.conf for all the ltsp thin clients. Then I started synergyc on each client with <code>synergyc --name ltsp1 localhost</code>, adapting the name for each machine. Finally, I removed all keyboards and mice from the computers and only left the wireless keyboard and mouse on the LTSP server. This left me with a group of computers without any keyboards or mice plugged to them, which could each be controlled by a wireless devide, by simply dragging the wireless mouse from a screen to another. All that was left to do was to launch the slideshows on each machine and hide the keyboard and mouse in a corner, to be used only when necessary.</p>"},{"url":"/posts/20080905-blogspot-naturaldocs/","relativePath":"posts/20080905-blogspot-naturaldocs.md","relativeDir":"posts","base":"20080905-blogspot-naturaldocs.md","name":"20080905-blogspot-naturaldocs","frontmatter":{"title":"NaturalDocs","template":"post","date":"2008-09-05T17:52:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/naturaldocs.html","blogspot_url":"https://raphink.blogspot.com/2008/09/naturaldocs.html","tags":["augeas","css","debian","documentation","english","perl","sysadmin","ubuntu","website"]},"html":"<p>While the collection of available Augeas modules is increasing dramatically, there is more and more of a need for a good documentation. Some time ago, R. I. Pienaar (aka Volcane) made a proposition to write standard inline documentation in <a href=\"http://reductivelabs.com/trac/puppet/wiki/ModuleDocumentationStandards\">Puppet modules,</a> using the <a href=\"http://www.naturaldocs.org/\">NaturalDocs</a> tool. I thought I would just try to see if it could easily be used to document Augeas modules.  </p>\n<p>After a few attempts, I found that I was rewriting all my code in the comments, and that was not very optimized, so I wrote to the NaturalDocs's developer, Greg Valure, to ask him about how hard it would be to support the Augeas language in ND. Not only did he answer quickly, but he provided <a href=\"http://r.pinson.free.fr/augeas/doc/conf/\">better configuration files for ND</a>, aswell as a <a href=\"http://r.pinson.free.fr/augeas/doc/modules/Augeas.pm\">Perl Module</a> to enhance Augeas's support in ND! Thank you very much Greg, you are a great help!  </p>\n<p>So I spent some more time trying to enhance the comments in the <a href=\"http://r.pinson.free.fr/augeas/doc/augfiles/\">example modules I chose</a>. After talking with David about it, we still feel like it would be better if we didn't have to prefix every declaration with a comment to get it included in the documentation, and if parameters and parameter types could be detected automatically by parsing the code. From what I understand, all this should be possible with ND by improving the Augeas.pm and ideally turning it into a full language support Perl Module. One down side of this is that ND is currently being rewritten in .Net/Mono, so the current work on Perl modules will not work with ND 2.0 anymore.  </p>\n<p>I also spent quite a few hours yesterday modifying the CSS stylesheet to match the Augeas website.  </p>\n<p>Now for the demo: you can see it <a href=\"http://r.pinson.free.fr/augeas/doc\">here</a>!</p>"},{"url":"/posts/20080915-blogspot-modular-tar-pipe-tar-in-perl/","relativePath":"posts/20080915-blogspot-modular-tar-pipe-tar-in-perl.md","relativeDir":"posts","base":"20080915-blogspot-modular-tar-pipe-tar-in-perl.md","name":"20080915-blogspot-modular-tar-pipe-tar-in-perl","frontmatter":{"title":"Modular tar pipe tar in Perl","template":"post","date":"2008-09-15T16:23:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/modular-tar-pipe-tar-in-perl.html","blogspot_url":"https://raphink.blogspot.com/2008/09/modular-tar-pipe-tar-in-perl.html","tags":["english","perl","sysadmin","ubuntu"]},"html":"<p>I was trying to write a nice tar pipe tar system in a perl script, and got to this, which I think can be useful:  </p>\n<p><code>#!/usr/bin/perl use Archive::Tar; use Net::SSH; my $tar = Archive::Tar->new; my $dir = \"/path/to/dir\"; # Copy files from dir without recursion my @files = glob(\"$dir/*\"); $tar->add_files(@files); $user = \"root\"; $host = \"myhost.example.org\"; $cmd = \"cd / &#x26;&#x26; tar xf -\"; Net::SSH::sshopen2(\"$user\\@$host\", *READER, *WRITER, \"$cmd\") || die \"ssh: $!\"; print WRITER $tar->write; close(WRITER); close(READER);</code>  </p>\n<p>If you know of a nicer way to do it, I'm open to ideas :)</p>"},{"url":"/posts/20080917-blogspot-thou-shalt-not-loop/","relativePath":"posts/20080917-blogspot-thou-shalt-not-loop.md","relativeDir":"posts","base":"20080917-blogspot-thou-shalt-not-loop.md","name":"20080917-blogspot-thou-shalt-not-loop","frontmatter":{"title":"Thou shalt not loop","template":"post","date":"2008-09-17T14:11:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/thou-shalt-not-loop.html","blogspot_url":"https://raphink.blogspot.com/2008/09/thou-shalt-not-loop.html","tags":["english","sysadmin","ubuntu"]},"html":"<p>I've been using <a href=\"http://inotify.aiken.cz/?section=incron&#x26;page=about&#x26;lang=en\">incron</a> extensively to write a CVS synchronizer for my company lately (the last post about Perl modules was part of that). This synchronizer uses incron to monitor all CVSROOT/history files. When history files are modified, the script is launched, analyzes the changed files and synchronizes them (using the tar|tar method described in my last post) to the fallback machine. This allows to have a pretty much synchrone fallback... but that's without counting on the developers using the machine... Some of them commit as much as 300 files a minute, which triggers the script just as many times!  </p>\n<p>Of course, one of the first things I wrote in the script is a lock. In that case, it uses Proc::Processtable to make the script exit if it is already running on the same history file. But that was not enough.  </p>\n<p>Then I discovered IN_NO_LOOP. This optional parameter in incrontab is described this way:  </p>\n<p>Additionaly, there is a symbol which doesn't appear in the inotify symbol set. It is IN_NO_LOOP. This symbol disables monitoring events until the current one is completely handled (until its child process exits).  </p>\n<p>This solved my problem. When an history file is changed, incron fires the command and disables the monitoring on this history file until the command returns.</p>"},{"url":"/posts/20080912-blogspot-its-name-is/","relativePath":"posts/20080912-blogspot-its-name-is.md","relativeDir":"posts","base":"20080912-blogspot-its-name-is.md","name":"20080912-blogspot-its-name-is","frontmatter":{"title":"Its name is...","template":"post","date":"2008-09-12T10:59:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/its-name-is.html","blogspot_url":"https://raphink.blogspot.com/2008/09/its-name-is.html","tags":["english","meme","ubuntu"]},"html":"<p>Playing the Ubuntu meme, too...  </p>\n<p>I usually name my machines after the characters in Narnia. For a long time, my main desktop machine was thus called aslan. My laptop was peter, and my online server was caspian. Then I reinstalled peter and renamed it lucy, and my online server died and I gave the caspian name to ... my USB key!  </p>\n<p>Now I tend to use names from Bible characters. The last machine I installed is called caleb, after Joshua's companion when the Hebrews reached Israel.</p>"},{"url":"/posts/20080923-blogspot-forcing-environment-in-ssh/","relativePath":"posts/20080923-blogspot-forcing-environment-in-ssh.md","relativeDir":"posts","base":"20080923-blogspot-forcing-environment-in-ssh.md","name":"20080923-blogspot-forcing-environment-in-ssh","frontmatter":{"title":"Forcing environment in SSH","template":"post","date":"2008-09-23T16:10:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/forcing-environment-in-ssh.html","blogspot_url":"https://raphink.blogspot.com/2008/09/forcing-environment-in-ssh.html","tags":["english","linux","notes","sysadmin","ubuntu"]},"html":"<p>It's quite easy to force environments variables in an SSH session, since /etc/profile, /etc/bash.bashrc etc. are read. But when you launch commands with SSH without opening a session, these files are not parsed, so it gets harder to set the environment.  </p>\n<p>So it can be useful to know that /etc/environment is read by SSH aswell as login. The format is \"VARIABLE=VALUE\" for each line. In my case, I needed to force TMPDIR to \"/var/lib/gforge-dop/chroot/tmp\" so I just put \"TMPDIR=/var/lib/gforge-dop/chroot/tmp\" in /etc/environment and it worked :)  </p>\n<p>You can test if your variable is added by doing :<br>\nssh user@host env  </p>\n<p>and see if your variable is listed properly by env.  </p>\n<p>If you need to set environment variables per user, you can use ~/.ssh/environment. In order to do that, you need to set PermitUserEnvironment to \"yes\" in sshd_config and restart sshd.</p>"},{"url":"/posts/20080923-blogspot-ssh-pamchroot/","relativePath":"posts/20080923-blogspot-ssh-pamchroot.md","relativeDir":"posts","base":"20080923-blogspot-ssh-pamchroot.md","name":"20080923-blogspot-ssh-pamchroot","frontmatter":{"title":"SSH + pam_chroot","template":"post","date":"2008-09-23T16:09:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/09/ssh-pamchroot.html","blogspot_url":"https://raphink.blogspot.com/2008/09/ssh-pamchroot.html","tags":["english","linux","notes","sysadmin","ubuntu"]},"html":"<p>I've been trying to get pam_chroot to work with ssh. There's a few little things to do to get it to work.  </p>\n<ul>\n<li>Install pam_chroot and set your chroot. See <a href=\"http://singe.za.net/blog/archives/378-Linux-SSH-Jail-with-pam_chroot.html\">http://singe.za.net/blog/archives/378-Linux-SSH-Jail-with-pam_chroot.html</a>  </li>\n<li>Set UsePrivilegeSeparation to \"no\" in sshd_config and restart sshd  </li>\n<li>Add the pam_chroot line to /etc/pam.d/ssh aswell as /etc/pam.d/login.  </li>\n<li>Make sure there's a /tmp dir in your chroot. If not, create it : mkdir -m 1777 $CHROOTDIR/tmp  </li>\n<li>\n<p>Make sure you have the libs to execute your shell inside the chroot. A bit of a barbarian way to do that (adapt to your shell) is :<br>\ncp $(ldd /bin/bash | sed -e \"s/.* => \\([^\\)]*\\) .*/\\1/\") $CHROOTDIR/lib/  </p>\n<p>If you need to debug:  </p>\n</li>\n<li>Turn on debug in the pam_chroot line of /etc/pam.d/ssh. This will display the debug messages in /var/log/auth.log on Debian.  </li>\n<li>Turn on debug in sshd ('SSHD_OPTS=\"-d\"' in /etc/default/ssh) and restart sshd.  </li>\n<li>Use verbose on your ssh client.  </li>\n<li>Check the logs inside the chroot too, if you have syslogging on.</li>\n</ul>"},{"url":"/posts/20081006-blogspot-setting-printer-on-kde4/","relativePath":"posts/20081006-blogspot-setting-printer-on-kde4.md","relativeDir":"posts","base":"20081006-blogspot-setting-printer-on-kde4.md","name":"20081006-blogspot-setting-printer-on-kde4","frontmatter":{"title":"Setting a printer on KDE4","template":"post","date":"2008-10-06T22:21:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2008/10/setting-printer-on-kde4.html","blogspot_url":"https://raphink.blogspot.com/2008/10/setting-printer-on-kde4.html","tags":["kde","kde4","linux","ubuntu"]},"html":"<p>I just installed a new computer on hardy with KDE 4.1.2. Tonight, I plugged my good old HP PSC 1510 to it. It was already quite easy to set it on KDE 3, like 5 clicks away or so. Now here is a little tutorial on how to set it on KDE4:  </p>\n<ol start=\"2\">\n<li>Plug the printer to the computer  </li>\n<li>Turn the printer on  </li>\n<li>Read the little popup saying that the printer was detected and is ready to print  </li>\n<li>Print  </li>\n</ol>\n<p>Could it be easier? Thank you KDE devs!</p>"},{"url":"/posts/20090108-blogspot-few-news/","relativePath":"posts/20090108-blogspot-few-news.md","relativeDir":"posts","base":"20090108-blogspot-few-news.md","name":"20090108-blogspot-few-news","frontmatter":{"title":"A few news","template":"post","date":"2009-01-08T17:19:00.001+01:00","canonical_url":"https://raphink.blogspot.com/2009/01/few-news.html","blogspot_url":"https://raphink.blogspot.com/2009/01/few-news.html","tags":["english","france","français","life","pdf"]},"html":"<p>This year, Jimena and I decided to write a newsletter to let our friends know about our life here.  </p>\n<p>You can find this newsletter here:  </p>\n<ul>\n<li><a href=\"http://r.pinson.free.fr/newsletter/2008/Newsletter_2008_en.pdf\">in English</a>  </li>\n<li><a href=\"http://r.pinson.free.fr/newsletter/2008/Newsletter_2008_fr.pdf\">en français</a>  </li>\n</ul>\n<p>Have a blessed new year!</p>"},{"url":"/posts/20081218-blogspot-follow-up-on-screen-profiles/","relativePath":"posts/20081218-blogspot-follow-up-on-screen-profiles.md","relativeDir":"posts","base":"20081218-blogspot-follow-up-on-screen-profiles.md","name":"20081218-blogspot-follow-up-on-screen-profiles","frontmatter":{"title":"Follow-up on screen profiles","template":"post","date":"2008-12-18T10:38:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2008/12/follow-up-on-screen-profiles.html","blogspot_url":"https://raphink.blogspot.com/2008/12/follow-up-on-screen-profiles.html","tags":["debian","linux","screen","sysadmin","ubuntu"]},"html":"<p>After reading <a href=\"http://blog.dustinkirkland.com/2008/12/ubuntu-server-includes-window-manager.html\">Justin's proposal</a> on screen profiles and <a href=\"http://nicolas.barcet.com/drupal/screen-by-default\">Nicolas' answer</a> on automatic screen launch, I've worked a bit on a profile for my company. I added a few things to it:  </p>\n<ul>\n<li>\"hostname -f\" after the release. %H provides \"hostname -s\" but I preferred a fqdn since we have many machines named the same.  </li>\n<li>load average (%l) after updates-available  </li>\n<li>number of users (taken from `uptime`)  </li>\n<li>uptime (taken from `uptime`)</li>\n</ul>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixVK_h6q1cbauRMpNmFHadFhfHlqRwKuTY4QeDTF83zkUnXRg7_KyFOuWLkbjiEbpETyT_ChE85H48JSl5arX7LcpELNdNo3on9Cc06RmHWhmw8poS8l33Jnl8opGnUSYhYZLfAyeTywiQ/s1600-h/hebex-screen.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixVK_h6q1cbauRMpNmFHadFhfHlqRwKuTY4QeDTF83zkUnXRg7_KyFOuWLkbjiEbpETyT_ChE85H48JSl5arX7LcpELNdNo3on9Cc06RmHWhmw8poS8l33Jnl8opGnUSYhYZLfAyeTywiQ/s320/hebex-screen.png\"></a>  </p>\n<p>with some nice colors. For thoses interested, here are the scripts :  </p>\n<p><code>$ cat load-average #!/bin/sh uptime | sed -e \"s/.*load average: //\" | tr -d \" \" | tr \",\" \" \"</code><br>\n<code>$ cat nb-users #!/bin/sh uptime | sed -e \"s/.*, *\\(.* users\\), .*/\\1/\"</code><br>\n<code>$ cat release-short #!/bin/sh lsb_release -i -c -s | tr \"\\n\" \" \"</code><br>\n<code>$ cat uptime #!/bin/sh uptime | sed -e \"s/.* up *\\(.*\\), *.* users, .*/\\1/\"</code>  </p>\n<p>I added the following lines to the \"common\" profile :<br>\n<code>backtick 105 5 5 /usr/share/screen-profiles/bin/load-average backtick 106 10 10 /usr/share/screen-profiles/bin/nb-users backtick 107 10 10 /usr/share/screen-profiles/bin/uptime backtick 108 10 10 /bin/hostname -f backtick 109 3600 3600 /usr/share/screen-profiles/bin/release-short</code>  </p>\n<p>and this is my hardstatus line :<br>\n<code>hardstatus string '%{+b Wr} (*) Hebex %{+b wk} %100`%{= Wk}|%{+b Gk}%108`%{= Wk}|%= |%{+b rW}%101`%{= Wk}|%{+b Ck}%l%{= Wk}|%{+b Mk}%106`%{= Wk}|%{+b bW}%107`%{= Wk}|%{+b gW}%103`%{= Wk}|%{= wk}%Y-%m-%d %c:%s'</code>  </p>\n<p>Then I tried to connect from my Acer Aspire One and I realized that the line didn't fit on my screen (the 9\" screen that is...). So I made a short profile for this purpose, by using %H instead of \"hostname -f\" and making a release-short function that only displays `lsb_release -i -c -s | tr \"\\n\" \" \"`. I made this a new profile. Here is the hardstatus line :  </p>\n<p><code>hardstatus string '%{+b Wr} (*) Hebex %{+b wk}%109`%{= Wk}|%{+b Gk}%H%{= Wk} %=|%{+b rW}%101`%{= Wk}|%{+b Ck}%l%{= Wk}|%{+b Mk}%106`%{= Wk}|%{+b bW}%107`%{= Wk}|%{+b gW}%103`%{= Wk}|%{= wk}%Y-%m-%d %c:%s'</code>  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZuS07DN1XlUr_0tkGfc3X1ITbtxOym3XkxQf76VdRpEY1fM7NwpQeuKAPSq-O9Q9aEuHGVwXJObSWDZS5xaSS4gj7Qjjueonrui2osX-WAfjUniJrQkH0ZqdFW_k0Yfuhk54MM-r72Goi/s1600-h/hebex-screen-short.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZuS07DN1XlUr_0tkGfc3X1ITbtxOym3XkxQf76VdRpEY1fM7NwpQeuKAPSq-O9Q9aEuHGVwXJObSWDZS5xaSS4gj7Qjjueonrui2osX-WAfjUniJrQkH0ZqdFW_k0Yfuhk54MM-r72Goi/s320/hebex-screen-short.png\"></a><br>\nThen I wondered how I could load it automatically when I connect from my notebook. So I tweaked a bit Nicolas' lines and made a .screen_bashrc which I source in my .bashrc :  </p>\n<p><code>if [ \"$PS1\" ]; then # Set screen-profile to short if we're on a notebook # to normal otherwise if [ \"x${LC_NOTEBOOK}\" = \"xyes\" ]; then ln -sf /usr/share/screen-profiles/profiles/hebex-short.screenrc $HOME/.screenrc-profile else ln -sf /usr/share/screen-profiles/profiles/hebex.screenrc $HOME/.screenrc-profile fi if [ \"$TERM\" != \"screen\" ]; then #screen -D -R #Update 2008 Dec 16: -xRR is way better screen -xRR fi fi</code>  </p>\n<p>On my notebook, I added a \"export LC_NOTEBOOK=yes\" to my .bashrc, so whenever I connect to a machine using the notebook, the short screen profile is automatically selected instead of the normal one, and I can see all infos on my small screen :)  </p>\n<p>Note: I chose LC_NOTEBOOK because LC_* variables are usually listed in AcceptEnv in /etc/ssh/sshd_config to export language settings, so I was pretty sure that the variable would be exported fine.</p>"},{"url":"/posts/20090219-blogspot-transcoding-avchd/","relativePath":"posts/20090219-blogspot-transcoding-avchd.md","relativeDir":"posts","base":"20090219-blogspot-transcoding-avchd.md","name":"20090219-blogspot-transcoding-avchd","frontmatter":{"title":"Transcoding AVCHD","template":"post","date":"2009-02-19T10:35:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2009/02/transcoding-avchd.html","blogspot_url":"https://raphink.blogspot.com/2009/02/transcoding-avchd.html","tags":["computers","english","ffmpeg","film","open-source","ubuntu","video"]},"html":"<p>Since we're expecting a baby for the end of April, Jimena and I thought it would be really nice to have a video camera to record those precious memories. I thought it was worth it to get an HD camera, even though we don't have any HD devices yet, because we'll be happy to watch our memories in HD in a few years, when we can afford a new TV ;)  </p>\n<p>So there we went, and got the <a href=\"http://www2.panasonic.com/consumer-electronics/support/Hi-Def-Camcorders/model.HDC-SD9\">Panasonic HDC-SD9</a>. It's a great camera, which records directly in HD formats, from 1440x1080 to 1920x1080. The only issue came when I wanted to play these videos on my computer. Our poor dual-core Intel Pentium D with 1.5 GB RAM struggles to play the lowest quality the SD9 can produce. So there was a need to transcode this format into a more readable one, for example avi or mov.  </p>\n<p>My first approach was to use a simple ffmeg line :<br>\n<code>ffmpeg -i somefile.mts -s svga -r 25 somefile.mov</code>  </p>\n<p>It does convert the file to a .mov successfully, but the result is horrible: the images are almost just as grabbled as reading the mts directly with VLC.  </p>\n<p>This morning, I found <a href=\"http://www.fsckin.com/2008/01/03/transcoding-mtsm2ts-avchd-video-files-with-free-software/\">this post</a>, which linked to <a href=\"http://www.avsforum.com/avs-vb/showthread.php?s=58b68293508c54a5b0d77f58739058cb&#x26;t=789775&#x26;page=5\">this forum</a>, which features a great talk on how to efficiently transcode AVCHD to mov or avi. It also linked to <a href=\"http://ubuntuforums.org/showthread.php?t=1045153&#x26;highlight=avchd+to+avi\">Ubuntu forums</a> where a guy proposed to use mencoder for the job with something like<br>\n<code>mencoder $f -o `basename $f .mts`.avi -oac copy -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=10000 -fps 50 -vf scale=720:576</code>  </p>\n<p>The result, once again, was horrible in my case.  </p>\n<p>Eventually, I tried the script located at <a href=\"http://marks.org/avchd/hdffxvrt-mov1-8-09\">http://marks.org/avchd/hdffxvrt-mov1-8-09</a>, and it worked great! The only problem I had with it was that most of my videos were in 1440, so I had to use \"-i 1440x1080\" on them. Otherwise, the results are very nice.  </p>\n<p>This script has preset formats, so you can simply call it this way<br>\n<code>./hdffxvrt-mov1-8-09 -p small -i 1440x1080 00000.mts</code>  </p>\n<p>Nice and efficient, and uses ffmpeg under the hood, only going through a yuv demux before actually transcoding (from the little I understand about video transcoding at least). You only need to be patient.  </p>\n<p><strong>Update:</strong>  </p>\n<p>I worked a bit on the script and improved it quite a bit. The first improved version can be found on <a href=\"http://r.pinson.free.fr/avchd/hdffxvrt-raphink-2-20-09\">this link</a> and includes the following changes:  </p>\n<ul>\n<li>Take -f switch to choose format (mov by default) instead of having two separate scripts ;</li>\n<li>Use mktemp to create the TMPDIR ;</li>\n<li>Add traps to remove temporary files and directories on interrupt, kill and exit ;</li>\n<li>Also remove output file if the file is interrupted ;</li>\n<li>Try to detect resolution if it wasn't specified with the -i switch. This is currently a bit ugly but it works for me so far. Any improvement in this would be welcome.  </li>\n</ul>\n<p>After that, I thought it would be nice to integrate it in Dolphin/Konqueror as a service menu. It gave birth to the script you will find on <a href=\"http://r.pinson.free.fr/avchd/mts2mov-kde_0.1.tar.gz\">this link</a>. This is still quite a hack, and I will probably work more on it later on, but you are already welcome to try it and give some feedback.  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOXRwwD7DtWKUImh0kRGOYX76CwiklDlkCQGQ47MJyYs6gWFQNV5P4xT20Dq1vEdDZUiKVXCuayLLlRb6oYMsnNXIdQGhqxFBa00bDfvTtGb53yj05PCI6Y7qLz_qaJYxxZP6gAh9vN16u/s1600-h/snapshot1.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOXRwwD7DtWKUImh0kRGOYX76CwiklDlkCQGQ47MJyYs6gWFQNV5P4xT20Dq1vEdDZUiKVXCuayLLlRb6oYMsnNXIdQGhqxFBa00bDfvTtGb53yj05PCI6Y7qLz_qaJYxxZP6gAh9vN16u/s320/snapshot1.png\"></a>  </p>\n<p>This KDE version includes the following improvements:  </p>\n<ul>\n<li>Register .mts files in the system ;</li>\n<li>Call the script from ServiceMenus (Dolphin/Konqueror) for various preset sizes ;</li>\n<li>Uses kdialog to provide information while encoding ;  </li>\n<li>Progress bar support interacting with ffmpeg encoding progress ;  </li>\n<li>Support for the \"Cancel\" button while encoding.</li>\n</ul>"},{"url":"/posts/20090115-blogspot-setting-up-access-point-with-wpa-on/","relativePath":"posts/20090115-blogspot-setting-up-access-point-with-wpa-on.md","relativeDir":"posts","base":"20090115-blogspot-setting-up-access-point-with-wpa-on.md","name":"20090115-blogspot-setting-up-access-point-with-wpa-on","frontmatter":{"title":"Setting up an access point with WPA on Ubuntu Intrepid","template":"post","date":"2009-01-15T23:00:00.001+01:00","canonical_url":"https://raphink.blogspot.com/2009/01/setting-up-access-point-with-wpa-on.html","blogspot_url":"https://raphink.blogspot.com/2009/01/setting-up-access-point-with-wpa-on.html","tags":["debian","documentation","english","linux","network","open-source","sysadmin","ubuntu","wifi"]},"html":"<p>This is a quick tutorial inspired by the French tutorial I used from <a href=\"http://www.lea-linux.org/cached/index/Cr%C3%83%C2%A9er_un_point_d%27acc%C3%83%C2%A8s_s%C3%83%C2%A9curis%C3%83%C2%A9_avec_hostAPd.html\">Léa Linux</a>, as I spent two hours configuring an access point on my machine, using WPA authentication with a madwifi card (atheros).  </p>\n<h2>Basic network configuration</h2>\n<p>Madwifi needs a network bridge to work. Fortunately for me, the atheros card has its bridge automatically set up when the interface is mounted. The card gets the ath0 interface, while the bridge is labelled wifi0.  </p>\n<p>With this card, I had to add an autocreate=ap option in modprobe. To do that :<br>\n<code>echo \"options ath_pci autocreate=ap\" | sudo tee /etc/modprobe.d/ath_pci</code>  </p>\n<p>Then I set ath0 in /etc/network/interfaces :<br>\n<code>$ cat /etc/network/interfaces auto lo iface lo inet loopback auto eth0 iface eth0 inet dhcp auto ath0 iface ath0 inet static wireless-mode master wireless-channel 9 wireless-essid jonah address 192.168.1.254 netmask 255.255.255.0 broadcast 192.168.1.255</code>  </p>\n<p>I get my Internet connection through a router with a DHCP server on eth0.  </p>\n<p>Reload your network after you're done setting it up :<br>\n<code>sudo /etc/init.d/networking restart</code>  </p>\n<h2>Setting up hostapd</h2>\n<p>Hostapd allows to set up a nice wifi access point with WPA authentication in a short time. Here is my configuration (ignoring empty and commented lines) :<br>\n<code>cat /etc/default/hostapd | grep -v \"^\\(#\\|$\\)\" RUN_DAEMON=\"yes\" DAEMON_CONF=\"/etc/hostapd/hostapd.conf\" $ sudo cat /etc/hostapd/hostapd.conf | grep -v \"^\\(#\\|$\\)\" interface=ath0 bridge=wifi0 driver=madwifi logger_syslog=-1 logger_syslog_level=2 logger_stdout=-1 logger_stdout_level=2 debug=0 dump_file=/tmp/hostapd.dump ctrl_interface=/var/run/hostapd ctrl_interface_group=0 ssid=jonah hw_mode=g channel=60 beacon_int=100 dtim_period=2 max_num_sta=255 rts_threshold=2347 fragm_threshold=2346 macaddr_acl=0 auth_algs=3 ignore_broadcast_ssid=0 wme_enabled=1 wme_ac_bk_cwmin=4 wme_ac_bk_cwmax=10 wme_ac_bk_aifs=7 wme_ac_bk_txop_limit=0 wme_ac_bk_acm=0 wme_ac_be_aifs=3 wme_ac_be_cwmin=4 wme_ac_be_cwmax=10 wme_ac_be_txop_limit=0 wme_ac_be_acm=0 wme_ac_vi_aifs=2 wme_ac_vi_cwmin=3 wme_ac_vi_cwmax=4 wme_ac_vi_txop_limit=94 wme_ac_vi_acm=0 wme_ac_vo_aifs=2 wme_ac_vo_cwmin=2 wme_ac_vo_cwmax=3 wme_ac_vo_txop_limit=47 wme_ac_vo_acm=0 eapol_key_index_workaround=0 eap_server=0 own_ip_addr=127.0.0.1 wpa=1 wpa_passphrase=My Great Passphrase for the Access Point wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP wpa_group_rekey=600 wpa_gmk_rekey=86400</code>  </p>\n<p>Don't forget to change the passphrase (up to 63 characters).  </p>\n<p>Check your conf with :<br>\n<code>sudo hostapd -dd /etc/hostapd/hostapd.conf</code>  </p>\n<p>I had problems there at first because my card wasn't able to be set to master mode (ap, see above for the modprobe.d hack). To set your card to master mode without rebooting, reload the driver :<br>\n<code>sudo modprobe -r ath_pci sudo modprobe ath_pci autocreate=ap</code>  </p>\n<p>If hostapd works fine, you can launch it in the background :<br>\n<code>sudo /etc/init.d/hostapd start</code>  </p>\n<p>Once hostapd is set up, you should be able to connect to your AP by providing a manual IP address (in the right network). But you might want to go further, and set your AP as a DHCP server and router. These next steps are compiled from <a href=\"http://ubuntuforums.org/archive/index.php/t-335465.html\">a post on Ubuntu Forums</a>.  </p>\n<h2>Setting up DHCP and DNS</h2>\n<p>The first thing you want is a DHCP server. Since the post on Ubuntu Forums gets us to install dnsmasq to forward the DNS requests, and that dnsmasq can also be used as a DHCP server, I didn't see the use for dhcp3-server, so I deactivated it. Here is my configuration for dnsmasq :<br>\n<code>$ sudo cat /etc/default/dnsmasq | grep -v \"^\\(#\\|$\\)\" ENABLED=1 $ sudo cat /etc/dnsmasq.conf | grep -v \"^\\(#\\|$\\)\" dhcp-range=192.168.1.50,192.168.1.150,12h</code>  </p>\n<p>Pretty easy, no? This will simply forward the DNS requests to the DNS servers listed in /etc/resolv.conf, and provide a very basic DHCP server serving addresses from 192.168.1.50 to 192.168.1.150.  </p>\n<p>We can now restart dnsmasq with :<br>\n<code>sudo /etc/init.d/dnsmasq restart</code>  </p>\n<p>This step should now let you connect to your AP without setting a static IP (using knetworkmanager for example). But you won't get really far from there, until you set up the NAT.  </p>\n<h2>Setting up the NAT</h2>\n<p>This is the last step, setting up the NAT so that your AP can work as a router.  </p>\n<p>First, add IP forwarding settings to your sysctl.conf (to have it at next reboot) and activate it (for now) :<br>\n<code>sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE echo \"net.ipv4.ip_forward = 1\" | sudo tee -a /etc/sysctl.conf echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward\"</code>  </p>\n<p>Then set ipmasq to start up after services are started. This way, ipmasq will manage your iptables firewall after the interfaces are up and both dnsmasq and hostapd are up :<br>\n<code>sudo dpkg-reconfigure dnsmasq</code>  </p>\n<p>Warning: if you have static firewall rules, you should add them to the ipmasq settings (see \"man ipmasq for more details).  </p>\n<p>Restart your AP host to make sure it still works after a reboot and enjoy.</p>"},{"url":"/posts/20090417-blogspot-welcome-dinah-salome/","relativePath":"posts/20090417-blogspot-welcome-dinah-salome.md","relativeDir":"posts","base":"20090417-blogspot-welcome-dinah-salome.md","name":"20090417-blogspot-welcome-dinah-salome","frontmatter":{"title":"Welcome, Dinah Salomé!","template":"post","date":"2009-04-17T08:41:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2009/04/welcome-dinah-salome.html","blogspot_url":"https://raphink.blogspot.com/2009/04/welcome-dinah-salome.html","tags":["dinah","family"]},"html":"<p>Our daughter is born! Her name is Dinah Salomé, and she weights 2.940 kg. She was born on the 14th of April at 15:15, in Grasse. We thank God for this wonderful gift, and we thank you all for your prayers and encouragements!  </p>\n<p>Here are a few videos of her.</p>"},{"url":"/posts/20090427-blogspot-on-spiritual-authority-of-men/","relativePath":"posts/20090427-blogspot-on-spiritual-authority-of-men.md","relativeDir":"posts","base":"20090427-blogspot-on-spiritual-authority-of-men.md","name":"20090427-blogspot-on-spiritual-authority-of-men","frontmatter":{"title":"On the spiritual authority of men","template":"post","date":"2009-04-27T10:23:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2009/04/on-spiritual-authority-of-men.html","blogspot_url":"https://raphink.blogspot.com/2009/04/on-spiritual-authority-of-men.html","tags":["authority","bible","english","faith","family","prayer"]},"html":"<p>Those of you who know me personaly probably know my opinion on the necessary complementarity of the roles of men and women in a couple. These roles are not only practical (working vs. staying at home for example), they are mostly spiritual: the apostle Paul is clear on the fact that men are to be spiritual leaders.  </p>\n<p>Yesterday, we had the opportunity to experience not only the leadership of men in a couple, but also the authority associated with it.  </p>\n<p>Since our daughter Dinah was born, Jimena and I are more and more able to identify her various ways of crying. There's two main ways of crying I can identify so far: crying for food, and crying because she is cold.  </p>\n<p>When Dinah is hungry, it is quite easy to tell. She does cry, but she also opens her mouth, sticks her tongue between her lips, and tries to suck her hand, or anything that passes close to her mouth.  </p>\n<p>When we change her, she often gets cold and cries. This is a very loud cry of anger, associated with trembling, and it stops as soon as she has her pyjamas on.  </p>\n<p>Yesterday evening, as I entered our bedroom, Dinah was crying. She was in her crib, had just been breastfed, and had no apparent reason to cry. It was obvious that she wasn't crying for food, and she wasn't cold either. It was more of an awkward kind of crying. I got to her crib and rocked it for a few minutes. She stopped crying. I went to the other side of the bed, and she began to cry again. I went back to her crib, but this time, I felt called to pray, so I suggested to Jimena that we all prayed together before sleeping. As soon as I began to pray, Dinah stopped crying and peace settled in the room. We had possibly the most quiet and peaceful night since Dinah was born.  </p>\n<p>Just before we slept, Jimena told me she had been praying with Dinah before I entered the room. Each time she prayed, Dinah stopped crying, but each time she stopped praying, Dinah would cry again. It was only when I prayed with Jimena and Dinah that she stopped crying for the rest of the night.  </p>\n<p>A lot of women in Christian couples have an active prayer life, and it is often harder for men to spend time in prayer for their family. I have been blessed a lot in the past by the time Jimena spends in prayer for our couple, but yesterday was a lesson on the spiritual authority of the head of the family. This authority doesn't have to do with the personality of the man, it has to do with his spiritual position, as the representant of Christ in his household. The man is set as the head of his house, just like Christ is the head of the Church, and as such, he receives spiritual authority from Christ.  </p>\n<p>Jimena confirmed this from past experiences. For example, she witnessed of a great peace in the house after I decided to attend the men's group at Church in the morning.  </p>\n<p>I was touched yesterday when I read from 2 Kings 2 about Elishah asking Elijah to let him have a double part of his spirit. Elishah received part of Elijah's spirit when Elijah was taken up, and all the prophets could witness of it because Elishah could open the Jordan with Elijah's coat.  </p>\n<p>There is a reality in the delegation of spiritual authority. We can also see it in the Gospel as Jesus sends His disciples to chase demons and heal in His name.  </p>\n<p>Even though it is tempting to rely on the prayer life of our wives, it is the responsibility of a man to practice this spiritual authority in his family. Doing so makes a great difference.</p>"},{"url":"/posts/20090428-blogspot-two-years-two-weeks/","relativePath":"posts/20090428-blogspot-two-years-two-weeks.md","relativeDir":"posts","base":"20090428-blogspot-two-years-two-weeks.md","name":"20090428-blogspot-two-years-two-weeks","frontmatter":{"title":"Two years, two weeks","template":"post","date":"2009-04-28T18:56:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2009/04/two-years-two-weeks.html","blogspot_url":"https://raphink.blogspot.com/2009/04/two-years-two-weeks.html","tags":["dinah","english","family"]},"html":"<p>Like the two little tickers on the right side of my blog show, today is a special day for us. Jimena and I are celebrating our two years of marriage, and we also celebrate the two weeks of our daughter Dinah Salomé!  </p>\n<p>So today is the day of twos: two anniversaries, one for two years, one for two weeks :-)</p>"},{"url":"/posts/20090520-blogspot-diathica-bible-bot/","relativePath":"posts/20090520-blogspot-diathica-bible-bot.md","relativeDir":"posts","base":"20090520-blogspot-diathica-bible-bot.md","name":"20090520-blogspot-diathica-bible-bot","frontmatter":{"title":"Diathica Bible bot","template":"post","date":"2009-05-20T14:17:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2009/05/diathica-bible-bot.html","blogspot_url":"https://raphink.blogspot.com/2009/05/diathica-bible-bot.html","tags":["bible","christian","identica","microblogging","perl","ubuntu"]},"html":"<p>I have begun to do microblogging again, this time on <a href=\"http://identi.ca/\">identi.ca</a>. As I wanted to play a bit with the API, I coded a Bible bot using <a href=\"http://www.crosswire.org/sword/diatheke/\">diatheke</a> and <a href=\"http://search.cpan.org/%7Ecthom/Net-Twitter-2.12/lib/Net/Identica.pm\">Net::Identica</a>.  </p>\n<p>The source is on <a href=\"https://launchpad.net/diathica\">Launchpad</a> and the bot is currently running on one of my machines, with the <a href=\"http://identi.ca/votd\">@votd account on identi.ca</a>. Have a try! You can look at the replies of the @votd account to have an idea of what you can ask it to do.</p>"},{"url":"/posts/20090428-blogspot-minimal-dependencies-and-backports/","relativePath":"posts/20090428-blogspot-minimal-dependencies-and-backports.md","relativeDir":"posts","base":"20090428-blogspot-minimal-dependencies-and-backports.md","name":"20090428-blogspot-minimal-dependencies-and-backports","frontmatter":{"title":"Minimal dependencies and backports","template":"post","date":"2009-04-28T09:55:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2009/04/minimal-dependencies-and-backports.html","blogspot_url":"https://raphink.blogspot.com/2009/04/minimal-dependencies-and-backports.html","tags":["backports","debian","linux","package","packaging","sysadmin","ubuntu"]},"html":"<p>I've been packaging for a production environment for nearly three years now. We have a lot of machines using old systems, such as sarge and etch, and my work is often to backport new software to these OSes. While the work is fun, I often find myself fighting with dependencies, and especially with debhelper build dependencies.  </p>\n<p>For example, I find more and more Perl packages in Debian that depend on debhelper 7. I tried to backport debhelper 7 for etch, but it pulled all sorts of things with it, all the way to dpkg-dev, which messed up the whole system, so I gave up. In this case, I have to end up upgrading debhelper 5 packages to newer upstream versions when I need, since using debhelper 7 is virtually impossible in sarge and etch.  </p>\n<p>In other cases, I find packages that build depend on debhelper 6 and have \"6\" in debian/compat. Changing the values to \"5\" doesn't prevent the build, so all that is to do is change the value back to \"5\".  </p>\n<p>Maybe it's me not understanding the concept of minimal dependency and compatibility, but I really don't get why it is a rule to keep bumping debhelper compability (and often, other libs too) when there is absolutely no need for it. If a program works with Perl 5.4, why make people believe it requires Perl 5.8 to run? There are still people running Perl 5.4 on old machines somewhere, who would be happy to not have to change the minimal dependencies to use a program that would run fine on their machines.  </p>\n<p>I understand very well that package development in the community is centered on new versions and that it's just easier to \"force\" people to upgrade their systems altogether (remember the \"mysql-server\" meta-packages magically upgrading from MySQL 4.1 to MySQL 5.0 in etch ?), but if Debian and Ubuntu want to be enterprise-class OSes, I think we ought to adapt better to the reality of production environments, and that includes making it easier for sysadmins to manage old systems without depending on new tools.  </p>\n<p>I'd be very happy to hear from the experience of other sysadmins in production environments regarding this issue.</p>"},{"url":"/posts/20090610-blogspot-speedtest-from-home/","relativePath":"posts/20090610-blogspot-speedtest-from-home.md","relativeDir":"posts","base":"20090610-blogspot-speedtest-from-home.md","name":"20090610-blogspot-speedtest-from-home","frontmatter":{"title":"Speedtest","template":"post","date":"2009-06-10T23:30:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2009/06/speedtest-from-home.html","blogspot_url":"https://raphink.blogspot.com/2009/06/speedtest-from-home.html","tags":["meme","ubuntu"]},"html":"<h2>From home...</h2>\n<p><img src=\"http://www.speedtest.net/result/493074355.png\">  </p>\n<h2>and from work</h2>\n<p><img src=\"http://www.speedtest.net/result/493495339.png\"></p>"},{"url":"/posts/20090527-blogspot-chernobyl-third-angel/","relativePath":"posts/20090527-blogspot-chernobyl-third-angel.md","relativeDir":"posts","base":"20090527-blogspot-chernobyl-third-angel.md","name":"20090527-blogspot-chernobyl-third-angel","frontmatter":{"title":"Chernobyl - the third angel","template":"post","date":"2009-05-27T23:57:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2009/05/chernobyl-third-angel.html","blogspot_url":"https://raphink.blogspot.com/2009/05/chernobyl-third-angel.html","tags":["bible","english","nuclear","prophecy","revelations"]},"html":"<p>I'm not too much into the book of Revelations, the end times and trying to guess which prophecy applies to which recent events, but today, I stumbled upon something I found very interesting.  </p>\n<p>I was reading about the Chernobyl area on <a href=\"http://www.kiddofspeed.com/chernobyl-revisited/\">Elena Filatova's website</a> and there she mentioned Revelations 8.10-11. I went to look this passage up, and this is what it says (KJV) :  </p>\n<blockquote>\n<p><br>\nAnd the third angel sounded, and there fell a great star from heaven, burning as it were a lamp, and it fell upon the third part of the rivers, and upon the fountains of waters;  </p>\n<p>And the name of the star is called Wormwood: and the third part of the waters became wormwood; and many men died of the waters, because they were made bitter.  </p>\n</blockquote>\n<p>OK, this is a prophecy about the end times from the book of Revelations. Now, what does it have to do with Chernobyl? In the Ukrainian language, \"chernobyl\" means \"wormwood\" or \"absinth\". A coïncidence? Let's look a bit further.  </p>\n<h2>The name Chernobyl</h2>\n<p>The little town of Chernobyl in Ukraine was probably named this way because it is located in an area that is full of absinth forests. Now that nature has taken back its right in this region after the incident of 1986, Elena Filatova reports that the wormwood forests are growing a lot around Chernobyl.  </p>\n<p>Maybe you don't remember what happened in Chernobyl in 1986, so let me refresh your mind. On the 25th of April 1986, one of the reactors of the nuclear plant in Chernobyl, Ukraine, exploded, freeing into the air tons of radioactive particles that were carried all throughout Europe in the weeks that followed. I recommend watching <a href=\"http://www.youtube.com/view_play_list?p=2FF1EDE7A43CF9A0\">this movie</a> if you want to learn more about this.  </p>\n<h2>Bitter waters</h2>\n<p>The word \"wormwood\" is used quite a few times in the Bible, mostly in the Old Testament. It is often used to picture bitterness or poison, especially associated with water.  </p>\n<p>What happened in Chernobyl has to do with both. If you watched the movie linked above, you know that the nuclear plant in Chernobyl was built on the border of a small lake, and the river Pripyat', which then flows into the Dniepper, which in turn flows into the Black Sea. After the explosion of the reactor, the radioactive magma found itself dripping under the plant towards the water. As a result, the water was poisoned by the radioactive particles, which were then carried by the river.  </p>\n<p>Another effect was due to the radioactive cloud that resulted from the explosion. Tons of radioactive particles were released into the air and were taken all throughout Europe by the winds. When these particles fell on the ground again, they infected sources and rivers, making them bitter, so to say.  </p>\n<h2>A burning lamp</h2>\n<p>In the movie linked above, you could look at time index 1:50, which gives an idea of how the whole thing looked. This is obviously not an original document, but it is likely to have been made from people's testimonies. The technician talking right after this passage explains that it was very beautiful, like a glowing rainbow. The image that we see at time index 1:50 resembles a huge burning lamp.  </p>\n<p>Like I said, I'm not much into prophecies, but I find that all this is very accurate. This is all too likely to not call my attention to it.</p>"},{"url":"/posts/20091009-blogspot-authentication-on-both-postgresql-and/","relativePath":"posts/20091009-blogspot-authentication-on-both-postgresql-and.md","relativeDir":"posts","base":"20091009-blogspot-authentication-on-both-postgresql-and.md","name":"20091009-blogspot-authentication-on-both-postgresql-and","frontmatter":{"title":"Authentication on both PostgreSQL and LDAP in Gforge","template":"post","date":"2009-10-09T11:57:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2009/10/authentication-on-both-postgresql-and.html","blogspot_url":"https://raphink.blogspot.com/2009/10/authentication-on-both-postgresql-and.html","tags":["debian","gforge","ldap","nss","postgresql","sysadmin","ubuntu"]},"html":"<p>I am currently working on migrating a Gforge platform from authenticating on the local PostgreSQL to a centralized LDAP server.  </p>\n<p>Apart from setting up PAM-LDAP (Martin Owens can tell you <a href=\"http://doctormo.wordpress.com/2009/10/08/ldap-pam-users/\">all about the mess it is</a>), the fun thing was to have some users authenticate on PostgreSQL and others on LDAP during the time of the migration.  </p>\n<p>Using a modified version of the LDAP plugin for Gforge, users are prompted for their LDAP login and password when they log in, and we fill a <em>plugin\\</em>ldapextauth_users_ table with the Gforge IDs of the migrated users. Then comes the fun part. Users that are not yet migrated have to be able to log in using their Gforge login, but not their LDAP login. Users that are migrated have to be able to use their LDAP login, but their Gforge login should fail.  </p>\n<p>Note: This post is not a tutorial on how to set up libnss-pgsql2 or libnss-ldap. I won't be explaining these.  </p>\n<p>This is the solution I've come to. The machines are running on Debian Etch.  </p>\n<h2>In /etc/nsswitch.conf</h2>\n<p>passwd: compat pgsql ldap<br>\ngroup:  compat pgsql ldap<br>\nshadow: compat pgsql ldap  </p>\n<p>PostgreSQL is tried before ldap, but must fail when the user has been migrated.  </p>\n<h2>In /etc/nss-pgsql.conf and /etc/nss-pgsql-root.conf</h2>\n<h1>getpwnam        = SELECT login AS username,passwd,gecos,('/var/lib/gforge/chroot/home/users/' || login) AS homedir,shell,uid,gid FROM nss_passwd WHERE login = $1</h1>\n<p>getpwnam        = SELECT nss.login AS username,nss.passwd,nss.gecos,('/var/lib/gforge/chroot/home/users/' || login) AS homedir,nss.shell,nss.uid,nss.gid FROM nss_passwd nss JOIN users ON users.user_name=nss.login LEFT JOIN plugin_ldapextauth_users ldap ON users.user_id=ldap.user_id WHERE ldap.user_id IS NULL AND login = $1;  </p>\n<h1>shadowbyname    = SELECT login AS shadow_name, passwd AS shadow_passwd, 14087 AS shadow_lstchg, 0 AS shadow_min, 99999 AS shadow_max, 7 AS shadow_warn, '' AS shadow_inact, '' AS shadow_expire, '' AS shadow_flag FROM nss_passwd WHERE login = $1 AND ldap = 0</h1>\n<p>shadowbyname     = SELECT nss.login AS shadow_name,nss.passwd AS shadow_passwd,14087 AS shadow_lstchg, 0 AS shadow_min, 99999 AS shadow_max, 7AS shadow_warn, '' AS shadow_inact, '' AS shadow_expire, '' AS shadow_flag FROM nss_passwd nss JOIN users ON users.user_name=nss.login LEFT JOIN plugin_ldapextauth_users ldap ON users.user_id=ldap.user_id WHERE ldap.user_id IS NULL AND login = $1;  </p>\n<p>This requires to GRANT SELECT access to tables <em>users</em> and <em>plugin\\</em>ldapextauth_users_ to user <em>gforge\\</em>nss<em>. As a result, the request fails if the user id is found in the _plugin\\</em>ldapextauth_users_ table, so it goes to try LDAP instead.  </p>\n<h2>In /etc/pam.d/common-auth</h2>\n<p># pam_unix fails permanently when users are migrated<br>\nauth sufficient pam_unix.so nullok_secure<br>\n# Filter accounts that are not migrated<br>\n# Since pam_unix fails for migrated accounts,<br>\n#  uid then comes from ldap<br>\nauth required   pam_succeed_if.so uid > 60000<br>\n# Note: try_first_pass will prompt for LDAP password<br>\n#  if provided password failed<br>\nauth required   pam_ldap.so try_first_pass  </p>\n<p>This is the only thing I needed to modify in /etc/pam.d. Gforge accounts use the 20xxx uid range, while our LDAP uses 60xxx, hence the pam_succeed_if.so condition to filter LDAP accounts only. This works because the uid that is returned by \"getent passwd $user\" corresponds to the one in the LDAP when a user is migrated, since the PostgreSQL query fails in that case (see /etc/nss-pgsql*.conf).  </p>\n<h2>Chowning homes</h2>\n<p>When migrating users, uids change, and sometimes even login names. I've chosen to use inotify (incron) to fix that issue. When a user is migrated in the web interface, it drops a file in /var/lib/gforge/ldap_users named after the Gforge user name, which contains the LDAP user name.  </p>\n<p>Incron watches /var/lib/gforge/ldap_users with the following conf:<br>\n/var/lib/gforge/ldap_users IN_CREATE /usr/lib/gforge-dop-pamldap/chown_home.sh $# $@  </p>\n<p>The chown_home.sh script then achieves the following tasks:  </p>\n<ul>\n<li>Symlink the new home directory (/home/$ldap_user) to the old one (/var/lib/gforge/chroot/home/users/$gforge_user)</li>\n<li>Chown the directory with the new user (chown $ldap_user. $home)</li>\n</ul>\n<h2>Remaining issues</h2>\n<p>The main remaining issue is groups. The Gforge database uses the login to map users to groups, instead of the Gforge id, so when the Gforge login is different from the LDAP login, migrated users currently lose their groups. The easiest option is probably to change all occurrences of the login with the LDAP login in all group tables, but it's a bit violent...</p>"},{"url":"/posts/20091012-blogspot-dynamic-presentations-with-jessyink/","relativePath":"posts/20091012-blogspot-dynamic-presentations-with-jessyink.md","relativeDir":"posts","base":"20091012-blogspot-dynamic-presentations-with-jessyink.md","name":"20091012-blogspot-dynamic-presentations-with-jessyink","frontmatter":{"title":"Dynamic presentations with Jessyink","template":"post","date":"2009-10-12T21:38:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2009/10/dynamic-presentations-with-jessyink.html","blogspot_url":"https://raphink.blogspot.com/2009/10/dynamic-presentations-with-jessyink.html","tags":["inkscape","presentation","ubuntu"]},"html":"<p>Some time ago, I attended a presentation made with <a href=\"http://prezi.com/\">Prezi</a> and I was pretty impressed. As I looked into it, a few things bugged me though:  </p>\n<ul>\n<li>it's in flash</li>\n<li>I can't really share my presentations with anyone</li>\n<li>I have to design my slides online, or use an offline app (paid) which will connect online</li>\n<li>There is no good support for full screen presentations in Linux</li>\n<li>It doesn't use a portable format</li>\n</ul>\n<p>From looking at the features, I really couldn't see why it couldn't be implemented using SVG (except for the flash movies embedded in the presentation maybe...), so I began to search for a project that would do the same, using SVG, and I found Jessyink.  </p>\n<p><a href=\"https://launchpad.net/jessyink\">Jessyink</a> is exactly what I was searching for. It's a plugin for Inkscape which adds the possibility to design slides from an SVG, and ships a javascript library inside the resulting SVG for execution. The result is that:  </p>\n<ul>\n<li>It's portable: apps that understands SVG and javascript can play it (e.g. Firefox, on any platform. Arora did fine too, but Konqueror wouldn't work)</li>\n<li>It's an open format, with open standards: I can send the svg to someone, and they can edit it with inkscape using the Jessyink plugin</li>\n<li>The possibilities are endless: I'm not limited to styles and fonts found on prezi.com, I can choose whatever font I want, whatever style I want</li>\n<li>It's open-source!</li>\n<li>It not only supports prezi-like effects inside slides, but also traditional slides, that can each behave like prezi-like presentations</li>\n</ul>\n<p>Jessyink is harder to use than prezi.com though, but if you know how to use Inkscape, you will soon be able to do what you want with it.  </p>\n<p>Here is a <a href=\"http://r.pinson.free.fr/jessyink/mapnice.svg\">quick &#x26; dirty example</a> I made with Jessyink. Some tips:  </p>\n<ul>\n<li>Use the arrows to navigate through the presentation</li>\n<li>Use the 'i' key to get an index of the slides (two slides in that case)</li>\n<li>Use the 'd' key to draw on the presentation as you go through it!</li>\n<li>See <a href=\"http://code.google.com/p/jessyink/wiki/keyBindings\">this page</a> for more tips!</li>\n</ul>"},{"url":"/posts/20100603-blogspot-flammard-v2-is-out/","relativePath":"posts/20100603-blogspot-flammard-v2-is-out.md","relativeDir":"posts","base":"20100603-blogspot-flammard-v2-is-out.md","name":"20100603-blogspot-flammard-v2-is-out","frontmatter":{"title":"Flammard v2 is out","template":"post","date":"2010-06-03T14:38:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2010/06/flammard-v2-is-out.html","blogspot_url":"https://raphink.blogspot.com/2010/06/flammard-v2-is-out.html","tags":["bible","christian","flammard","javascript","python","release","ubuntu","wave"]},"html":"<p>After 6 months of loyal services, it was time for the Flammard Bible bot version 1 to leave.  </p>\n<p>Several reasons pushed me to rewrite the bot:  </p>\n<ul>\n<li>I had begun to rewrite the underlying modules used by the bot and wanted to use them;</li>\n<li>I wanted to have a go at Natural Language Processing and get rid of all (or as many as possible) tags;</li>\n<li>Finally, the robot API for Google Wave was updated, leaving me with a code that depended on an obsolete API. That was the spark to get me started;</li>\n</ul>\n<p>In short, version 2 of the Bible bot:  </p>\n<ul>\n<li>Has no '<blah>', 'v/blah/' or 'vl/blah/' tags. It just recognizes Bible references in the text and processes them automatically. The only bit of a tag that persists is 'q' or 'quote' in front of a verse reference, such as 'quote gen 1:3', which tells the bot to quote the verse instead of just making it a link to an external website;</li>\n<li>Comes with a Wave gadget to configure it which replaces the '<bible>' and '<pref>' tags.</li>\n</ul>\n<p>Two pretty much unused functionalities were dropped in version 2:  </p>\n<ul>\n<li>The '<votd>' tag has no equivalent. My logs showed that nobody was using it. If you still want it, ping me. I might replace it with a Wave gadget once I expose the Flammard API publicly;</li>\n<li>It is no longer possible to set the default Bible version per language, you can only set one default version per Wave.</li>\n</ul>\n<p>You can install the bot by visiting <a href=\"https://wave.google.com/wave/#minimized:nav,minimized:contact,minimized:search,restored:wave:googlewave.com!w%252BCAl2tcajD.4\">this wave</a>.</p>"},{"url":"/posts/20091114-blogspot-wave-bible-bot/","relativePath":"posts/20091114-blogspot-wave-bible-bot.md","relativeDir":"posts","base":"20091114-blogspot-wave-bible-bot.md","name":"20091114-blogspot-wave-bible-bot","frontmatter":{"title":"Wave Bible Bot","template":"post","date":"2009-11-14T13:38:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2009/11/wave-bible-bot.html","blogspot_url":"https://raphink.blogspot.com/2009/11/wave-bible-bot.html","tags":["bible","christian","chrétien","flammard","open-source","python","ubuntu","wave"]},"html":"<p>After a month of using Google Wave, I finally made my first Wave bot. My initial idea was to make a Bible bot using the sword API, but I couldn't find an easy way of doing that in python, and I am very reluctant to use Java. So instead, I did a python bot which parses BibleGateway.com to retrieve the results. Later, I added modules to deal with other translations, such as ESV, NET and specific French versions such as Colombe, TOB or NBS.  </p>\n<h4>What does it do?</h4>\n<p>Flammard is a Bible bot for Google Wave. You can add it to a wave, and it will replace some specific patterns with other content.  </p>\n<p>Currently, it recognizes two kinds of patterns:  </p>\n<ul>\n<li>&#x3C;verse verseReference [from Version]> is replaced by the verse. The version is optional. Examples: &#x3C;verse Gen 1:1 from NIV> or &#x3C;verse Acts 3:5-15>  </li>\n<li>&#x3C;verselink verseReference [from Version]> is replaced by a link to the verse on another website. The version is optional. Examples: &#x3C;verselink Gen 1:1 from NIV> or &#x3C;verselink Acts 3:5-15>  </li>\n</ul>\n<h4>Known versions</h4>\n<p>The Bible bot uses several resources in order to support as many Bible versions as possible.  </p>\n<p>The versions currently supported are:  </p>\n<ul>\n<li>All versions from <a href=\"http://www.biblegateway.com/\">BibleGateway.com</a>, referenced by their code (e.g. KJV, NIV, LSG) or their ID (the full list can be found <a href=\"http://www.biblegateway.com/usage/linking/versionslist.php\">on this page</a>);  </li>\n<li>The <a href=\"http://www.gnpcb.org/esv/\">ESV (English Standard Version) Bible</a>;  </li>\n<li>The NET Bible, from <a href=\"http://bible.org/\">Bible.org</a>;  </li>\n<li>The BFC (La Bible en francais courant), PDV (Parole de Vie), Colombe (La Colombe), NBS (Nouvelle Bible de Segond), TOB (Traduction OEcumenique de la Bible) from <a href=\"http://lire.la-bible.net/\">La-Bible.net</a>.  </li>\n</ul>\n<p>In all cases, the version is optional. When possible, the bot will try to find the right version for your blip depending on the language you have typed in the context of the tag.  </p>\n<h4>Example</h4>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIC_N9xhvnB2NncAIJEqAqIjOISn5utGbJ8KtUCkE4sthPCJaGrl6Ga3NepAHY4TLW2XjQ35HwHhyphenhyphenGppzIY51NBB1kZ7jG4zItnyKSlKrIM2Lgxx9ZNLy-9OyrEdoPCUOh8I9H-BcBLXrG/s1600-h/wave_bible_bot1.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIC_N9xhvnB2NncAIJEqAqIjOISn5utGbJ8KtUCkE4sthPCJaGrl6Ga3NepAHY4TLW2XjQ35HwHhyphenhyphenGppzIY51NBB1kZ7jG4zItnyKSlKrIM2Lgxx9ZNLy-9OyrEdoPCUOh8I9H-BcBLXrG/s320/wave_bible_bot1.png\"></a>  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhmGPNI_bktFKCOi1GfdYADqUSg39-l4Kye9ck4rIqpXDhRRPB8cWcGHK41eBkPerl84F6Y46MJriX6fI_5pC8W61gsjDohCPSlkRIMKZnCyQK11TJjU8iyNngm6-2CBSE2jipg_1ibnUx/s1600-h/wave_bible_bot2.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhmGPNI_bktFKCOi1GfdYADqUSg39-l4Kye9ck4rIqpXDhRRPB8cWcGHK41eBkPerl84F6Y46MJriX6fI_5pC8W61gsjDohCPSlkRIMKZnCyQK11TJjU8iyNngm6-2CBSE2jipg_1ibnUx/s320/wave_bible_bot2.png\"></a>  </p>\n<h4>Live demo</h4>\n<p>Here is a video demo of the bot working (as of today's functionalities):  </p>\n<h4>How can I add this bot to a wavelet ?</h4>\n<p>Simply add flammard@appspot.com as a participant to your wavelet.  </p>\n<h4>Where can I find the source code?</h4>\n<p>The project is hosted on Launchpad: <a href=\"https://launchpad.net/wavebiblebot\">https://launchpad.net/wavebiblebot</a>. You are welcome to report bugs and wishes, or to send patches.</p>"},{"url":"/posts/20041122-blogspot-chronicles-of-narnia/","relativePath":"posts/20041122-blogspot-chronicles-of-narnia.md","relativeDir":"posts","base":"20041122-blogspot-chronicles-of-narnia.md","name":"20041122-blogspot-chronicles-of-narnia","frontmatter":{"title":"The Chronicles of Narnia","template":"post","date":"2004-11-22T15:44:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2004/11/chronicles-of-narnia.html","blogspot_url":"https://raphink.blogspot.com/2004/11/chronicles-of-narnia.html","tags":["book","christian","english","multiply","restored","review"]},"html":"<p>Category:</p>\n<p>Books</p>\n<p>Genre:</p>\n<p>Childrens Books</p>\n<p>Author:</p>\n<p>C.S. Lewis</p>\n<p><img src=\"http://web.archive.org/web/20041207171041/http://www.smsjunior.net/Narnia/animalherd.jpg\"></p>\n<p><strong>N</strong>arnia is a wonderful series of books for children or adults.<br>\nC.S. Lewis writes great imaginative stories, very educational and constructive for the personality, gathering his experience, classical tales, the bible...  </p>\n<p>I'm definitely a C.S. Lewis fan !!</p>"},{"url":"/posts/20060310-blogspot-improving-my-blog/","relativePath":"posts/20060310-blogspot-improving-my-blog.md","relativeDir":"posts","base":"20060310-blogspot-improving-my-blog.md","name":"20060310-blogspot-improving-my-blog","frontmatter":{"title":"Improving my blog","template":"post","date":"2006-03-10T15:00:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2006/03/improving-my-blog.html","blogspot_url":"https://raphink.blogspot.com/2006/03/improving-my-blog.html","tags":["blog","cv","english"]},"html":"<p>So tonight I’ve been trying to improve my blog… Making it my main homepage, now hosted directly on <a href=\"http://web.archive.org/web/20060806182544/http://www.raphink.info/\">http://www.raphink.info</a> and redirected from <a href=\"http://web.archive.org/web/20060806182544/http://www.raphink.net/\">http://www.raphink.net</a> when my computer is online <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_smile.gif\" alt=\":)\"></p>\n<p>I added some pages, inluding professional and personal infos…</p>\n<p>All comments are welcome (especially on embedding the pdf CV …) <img src=\"http://web.archive.org/web/20060806182544/http://raphink.info/blog/wp-includes/images/smilies/icon_biggrin.gif\" alt=\":D\"></p>"},{"url":"/posts/20100611-blogspot-flammard-is-official-wave-extension/","relativePath":"posts/20100611-blogspot-flammard-is-official-wave-extension.md","relativeDir":"posts","base":"20100611-blogspot-flammard-is-official-wave-extension.md","name":"20100611-blogspot-flammard-is-official-wave-extension","frontmatter":{"title":"Flammard is an official Wave extension","template":"post","date":"2010-06-11T11:27:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2010/06/flammard-is-official-wave-extension.html","blogspot_url":"https://raphink.blogspot.com/2010/06/flammard-is-official-wave-extension.html","tags":["flammard","ubuntu","wave"]},"html":"<p>Flammard was approved in the Extensions gallery on Google Wave. This means you can now add it by going to \"Extensions -> All\" in the Navigation column.  </p>\n<p>Quite a few changes have been made to it in order to get it accepted, mostly on the gadget style. The gadget now has tabs and a nicer look.  </p>\n<p>Enjoy!</p>"},{"url":"/posts/20100917-blogspot-helping-young-children-learn-to-read/","relativePath":"posts/20100917-blogspot-helping-young-children-learn-to-read.md","relativeDir":"posts","base":"20100917-blogspot-helping-young-children-learn-to-read.md","name":"20100917-blogspot-helping-young-children-learn-to-read","frontmatter":{"title":"Helping young children learn to read","template":"post","date":"2010-09-17T18:00:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2010/09/helping-young-children-learn-to-read.html","blogspot_url":"https://raphink.blogspot.com/2010/09/helping-young-children-learn-to-read.html","tags":["books","children","edubuntu","education","english","open-source","reading","ubuntu","usborne"]},"html":"<p>Learning to read is a complex task. European children surely have it easier than many Asians who need to learn long series of ideograms, yet the association of signs and sounds is often difficult. In particular, English and French are two examples of European languages where children struggle to learn to read.  </p>\n<p><a href=\"http://farm4.static.flickr.com/3584/3291620563_cdfc280d25_z.jpg?zz=1\"><img src=\"http://farm4.static.flickr.com/3584/3291620563_cdfc280d25_z.jpg?zz=1\"></a></p>\n<p>In these two languages, and even more so in English, there are more sounds than available letters, so the language ends up with complex associations of letters to produce sounds, and it is not rare to find letters or associations of letters that are pronounced differently in different words, even though they are written the exact same way.</p>\n<p>This has led both English and French teachers of young children to develop new techniques to learn to read, based on recognizing whole words instead of phonemes. English teachers called it 'Look Say' (also known as '<a href=\"http://en.wikipedia.org/wiki/Sight_word\">Sight Word</a>' or 'Whole-Word') method, while in France it was called 'Méthode globale'. These methods were used extensively in the 80s, often with very mitigated results. While children were able to read common words, however complicated their phonemes were, they often lacked the skills to decipher words they had not yet learned, and those were many.</p>\n<p>The early 21st century has seen a return to reading basics, namely a revival of the 'Phonics' method, widely used before the era of the 'Look Say' last century. It is true that some words eventually have to be learned the global way, but most words can be learned phoneme by phoneme, if the method used is properly set. In the UK, the government has made a set of recommendations as to how this method should be implemented to teach young children to read.  </p>\n<p><a href=\"http://www.usborne.com/veryfirstreading/images/homepage/book-1-spread.jpg\"><img src=\"http://www.usborne.com/veryfirstreading/images/homepage/book-1-spread.jpg\"></a></p>\n<p>The British editor of children books <a href=\"http://www.usborne.com/\">Usborne</a> produces a series of 15 books called <a href=\"http://www.usborne-riviera.fr/2010/09/usborne-very-first-reading.html\">'Very First Reading'</a> to help children who are just starting to read, introducing letters by sets, as recommended by the <a href=\"http://www.letters-and-sounds.com/phase-2.html\">DfES Letters and Sounds phonics programe</a>. In the first books of the series, children are simply encouraged to complete the story read by an adult by reading simple words. As the child moves on in the series, the part read by the adult diminishes, until the whole story is read by the child alone.</p>\n<p><a href=\"http://www.jeuxpourlaclasse.fr/img/exe/gcompris.jpg\"><img src=\"http://www.jeuxpourlaclasse.fr/img/exe/gcompris.jpg\"></a>In the open-source world, we have good programs to help children start reading, too. <a href=\"http://gcompris.net/\">Gcompris</a> is a great example of such programs for young children, and it takes phonics into consideration in its modules.</p>\n<p>Do you teach your young children to read? What books or methods do you use for this?</p>"},{"url":"/posts/20100918-blogspot-getting-block-position-in-javascript/","relativePath":"posts/20100918-blogspot-getting-block-position-in-javascript.md","relativeDir":"posts","base":"20100918-blogspot-getting-block-position-in-javascript.md","name":"20100918-blogspot-getting-block-position-in-javascript","frontmatter":{"title":"Getting block position in Javascript with Chrome","template":"post","date":"2010-09-18T10:48:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2010/09/getting-block-position-in-javascript.html","blogspot_url":"https://raphink.blogspot.com/2010/09/getting-block-position-in-javascript.html","tags":["chrome","javascript","ubuntu","usborne","website"]},"html":"<p>Dear Lazyweb,  </p>\n<p>Yesterday, I had fun implementing a little ducky that hides on the pages of <a href=\"http://www.usborne-riviera.fr\">this website</a>. To my distress, I ended up having to hardcode a few things, since I couldn't get Chrome to properly give me the real position of relative blocks. After reading quite a few things on the subject, I went the parentOffset recursive way, but it just doesn't seem to work.  </p>\n<p>I've also posted on StackOverflow <a href=\"http://stackoverflow.com/questions/3741056/get-real-position-of-objects-in-javascript-with-chrome\">here</a> if you feel like answering me there.  </p>\n<p>Does anyone know of a cross-browser way of getting real block positions that actually works?</p>"},{"url":"/posts/20101004-blogspot-matching-mac-address/","relativePath":"posts/20101004-blogspot-matching-mac-address.md","relativeDir":"posts","base":"20101004-blogspot-matching-mac-address.md","name":"20101004-blogspot-matching-mac-address","frontmatter":{"title":"Matching a mac address","template":"post","date":"2010-10-04T22:52:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2010/10/matching-mac-address.html","blogspot_url":"https://raphink.blogspot.com/2010/10/matching-mac-address.html","tags":["regexp","ubuntu"]},"html":"<p>If it's of any use to someone, here is a regexp that matches mac addresses:  </p>\n<p>/[0-9A-Fa-f]{2}(?P<sep>[-:])[0-9A-Fa-f]{2}(?:(?P=sep)[0-9A-Fa-f]{2}){4}/</p>\n<p>Example usage:  </p>\n<p>$ python</p>\n<blockquote>\n<blockquote>\n<blockquote>\n<p>reg = re.compile('[0-9A-Fa-f]{2}(?P<sep>[-:])[0-9A-Fa-f]{2}(?:(?P=sep)[0-9A-Fa-f]{2}){4}')\nmac1 = \"00:af:15:35:48:5e\"\nmac2 = \"00-aa-bb-cc-dd-ee\"\nmac3 = \"00-aa-bb-cc-dd:ee\"\nreg.match(mac1).group(0)\n'00:af:15:35:48:5e'\nreg.match(mac2).group(0)\n'00-aa-bb-cc-dd-ee'\nreg.match(mac3).group(0)\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nAttributeError: 'NoneType' object has no attribute 'group'\nreg.match(mac1).group('sep')\n':'\nreg.match(mac2).group('sep')\n'-'</p>\n</blockquote>\n</blockquote>\n</blockquote>\n<p>Do you use another regexp to match mac addresses?</p>"},{"url":"/posts/20101218-blogspot-spams-of-new-kind-this-one-might/","relativePath":"posts/20101218-blogspot-spams-of-new-kind-this-one-might.md","relativeDir":"posts","base":"20101218-blogspot-spams-of-new-kind-this-one-might.md","name":"20101218-blogspot-spams-of-new-kind-this-one-might","frontmatter":{"title":"Spams of a new kind: this one might deserve a visit to the police","template":"post","date":"2010-12-18T20:03:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2010/12/spams-of-new-kind-this-one-might.html","blogspot_url":"https://raphink.blogspot.com/2010/12/spams-of-new-kind-this-one-might.html","tags":["spam","ubuntu"]},"html":"<p>Today, I received this nice spam (which was not flagged by the spam detecting system actually):  </p>\n<blockquote>\n<p>from</p>\n<p> Pat Wages </p>\n<p>to</p>\n<p>date</p>\n<p> Sat, Dec 18, 2010 at 3:07 PM</p>\n<p>subject</p>\n<p> ASSASIN I HAVE BEEN PAYED TO TERMINATE YOU.</p>\n<p>signed-by</p>\n<p> sbcglobal.net</p>\n<p>This is the only way I could contact you for now, I want you to be very careful about this  and keep this secret with you until I make out space for us to see. You have no need of knowing who I am or where I am from. I know this may sound very surprising to you but it's the situation. I have been paid some ransom in advance to terminate you with some reasons listed to me by my employer. It's someone I believe you call a friend; I have followed you closely for a while now and have seen that you are innocent of the accusations he leveled against you. Do not contact the police or try to send a copy of this to them, because if you do, I will know, and I might be pushed to do what I have been paid to do. Besides, this is the first time I turn out to be a betrayer in my job. I took pity on you, that is why I have made up my mind to help you if you are willing to help yourself.  </p>\n<p>Now listen, I will arrange for us to see face to face, but before that, I need $9,000. I will come to your home or you determine where you wish we meet; I repeat, do not arrange for the cops and if you play hard to get, it will be extended to your family. Do not set any camera to cover us or set up any tape to record our conversation; my employer is in my control now. Payment details will be provided for you to make a part payment of $3,000 first, which will serve as guarantee that you are ready to co-operate, then I will post a copy of the video tape that contains his request for me to terminate you which will be enough evidence for you to take any legal action against him before he employs another person for the job. You will pay the balance of $3,000 once you receive the tape.  </p>\n<p>Warning; do not contact the police, make sure you stay indoors once it is 7.30pm every day  until this whole thing is sorted out, if you neglect any of these warnings, you will have yourself to blame. You do not have much time, so get back to me immediately  </p>\n<p>Note: I will advise you keep this to yourself alone, not even a friend or a family member should know about it because it could be one of them.  </p>\n<p>Reply Me To My Personal Email: <a href=\"mailto:CUTSYCAT@LIVE.COM\">CUTSYCAT@LIVE.COM</a></p>\n</blockquote>\n<p> Now sometimes I find spam quite funny, but this one is really serious. I mean, I know enough people who can't tell what is spam and what is not to take this email seriously, and this is a grave subject. From checking on Google, it seems quite a few other people received this email lately. The police might not be willing to investigate on viagra spam, but this one deserves some research I think.</p>"},{"url":"/posts/20101122-blogspot-building-docs-with-naturaldocs/","relativePath":"posts/20101122-blogspot-building-docs-with-naturaldocs.md","relativeDir":"posts","base":"20101122-blogspot-building-docs-with-naturaldocs.md","name":"20101122-blogspot-building-docs-with-naturaldocs","frontmatter":{"title":"Building docs with NaturalDocs","template":"post","date":"2010-11-22T13:57:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2010/11/building-docs-with-naturaldocs.html","blogspot_url":"https://raphink.blogspot.com/2010/11/building-docs-with-naturaldocs.html","tags":["augeas","debian","documentation","packaging","ubuntu"]},"html":"<p>I mentionned NaturalDocs <a href=\"http://www.raphink.info/2008/09/naturaldocs.html\">in an earlier post</a> on this blog since we're using it to document the <a href=\"http://augeas.net/docs/references/c_api\">Augeas C API</a> and <a href=\"http://augeas.net/docs/references/lenses\">Augeas lenses</a>.  </p>\n<p>With the latest version of Augeas <a href=\"https://www.redhat.com/archives/augeas-devel/2010-November/msg00110.html\">now out</a>, I wanted to get these docs generated with the package so they could be shipped in an augeas-doc package in Debian and Ubuntu. I first had to fix a missing link in the <a href=\"https://launchpad.net/ubuntu/natty/+source/naturaldocs/1:1.5-0ubuntu1\">naturaldocs</a> package (which was also the occasion to upgrade it to 1.5). Then I had to get naturaldocs in main, since augeas is in main already. While going through the <a href=\"https://bugs.launchpad.net/ubuntu/+source/naturaldocs/+bug/677822\">MIR</a>, I was asked if other packages in main could benefit from this change. I haven't found any, but there's quite a few packages in universe that could use naturaldocs to generate documentation at build time. The <a href=\"https://bugs.launchpad.net/ubuntu/+source/naturaldocs/+bug/677822\">bug report</a> lists some of them.  </p>\n<p>If you're interested in building NaturalDocs documentation in your package, here is the way it was done for Augeas.  </p>\n<p>Augeas is a C project using autotools. It was thus easier to activate NaturalDocs as an option in configure. This can easily be adapted for other cases, including running the commands in debian/rules directly.  </p>\n<p>In configure.ac:  </p>\n<p>dnl Check for NaturalDocs\nAC_PATH_PROGS([ND_PROG], [naturaldocs NaturalDocs], missing)\nAM_CONDITIONAL([ND_ENABLED], [test \"x$ND_PROG\" != \"xmissing\"])</p>\n<p>dnl NaturalDocs output format, defaults to HTML\nND_FORMAT=HTML\nAC_ARG_WITH([naturaldocs-output],\n[AS_HELP_STRING([--with-naturaldocs-output=FORMAT],\n[format of NaturalDocs output (possible values: HTML/FramedHTML, default: HTML)])],\n[\nif test \"x$ND_PROG\" = \"xmissing\"; then\nAC_MSG_ERROR([NaturalDocs was not found on your path; there's no point in setting the output format])\nfi\ncase $withval in\nHTML|FramedHTML)\nND_FORMAT=$withval\n;;\n*)\nAC_MSG_ERROR($withval is not a supported output format for NaturalDocs)\n;;\nesac\n])\nAC_SUBST(ND_FORMAT)</p>\n<p>In doc/naturaldocs/Makefile.am:  </p>\n<p>EXTRA_DIST = $(wildcard conf/Augeas.css conf/c_api/*.txt) \\\n$(wildcard conf/lenses/*.txt) \\\nModules/NaturalDocs/Languages/Augeas.pm</p>\n<p>ND_CONF=$(srcdir)/conf\nND_OUTPUT=output\nND_STYLE=../Augeas</p>\n<p>ND_PERL5LIB=$(abs_srcdir)/Modules\nND_PERL5OPT='-MNaturalDocs::Languages::Augeas'</p>\n<p>if ND_ENABLED\nall-local: NaturalDocs\nendif</p>\n<p>NaturalDocs: NDLenses NDCAPI</p>\n<p>env:\necho LIB $(ND_PERL5LIB)\necho OPT $(ND_PERL5OPT)\ntest -n \"$$PERL5OPT\" &#x26;&#x26; ND_PERL5OPT=\"$(ND_PERL5OPT) $$PERL5OPT\" || ND_PERL5OPT=$(ND_PERL5OPT); \\\ntest -n \"$$PERL5LIB\" &#x26;&#x26; ND_PERL5LIB=\"$(ND_PERL5LIB):$$PERL5LIB\" || ND_PERL5LIB=$(ND_PERL5LIB); \\\nPERL5LIB=$$ND_PERL5LIB PERL5OPT=$$ND_PERL5OPT env | grep PERL</p>\n<p>NDLenses: NDConf\n@mkdir -p $(ND_OUTPUT)/lenses\n@(echo \"Format lens documentation\"; \\\ntest -n \"$$PERL5OPT\" &#x26;&#x26; ND_PERL5OPT=\"$(ND_PERL5OPT) $$PERL5OPT\" || ND_PERL5OPT=$(ND_PERL5OPT); \\\ntest -n \"$$PERL5LIB\" &#x26;&#x26; ND_PERL5LIB=\"$(ND_PERL5LIB):$$PERL5LIB\" || ND_PERL5LIB=$(ND_PERL5LIB); \\\nPERL5LIB=$$ND_PERL5LIB PERL5OPT=$$ND_PERL5OPT \\\n$(ND_PROG) -p conf/lenses \\\n-i $(top_srcdir)/lenses \\\n-o $(ND_FORMAT) $(ND_OUTPUT)/lenses \\\n-s $(ND_STYLE))</p>\n<p>NDCAPI: NDConf\n@mkdir -p $(ND_OUTPUT)/c_api\n@(echo \"Format C API documentation\"; \\\ntest -n \"$$PERL5OPT\" &#x26;&#x26; ND_PERL5OPT=\"$(ND_PERL5OPT) $$PERL5OPT\" || ND_PERL5OPT=$(ND_PERL5OPT); \\\ntest -n \"$$PERL5LIB\" &#x26;&#x26; ND_PERL5LIB=\"$(ND_PERL5LIB):$$PERL5LIB\" || ND_PERL5LIB=$(ND_PERL5LIB); \\\n$(ND_PROG) -p conf/c_api \\\n-i $(top_srcdir)/src \\\n-o $(ND_FORMAT) $(ND_OUTPUT)/c_api \\\n-s $(ND_STYLE))</p>\n<p>NDConf:\n@(if test ! -d $(ND_CONF); then \\\ncp -pr $(ND_CONF) conf; \\\nfi)</p>\n<p>clean-local:\nrm -rf output conf/Data\nrm -rf $(ND_CONF)/c_api/Data $(ND_CONF)/lenses/Data</p>\n<p>After that, the NaturalDocs docs can be built by calling --with-naturaldocs-output=HTML or --with-naturaldocs-output=FramedHTML.  </p>\n<p>I hope this can be useful to improve the shipped documentation for projects already using NaturalDocs.</p>"},{"url":"/posts/20101011-blogspot-screen-resolution-with-nvidia-driver-in/","relativePath":"posts/20101011-blogspot-screen-resolution-with-nvidia-driver-in.md","relativeDir":"posts","base":"20101011-blogspot-screen-resolution-with-nvidia-driver-in.md","name":"20101011-blogspot-screen-resolution-with-nvidia-driver-in","frontmatter":{"title":"Screen resolution with nVidia driver in Lucid","template":"post","date":"2010-10-11T10:21:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2010/10/screen-resolution-with-nvidia-driver-in.html","blogspot_url":"https://raphink.blogspot.com/2010/10/screen-resolution-with-nvidia-driver-in.html","tags":["nvidia","ubuntu","xorg"]},"html":"<p>I struggled recently with a bug on a desktop running Lucid. Whenever I would log in, I got a session set to 1024x768. I would go to the nvidia-settings, reset it to 1280x1024 and it would be fine. As I didn't have much time for it and logged in only once a week or so, I kept doing this for some time, but it ended up bugging me quite a bit. Then this morning, I found <a href=\"https://bugs.launchpad.net/ubuntu/+source/nvidia-settings/+bug/362704\">this bug report</a>.  </p>\n<p>So if you're experiencing this bug, it's very easy to fix:  </p>\n<ul>\n<li>Edit your ~/.config/monitors.xml</li>\n<li>Adapt it to your configuration (in my case, that's 1280x1024 and a rate of 60Hz).</li>\n<li>Log out and back in.</li>\n</ul>"},{"url":"/posts/20110218-blogspot-manually-associating-lenses-with-files/","relativePath":"posts/20110218-blogspot-manually-associating-lenses-with-files.md","relativeDir":"posts","base":"20110218-blogspot-manually-associating-lenses-with-files.md","name":"20110218-blogspot-manually-associating-lenses-with-files","frontmatter":{"title":"Manually associating lenses with files in Augeas","template":"post","date":"2011-02-18T22:59:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2011/02/manually-associating-lenses-with-files.html","blogspot_url":"https://raphink.blogspot.com/2011/02/manually-associating-lenses-with-files.html","tags":["augeas","ubuntu"]},"html":"<h4>Autoload statements and known files</h4>\n<p><a href=\"http://www.augeas.net/\">Augeas</a> comes with a good collection of lenses that allow to parse the most common configuration files. Lenses are bidirectional pieces of code which allow Augeas to parse a configuration file and turn its content into a tree. Most lenses have an autoload statement which associates them with a specific set of files. For example, mysql.aug is associated with /etc/my.cnf and fstab.aug is associated with /etc/fstab. When Augeas is loaded, it searches the system for files that it knows about and tries to parse them with the associated lenses.  </p>\n<h4>Lenses without autoload statements</h4>\n<p>In some cases though, lenses are not associated with specific files through an autoload statement. This is the case for generic modules, such as util.aug, sep.aug, rx.aug, build.aug or inifile.aug, but also for lenses for which we do not know specific configuration files in a standard system. Such is the case of json.aug, since JSON is used a lot to serialize data between two programs, but hardly as static configuration files. Augeas has a JSON lens, but it is not associated with any known file.  </p>\n<h4>How to use lenses without autoload statements</h4>\n<p>How then, would you go about using the JSON lens on a file of your choice? There are several possibilities. Let's say you have a JSON file called foo.json located at /home/bar/foo.json and you want to parse it using augtool.  </p>\n<h5>Manipulating Augeas metadata on-the-fly</h5>\n<p>Augeas provides two root nodes in its tree: \"/files\" and \"/augeas\". The former lists the files Augeas was able to parse with its set of lenses, while the latter provides access to Augeas' metadata. In this last part of the tree, lenses can be manipulated on-the-fly.  </p>\n<p>Let's launch augtool and add /home/bar/foo.json to the JSON lens:  </p>\n<p># --noload deactivates all known lenses to make augtool faster\n$ augtool --noload</p>\n<blockquote>\n<h1>json.aug is not loaded by default, set it up</h1>\n<p>set /augeas/load/Json/lens Json.lns</p>\n<h1>Add to /home/bar/foo.json to the known files</h1>\n<p>set /augeas/load/Json/incl /home/bar/foo.json</p>\n<h1>Load known files</h1>\n<p>load\nprint /files/home/bar/foo.json</p>\n</blockquote>\n<p>From the (soon to be released) 8.0.0 version of Augeas, a -i flag will be available in augtool allowing to interpret a file before launching an interactive augtool shell. With this option, it will be possible to set a file with the previous contents and use it as an argument to augtool:  </p>\n<p>$ cat json_foo.augtool</p>\n<h1>json.aug is not loaded by default, set it up</h1>\n<p>set /augeas/load/Json/lens Json.lns</p>\n<h1>Add to /home/bar/foo.json to the known files</h1>\n<p>set /augeas/load/Json/incl /home/bar/foo.json</p>\n<h1>Load known files</h1>\n<p>load\n$ augtool --noload -if json_foo.augtool</p>\n<blockquote>\n<p>print /files/home/bar/foo.json</p>\n</blockquote>\n<h5>Creating a derived lens for the file</h5>\n<p>Augeas allows lenses to use definitions declared in other lenses. This is how generic modules are made to make lens development easier. It also allows you to make your own lenses, derived from existing lenses.  </p>\n<p>In our case, we could write a lens and use it:  </p>\n<p>$ cat jsonfoo.aug\nmodule JsonFoo =\nautoload xfm\nlet filter = incl \"/home/bar/foo.json\"\nlet xfm = transform Json.lns filter\n$ augtool -I . </p>\n<blockquote>\n<p>print /files/home/bar/foo.json</p>\n</blockquote>\n<p>In Debian and Ubuntu systems, lenses provided with Augeas are placed in /usr/share/augeas/lenses/dist, so you can place your own lenses in /usr/share/augeas/lenses without a risk of overriding them.  </p>\n<h4>Other lenses</h4>\n<p>These techniques apply to all lenses provided by Augeas. If you have a PHP configuration that is not in a standard path, you can use them to manipulate this file.</p>"},{"url":"/posts/20110223-blogspot-exporting-augeas-tree-to-xml/","relativePath":"posts/20110223-blogspot-exporting-augeas-tree-to-xml.md","relativeDir":"posts","base":"20110223-blogspot-exporting-augeas-tree-to-xml.md","name":"20110223-blogspot-exporting-augeas-tree-to-xml","frontmatter":{"title":"Exporting the Augeas tree to XML","template":"post","date":"2011-02-23T09:00:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2011/02/exporting-augeas-tree-to-xml.html","blogspot_url":"https://raphink.blogspot.com/2011/02/exporting-augeas-tree-to-xml.html","tags":["augeas","config-augeas-exporter","perl","ubuntu"]},"html":"<p>While <a href=\"http://www.augeas.net\">Augeas</a> is usually used to modify configuration files (for example as a <a href=\"http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas\">Puppet type</a>), it can also be useful to request configuration files.  </p>\n<p>Sometimes, you might want to retrieve that information from several servers and gather it on a central machine or DB to request it later. Programs that permit this usually use several parsing modules or copy the configuration files verbatim.  </p>\n<h4>aug2xml</h4>\n<p>Since Augeas can now parse a lot of configuration files, it could be useful to export its tree to a XML file that could be stored and used elsewhere.  </p>\n<p>The problem, however, is that the Augeas tree is not valid XML. Mainly, the nodes in Augeas can be numbers (in the case of \"seq\" nodes) or begin with such characters as \"#\". These names are illegal as node names in XML.  </p>\n<p>For this reason, I have chosen to map the nodes as \"node\" elements, and use a \"label\" attribute to store their label. The value of the nodes (where there is node) is stored in the text sub-node.  </p>\n<p>The following script is a prototype to export the Augeas tree to an XML file. On Debian and Ubuntu systems, you will need to install the libconfig-augeas-perl, libxml-libxml-perl and libencode-perl packages:  </p>\n<p>$ sudo apt-get install libconfig-augeas-perl \\\nlibxml-libxml-perl libencode-perl</p>\n<p>Here is the script:  </p>\n<h1>!/usr/bin/perl -w</h1>\n<p>use strict;\nuse Config::Augeas;\nuse XML::LibXML;\nuse Getopt::Long;\nuse Encode;</p>\n<p>my $path = '/files';\nmy $root = '/';\nmy $verbose;\nmy $debug;\nmy $help;</p>\n<p>my $result = GetOptions (\n\"path=s\" => \\$path,\n\"root=s\" => \\$root,\n\"verbose\" => \\$verbose,\n\"debug\" => \\$debug,\n\"help\" => \\$help,\n);</p>\n<p>if ($help) {\nusage();\nexit 0;\n}</p>\n<p>$verbose ||= $debug;</p>\n<p>my $aug = Config::Augeas->new(root => $root);\nmy $doc = XML::LibXML::Document->new('1.0', 'utf-8');</p>\n<p>my $elem = aug2xml($path);\n$doc->setDocumentElement($elem);\nprint $doc->toString;</p>\n<p>#######</p>\n<h1>Subs</h1>\n<p>#######</p>\n<p>sub usage {\nprint \"$0 [-p ] [-r fakeroot] [-v] [-d] [-h]</p>\n<p> Flags:\n-h, --help             Show this help\n-v, --verbose          Verbose mode\n-d, --debug            Debug mode</p>\n<p> Options:\n-p, --path Set path to export\n-r, --root Set fakeroot for Augeas\n\";\n}</p>\n<p>sub aug2xml {\nmy ($path) = @_;</p>\n<p>   my $label = '';\nif ($path =~ m|.*/([^/\\[]+)(\\[\\d+\\])?|) {\n$label = $1;\n} else {\nwarn \"W: Could not parse $path\\n\";\n}\nmy $elem = XML::LibXML::Element->new('node');\n$elem->setAttribute(\"label\", $label);</p>\n<p>   my $value = $aug->get($path);\nif(defined($value)) {\n$elem->appendTextNode(encode('utf-8', $value));\n}</p>\n<p>   $path =~ s| |\\\\ |g;\nmy @children = $aug->match(\"$path/*\");</p>\n<p>   for my $child (@children) {\nmy $child_elem = aug2xml($child);\n$elem->appendChild($child_elem);\n}</p>\n<p>   return $elem;\n}</p>\n<p>Having saved this code as aug2xml and made it executable, you can run:  </p>\n<p>$ ./aug2xml > export.xml</p>\n<p>This should create an export.xml file which contains all the configuration files in your system that Augeas was able to parse.  </p>\n<h4>Looking into the XML export</h4>\n<p>Now we've created the export.xml file, let's see how it looks. I've created mine from a fakeroot containing only fstab and aliases files, so it's not too big.  </p>\n<p>We can use tools such as xmlindent to view it nicely:  </p>\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n <node label=\"files\">\n  <node label=\"etc\">\n   <node label=\"aliases\">\n    <node label=\"#comment\">See man 5 aliases for format</node>\n     <node label=\"1\">\n      <node label=\"name\">postmaster</node>\n      <node label=\"value\">root</node>\n     </node>\n    </node>\n    <node label=\"fstab\">\n     <node label=\"#comment\">/etc/fstab: static file system information.</node>\n     <node label=\"#comment\">Use 'blkid -o value -s UUID' to print the universally unique identifier</node>\n     <node label=\"#comment\">for a device; this may be used with UUID= as a more robust way to name</node>\n     <node label=\"#comment\">devices that works even if disks are added and removed. See fstab(5).</node>\n     <node label=\"#comment\">&lt;file system&gt; &lt;mount point&gt;   &lt;type&gt;  &lt;options&gt;       &lt;dump&gt;  &lt;pass&gt;</node>\n     <node label=\"1\">\n      <node label=\"spec\">proc</node>\n      <node label=\"file\">/proc</node>\n      <node label=\"vfstype\">proc</node>\n      <node label=\"opt\">nodev</node>\n      <node label=\"opt\">noexec</node>\n      <node label=\"opt\">nosuid</node>\n      <node label=\"dump\">0</node>\n      <node label=\"passno\">0</node>\n     </node>\n     <node label=\"#comment\">/ was on /dev/sda1 during installation</node>\n     <node label=\"2\">\n      <node label=\"spec\">UUID=4cbb4f80-45f9-4e46-a076-8ec1124f4835</node>\n      <node label=\"file\">/</node>\n      <node label=\"vfstype\">ext3</node>\n      <node label=\"opt\">errors\n       <node label=\"value\">remount-ro</node>\n      </node>\n      <node label=\"dump\">0</node>\n      <node label=\"passno\">1</node>\n     </node>\n     <node label=\"3\">\n      <node label=\"spec\">/dev/sda2</node>\n      <node label=\"file\">none</node>\n      <node label=\"vfstype\">swap</node>\n      <node label=\"opt\">rw</node>\n      <node label=\"dump\">0</node>\n      <node label=\"passno\">0</node>\n     </node>\n    </node>\n   </node>\n  </node>\n<h4>Let's play with the XML export</h4>\n<p>So now we have an XML export of our configuration. What now? Let's try to make requests on it!  </p>\n<p>Because of the choices explained above which turn out every node to be named 'node', the Xpath request will be uglier than it would be had we used Augeas directly.  </p>\n<p>For example, let's say we want to get the file system type of the \"/\" entry in /etc/fstab. In Augeas, we could do:  </p>\n<p>$ augtool 'get //fstab/*[file=\"/\"]/vfstype'\n//fstab/*[file=\"/\"]/vfstype = ext3</p>\n<p>That's straight-forward enough. Now let's try to do it from our XML file:  </p>\n<h1>!/usr/bin/perl -w</h1>\n<p>use strict;\nuse XML::LibXML;</p>\n<p>my ($file) = @ARGV;</p>\n<p>open (my $fh, \"&#x3C;$file\")\nor die \"E: Could not open $file: $!\\n\" ;\nmy $doc = XML::LibXML->load_xml(IO => $fh);\nclose $fh;</p>\n<p>print $doc->findvalue('//node[@label=\"fstab\"]/node/\nnode[@label=\"file\"][child::text()=\"/\"]/\n../node[@label=\"vfstype\"]').\"\\n\";</p>\n<p>Save this script as testaugxml, make it executable and run:  </p>\n<p>$ ./testaugxml.pl export.xml\next3</p>\n<p>It works, although the Xpath expression is certainly uglier than in Augeas.  </p>\n<h4>xml2aug</h4>\n<p>Now what if I wanted to import that XML file back to Augeas? That could be fun, and sometimes useful, too.  </p>\n<p>There's a problem here. Exporting the Augeas tree to a clean XML file is very simple ; importing it back is more complex, because of the existing tree.  </p>\n<p>Without getting into much details, we have roughly 3 possibilities:  </p>\n<ul>\n<li>xml2aug replaces the Augeas tree with the contents of the XML file, erasing the existing tree ;</li>\n<li>xml2aug replaces the Augeas tree with the contents of the XML file only on files listed in the XML file and doesn't touch other files ;</li>\n<li>xml2aug applies the XML file as a patch on the existing tree.</li>\n</ul>\n<p>The 3rd solution is very hard to implement, and the 2nd solution might be possible once Augeas allows to get more information on nodes (knowing if the node is a directory, a file or a configuration entry would help). I'm left with the 1st solution to implement my script for now.  </p>\n<p>This having been said, you have been warned: there is a --root option in this script, make use of it if you don't want to lose some configuration files!  </p>\n<p>Here is the script:  </p>\n<h1>!/usr/bin/perl -w</h1>\n<p>use strict;\nuse Config::Augeas;\nuse XML::LibXML;\nuse Getopt::Long;</p>\n<p>my $file;\nmy $root = '/';\nmy $verbose;\nmy $debug;\nmy $help;</p>\n<p>my $result = GetOptions (\n\"file=s\" => \\$file,\n\"root=s\" => \\$root,\n\"verbose\" => \\$verbose,\n\"debug\" => \\$debug,\n\"help\" => \\$help,\n);</p>\n<p>if ($help) {\nusage();\nexit 0;\n}</p>\n<p>$verbose ||= $debug;</p>\n<p>unless (defined($file)) {\ndie \"E: You must specify a filename\\n\";\n}</p>\n<p>open (my $fh, \"&#x3C;$file\")\nor die \"E: Could not open $file: $!\\n\" ;\nmy $doc = XML::LibXML->load_xml(IO => $fh);\nclose $fh;</p>\n<p>my $aug = Config::Augeas->new(root => $root);</p>\n<h1>we want to replace everything</h1>\n<p>$aug->rm('/files/*');\nmy @top_nodes = $doc->find('/*/node[@label != \"files\"]')->get_nodelist();</p>\n<p>for my $node (@top_nodes) {\nxml2aug($node, '/files');\n}</p>\n<p>$aug->print('/files') if $debug;\n$aug->save();\n$aug->print('/augeas//error');</p>\n<p>########</p>\n<h1>Subs</h1>\n<p>########</p>\n<p>sub usage {\nprint \"$0 [-f ] [-r fakeroot] [-v] [-d] [-h]</p>\n<p> Flags:\n-h, --help             Show this help\n-v, --verbose          Verbose mode\n-d, --debug            Debug mode</p>\n<p> Options:\n-f, --file Set XML file to import from\n-r, --root Set fakeroot for Augeas\n\";\n}</p>\n<p>sub xml2aug {\nmy ($elem, $path) = @_;</p>\n<p>   my $type = $elem->nodeType;</p>\n<p>   my $label = $elem->getAttribute('label');</p>\n<p>   my $matchpath = \"$path/*[last()]\";\n$matchpath =~ s| |\\\\ |g;\nmy $lastpath = $aug->match(\"$path/*[last()]\");</p>\n<p>   if(defined($lastpath)) {\nprint \"V: Inserting $label after $lastpath\\n\" if $verbose;</p>\n<deckgo-highlight-code   highlight-lines=\"undefined\">\n          <code slot=\"code\">  # Insert last node\n  $aug-&gt;insert($label, &quot;after&quot;, $lastpath);</code>\n        </deckgo-highlight-code>\n<p>   } else {\n$aug->set(\"$path/$label\", '');\n}</p>\n<p>   $matchpath = \"$path/${label}[last()]\";\n$matchpath =~ s| |\\\\ |g;\nmy $newpath = $aug->match($matchpath);</p>\n<p>   my $value;</p>\n<p>   for my $child ($elem->childNodes()) {\nif ($child->nodeType == XML_TEXT_NODE) {\n# Text node\n$value = $child->nodeValue;\n} else {\nxml2aug($child, $newpath);\n}\n}</p>\n<p>   if (defined($value)) {\nprint \"V: Setting value of $newpath to $value\\n\" if $verbose;\n$aug->set($newpath, $value);\n}\n}</p>\n<h4>Future development</h4>\n<p>The current state of this prototype is a set of Perl scripts. Eventually, I would like to merge at least the export functionality in augtool, so as not to depend on Perl.</p>"},{"url":"/posts/20110224-blogspot-augeas-080/","relativePath":"posts/20110224-blogspot-augeas-080.md","relativeDir":"posts","base":"20110224-blogspot-augeas-080.md","name":"20110224-blogspot-augeas-080","frontmatter":{"title":"Augeas 0.8.0","template":"post","date":"2011-02-24T11:21:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2011/02/augeas-080.html","blogspot_url":"https://raphink.blogspot.com/2011/02/augeas-080.html","tags":["augeas","ubuntu"]},"html":"<p>Right on time for Natty feature freeze, <a href=\"http://www.augeas.net\">Augeas</a> 0.8.0 <a href=\"https://www.redhat.com/archives/augeas-devel/2011-February/msg00068.html\">has been released</a> and is uploaded to Natty!  </p>\n<p>Packages are also available for Lucid and Maverick in the <a href=\"https://launchpad.net/~raphink/+archive/augeas/+packages\">Augeas PPA</a>.  </p>\n<h4>What's new in Augeas?</h4>\n<p>Well, quite a bit. To begin with, thanks to the great work of <a href=\"http://multivax.blogspot.com/\">Francis Giraldeau</a>, the main change is the addition of a new \"square\" lens combinator, which makes it possible to write such lenses as XML or Apache. Francis has committed both xml.aug and httpd.aug.  </p>\n<p>Antoher improvement which I am sure will bear fruits in the future is the addition of the aug_span API call. This will allow developments such as <a href=\"http://multivax.blogspot.com/2010/10/augedit-regedit-for-linux.html\">augedit</a>, which could greatly improve configuration management GUIs.  </p>\n<p>Augtool has also benefitted quite a bit from this release, gaining a few options:  </p>\n<ul>\n<li>--autosave makes sure to call \"save\" after all commands are issued ;</li>\n<li>--interactive lets you run an interactive shell after intepreting the commands passed in a file or via STDIN (see <a href=\"http://www.raphink.info/2011/02/manually-associating-lenses-with-files.html\">this post</a> for an example) ;</li>\n<li>--nostdinc, --noload and --noautoload have gained short options, so they can be used with augtool as an interpreter (in a shebang).</li>\n</ul>\n<p>With these improvements in augtool, you can now write a script like the following:  </p>\n<h1>!/usr/bin/augtool -Asif</h1>\n<p>set /augeas/load/Json/lens Json.lns\nset /augeas/load/Json/incl /home/raphink/myjson.json\nload</p>\n<p>Make this script executable and run it, and you will get an interactive shell with only /home/raphink/myjson.json parsed (hence a very low loading payload due to the -A flag). The -s flag will ensure that \"save\" is called when you quit the shell.  </p>\n<h4>What's new in the Ubuntu package?</h4>\n<p>Improvements have also been made in the last versions of the augeas package in Ubuntu. In 0.7.4, an augeas-doc package has been added. This package has been complemented in 0.8.0, so now it includes:  </p>\n<ul>\n<li>Generated API docs (with NaturalDocs) for the C API and lenses ;</li>\n<li>Generated PDF docs about <a href=\"http://en.wikipedia.org/wiki/Bidirectional_Transformation\">bx</a> theory on lenses and ambiguity ;</li>\n<li>Text docs about XPath expressions (see also <a href=\"http://augeas.net/page/Path_expressions\">the Augeas wiki</a>).</li>\n</ul>\n<h4>Feedback</h4>\n<p>We love to know what you do with Augeas, and the ideas that you have to improve it. We also like to know when things don't work so we can fix them.  </p>\n<p>There are many ways to <a href=\"http://augeas.net/developers.html\">contribute to Augeas</a>, so don't hesitate to join us!</p>"},{"url":"/posts/20110225-blogspot-introducing-configaugeasexporter/","relativePath":"posts/20110225-blogspot-introducing-configaugeasexporter.md","relativeDir":"posts","base":"20110225-blogspot-introducing-configaugeasexporter.md","name":"20110225-blogspot-introducing-configaugeasexporter","frontmatter":{"title":"Introducing Config::Augeas::Exporter","template":"post","date":"2011-02-25T23:13:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2011/02/introducing-configaugeasexporter.html","blogspot_url":"https://raphink.blogspot.com/2011/02/introducing-configaugeasexporter.html","tags":["augeas","config-augeas-exporter","perl","ubuntu"]},"html":"<p>Following my little experiments <a href=\"http://www.raphink.info/2011/02/exporting-augeas-tree-to-xml.html\">from two days ago</a>, I decided to turn my prototype into a Perl module so others could benefit from it.  </p>\n<p>I am therefore happy to introduce <a href=\"http://search.cpan.org/~raphink/Config-Augeas-Exporter-0.1/lib/Config/Augeas/Exporter.pm\">Config::Augeas::Exporter</a>, uploaded today to the CPAN. Packages are also available for Ubuntu Lucid and Natty in <a href=\"https://launchpad.net/~raphink/+archive/augeas/+packages\">the Augeas PPA</a>.  </p>\n<p>This module wraps up around <a href=\"http://search.cpan.org/~ddumont/Config-Augeas-0.701/lib/Config/Augeas.pm\">Config::Augeas</a> and currently provides 4 public export methods:  </p>\n<ul>\n<li>to_xml(): export the Augeas tree to an XML::LibXML::Document object ;</li>\n<li>to_hash(): export the Augeas tree to a hash ;</li>\n<li>to_json(): export the Augeas tree to a JSON document ;</li>\n<li>to_yaml(): export the Augeas tree to a YAML document ;</li>\n</ul>\n<p>In addition to this, it also provides a from_xml() method to import back an XML exported tree.  </p>\n<p>The module is shipped with two scripts, aug2xml and xml2aug, which are essentially the scripts presented in <a href=\"http://www.raphink.info/2011/02/exporting-augeas-tree-to-xml.html\">my previous blog entry</a>, rewritten to use the module.  </p>\n<p>Thanks to the great advice of <a href=\"http://watzmann.net/lutter/\">David Lutterkort</a>, Augeas' main developer, the XML layout has been improved, which makes it much nicer, smaller, easier to parse, and much easier to import back.  </p>\n<p>As a result of this, xml2aug is now able to replace only the files listed in the XML document, instead of replacing the whole tree.  </p>\n<p>Here are some examples of what can be done with aug2xml and xml2aug:  </p>\n<p># Let's export the pam.d files only, excluding the comment nodes\n$ aug2xml -p '/files/etc/pam.d' -e \"#comment\"  > export.xml</p>\n<h1>How many files where exported?</h1>\n<p>$ grep -o \"&#x3C;file\" export.xml | wc -l\n34\n$ ls -lh export.xml\n-rw-r--r-- 1 raphink raphink 20K 2011-02-25 23:06 export.xml</p>\n<h1>We can try some XPath requests on the file</h1>\n<h1>For example finding the control condition</h1>\n<h1>for the pam_env.so module in the cron rule</h1>\n<p>$ xpath -e '//file[@path=\"/etc/pam.d/cron\"]/node[node[@label=\"module\"]=\"pam_env.so\"]/node[@label=\"control\"]' export.xml\nFound 2 nodes in export.xml:\n-- NODE --\nrequired\n-- NODE --\nrequired</p>\n<h1>Now let's import it back to a clean fakeroot</h1>\n<p>$ mkdir -p fakeroot/etc/pam.d\n$ xml2aug -r fakeroot/ -f export.xml\n$ tree fakeroot/\nfakeroot/\n└── etc\n└── pam.d\n├── atd\n├── chfn\n├── chpasswd\n├── chsh\n├── common-account\n├── common-auth\n├── common-password\n├── common-session\n├── common-session-noninteractive\n├── cron\n├── cups\n├── cvs\n├── gdm\n├── gdm-autologin\n├── gnome-screensaver\n├── kcheckpass\n├── kdm\n├── kdm-kde4\n├── kdm-kde4-np\n├── kdm-np\n├── kscreensaver\n├── login\n├── newusers\n├── other\n├── passwd\n├── polkit\n├── polkit-1\n├── ppp\n├── samba\n├── sshd\n├── su\n├── sudo\n└── xscreensaver</p>\n<p>2 directories, 33 files</p>"},{"url":"/posts/20110224-blogspot-version-number-suggests-ubuntu-changes/","relativePath":"posts/20110224-blogspot-version-number-suggests-ubuntu-changes.md","relativeDir":"posts","base":"20110224-blogspot-version-number-suggests-ubuntu-changes.md","name":"20110224-blogspot-version-number-suggests-ubuntu-changes","frontmatter":{"title":"Version number suggests Ubuntu changes","template":"post","date":"2011-02-24T09:45:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2011/02/version-number-suggests-ubuntu-changes.html","blogspot_url":"https://raphink.blogspot.com/2011/02/version-number-suggests-ubuntu-changes.html","tags":["packaging","ubuntu"]},"html":"<p>Today, I was updating an Ubuntu package whose maintainer in Debian has a @canonical.com address.  </p>\n<p>When I was building the source package, I kept getting an error:  </p>\n<p>Version number suggests Ubuntu changes, but Maintainer: does not have Ubuntu address</p>\n<p>This error crashed dpkg-source and prevented me from building the source package.  </p>\n<p>Upon looking into the code (/usr/share/perl5/Dpkg/Vendor/Ubuntu.pm), I found that this errors only when DEBEMAIL is defined in the environment with a @ubuntu.com address ; a warning is issued otherwise.  </p>\n<p>So if you ever have this problem, it's easily fixed by unsetting DEBEMAIL:  </p>\n<p>$ unset DEBEMAIL</p>"},{"url":"/posts/20110318-blogspot-changing-remote-url-in-git/","relativePath":"posts/20110318-blogspot-changing-remote-url-in-git.md","relativeDir":"posts","base":"20110318-blogspot-changing-remote-url-in-git.md","name":"20110318-blogspot-changing-remote-url-in-git","frontmatter":{"title":"Changing a remote URL in git","template":"post","date":"2011-03-18T11:52:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2011/03/changing-remote-url-in-git.html","blogspot_url":"https://raphink.blogspot.com/2011/03/changing-remote-url-in-git.html","tags":["augeas","git","ubuntu"]},"html":"<p>You cloned a git repository using a public URL, and now everytime you want to push to it, you have to specify the push R/W URL because the public URL you used to pull is read-only.  </p>\n<p>This happened to me:  </p>\n<p>$ git remote show origin\n* remote origin\nFetch URL: git://git.fedorahosted.org/git/augeas.git\nPush  URL: git://git.fedorahosted.org/git/augeas.git\nHEAD branch: master\nRemote branch:\nmaster tracked\nLocal branch configured for 'git pull':\nmaster merges with remote master\nLocal ref configured for 'git push':\nmaster pushes to master (up to date)</p>\n<p>and here is how I fixed it:  </p>\n<p>$ git remote set-url --push origin ssh://raphink@git.fedorahosted.org/git/augeas.git\n$ git remote show origin\n* remote origin\nFetch URL: git://git.fedorahosted.org/git/augeas.git\nPush  URL: ssh://raphink@git.fedorahosted.org/git/augeas.git\nHEAD branch: master\nRemote branch:\nmaster tracked\nLocal branch configured for 'git pull':\nmaster merges with remote master\nLocal ref configured for 'git push':\nmaster pushes to master (up to date)</p>\n<p>Note that the --push option modified only the push URL, so I keep using the public URL for fetching, which is fine.</p>"},{"url":"/posts/20110329-blogspot-syntax-highlighting-in-latex/","relativePath":"posts/20110329-blogspot-syntax-highlighting-in-latex.md","relativeDir":"posts","base":"20110329-blogspot-syntax-highlighting-in-latex.md","name":"20110329-blogspot-syntax-highlighting-in-latex","frontmatter":{"title":"Syntax highlighting in LaTeX","template":"post","date":"2011-03-29T22:43:00.002+02:00","canonical_url":"https://raphink.blogspot.com/2011/03/syntax-highlighting-in-latex.html","blogspot_url":"https://raphink.blogspot.com/2011/03/syntax-highlighting-in-latex.html","tags":["augeas","latex","python","ubuntu"]},"html":"<p>Lately, I have been busy making a <a href=\"https://github.com/raphink/Augeas-book\">book on Augeas</a>. I wanted something that would look nice, and that would use only LaTeX.  </p>\n<p>A colleague of mine told me to have a look at the <a href=\"http://pygments.org/\">Pygments syntax highlighter</a> and its LaTeX module <a href=\"http://code.google.com/p/minted/\">minted</a>. I'm very happy with them so far.  </p>\n<h4>Installing Pygments</h4>\n<p>Installing Pygments on Ubuntu is as easy as can be:  </p>\n<p>$ sudo apt-get install python-pygments</p>\n<p>This provides a /usr/bin/pygmentize command, which is then used by minted.  </p>\n<h4>Installing minted</h4>\n<p>I didn't find a package for minted, so I downloaded the files <a href=\"http://www.ctan.org/tex-archive/macros/latex/contrib/minted/\">from the CTAN</a> and made a minted directory in my source.  </p>\n<p>When building with pdflatex, I make sure to export the TEXINPUTS=minted: variable so that pdflatex uses the minted directory for inclusions.  </p>\n<h4>Adding syntax highlighting</h4>\n<p>Now that all the tools are installed, we need to actually use them in the LaTeX source. Here is a simple example:  </p>\n<p>\\mint{console}|$ sudo apt-get install augeas-tools|</p>\n<p>A multi-line example:  </p>\n<p>\\begin{minted}{console}\n$ sudo yum install readline-devel\n$ sudo apt-get install libreadline-dev\n\\end{minted}</p>\n<p>And even a listing example:  </p>\n<p>\\begin{listing}\n\\begin{minted}{augtool-shell}\n/files/etc/foo.conf\n/files/etc/foo.conf/#include = /etc/foo.d/*\n\\end{minted}\n\\caption{Augeas does not interpret include statements}\n\\label{lst:intro_include_tree}\n\\end{listing}</p>\n<p>There is much that can be done, and the <a href=\"http://mirrors.ctan.org/macros/latex/contrib/minted/minted.pdf\">documentation is well made</a>.  </p>\n<h4>I'm missing lexers</h4>\n<p>I would love my book to be full of colours. Not only for languages that are well known, but also, and especially, for the languages and commands that are specific to Augeas.  </p>\n<p>Of course, Pygments doesn't provide lexers for Augeas syntax (yet), so I need to write them.  </p>\n<p>The first lexer I would like to have is the augtool-shell lexer, which will put syntax highlighting on commands from an augtool session.  </p>\n<p>My lexer looks like this:  </p>\n<p>import re</p>\n<p>try:\nset\nexcept NameError:\nfrom sets import Set as set\nfrom bisect import bisect</p>\n<p>from pygments.lexer import Lexer, LexerContext, RegexLexer, ExtendedRegexLexer, \\\nbygroups, include, using, this, do_insertions\nfrom pygments.token import Punctuation, Text, Comment, Keyword, Name, String, \\\nGeneric, Operator, Number, Whitespace, Literal\nfrom pygments.util import get_bool_opt\nfrom pygments.lexers.other import BashLexer</p>\n<p>__all__ = ['AugtoolShellLexer']</p>\n<p>class AugtoolShellLexer(RegexLexer):\n\"\"\"\nLexer for Augtool shell sessions.\n\"\"\"</p>\n<deckgo-highlight-code   highlight-lines=\"undefined\">\n          <code slot=\"code\">name = &#39;AugtoolShell&#39;\naliases = \\[&#39;augtool-shell&#39;\\]\nfilenames = \\[&#39;\\*.augtoolshell&#39;\\]\nmimetypes = \\[&#39;text/x-augtool-shell&#39;\\]\n\ntokens = {\n    &#39;root&#39;: \\[\n        (r&#39;^\\\\s+$&#39;, Text),           # empty line\n        (r&#39;^\\[;#\\].\\*?$&#39;, Comment),    # comment\n        (r&#39;^(rm\\\\s+:.\\*)&#39;, Text),     # removed nodes\n        (r&#39;^(Saved.\\*)&#39;, Text),      # saved\n        (r&#39;^(augtool\\\\&gt;)(\\\\s+)(\\\\S+)(?:(\\\\s+)(.\\*))?$&#39;,   # augtool prompt\n         bygroups(Generic.Prompt, Whitespace, Keyword, Whitespace, String)),\n        (r&#39;^(\\[^=\\]+)(?:(\\\\s+)(=)(\\\\s+)(.\\*))?$&#39;,    # ls/get/print\n         bygroups(String, Whitespace, Operator, Whitespace, String)),\n        (r&#39;^(\\\\S+)(\\\\s+)(label)(=)(\\\\S+)(\\\\s+)(value)(=)(\\\\S+)(\\\\s+)(span)(=)(\\\\S+)$&#39;,  # span output\n         bygroups(String, Whitespace, Keyword, Operator, String, Whitespace,\n                                Keyword, Operator, String, Whitespace,\n                                Keyword, Operator, String)),\n    \\]\n}</code>\n        </deckgo-highlight-code>\n<p>It is located in the augeas-lexer/augeaslexer/augeaslexer.py file, and the augeas-lexer directory contains the following files:  </p>\n<p>augeas-lexer/\n├── augeaslexer\n│   ├── augeaslexer.py\n│   └── __init__.py\n└── setup.py</p>\n<p>__init__.py is an empty file, and setup.py contains the following:  </p>\n<p>\"\"\"\nAugeas syntax highlighting for Pygments.\n\"\"\"</p>\n<p>from setuptools import setup</p>\n<p>entry_points = \"\"\"\n[pygments.lexers]\naugtool-shell = augeaslexer.augeaslexer:AugtoolShellLexer\n\"\"\"</p>\n<p>setup(\nname         = 'augeaslexer',\nversion      = '0.1',\ndescription  = __doc__,\nauthor       = \"Raphael Pinson\",\npackages     = ['augeaslexer'],\nentry_points = entry_points\n)</p>\n<p>In order to build with this new lexer, I need to install it in my system (if someone knows how to do without this step, I'll be very happy to hear it!). The following command does that:  </p>\n<p>$ sudo python setup.py install</p>\n<p>Now when I run pygmentize, I should see my lexer:  </p>\n<p>$ pygmentize -L | grep augtool\n* augtool-shell:\nAugtoolShell (filenames *.augtoolshell)</p>\n<p>I've actually added (or begun to add) 3 more lexers to my module: AugtoolLexer, AugeasLexer and PuppetAugeasLexer.  </p>\n<p>The result can be see in the <a href=\"http://www.scribd.com/doc/51028532/Augeas-devel\">PDF version of the book</a> (which is still a work in progress).  </p>\n<h4>Defining macros</h4>\n<p>One thing LaTeX users love is its flexibility. On big projects such as books, it is often useful to define macros and new environments.  </p>\n<p>Here is an example of a listing containing several languages with line numbering. The macros take care of applying the line numbering options (linenos and firstnumber) and defining the standard options for my document (fontsize=\\footnotesize and bgcolor=bg).  </p>\n<p>The macros look like this:  </p>\n<p>% minted commands</p>\n<p>\\newcommand{\\mymint}[3][]{\\mint[fontsize=\\footnotesize,bgcolor=bg,#1]{#2}#3}\n\\newcommand{\\consolecode}[2][]{\\mymint[#1]{console}#2}\n\\newcommand{\\augtoolshcode}[2][]{\\mymint[#1]{augtool-shell}#2}</p>\n<p>% inputminted</p>\n<p>\\newcommand{\\myinputminted}[3][]{\\inputminted[fontsize=\\footnotesize,bgcolor=bg,#1]{#2}{../listings/#3}}</p>\n<p>And the listing:  </p>\n<p>\\begin{listing}\n\\consolecode[linenos]|$ augtool --backup --root myroot|\n\\myinputminted[linenos,firstnumber=2]{augtool-shell}{rm_fstab_opt.augtoolshell}\n\\consolecode[linenos,firstnumber=15]|$ diff -u myroot/etc/fstab myroot/etc/fstab.augsave|\n\\myinputminted[linenos,firstnumber=16]{diff}{fstab_opt.diff}\n\\caption{Removing an option in fstab}\n\\label{lst:rm_fstab_opt}\n\\end{listing}</p>\n<p>This produces the following output:  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEW7b3Tbh62i5m6AmdfAoyETfZLpX5P4kyQ24d1SYBED2mBr9QFj4sIjsshETZJlIukypfTPQ0K-AjMtuTDad6CrWlgrK8cHckFyAjlgXyS_ZMODv0BHYOuxk4WNAyFOSTg_D13GFiVL3I/s1600/listing_minted_macros.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEW7b3Tbh62i5m6AmdfAoyETfZLpX5P4kyQ24d1SYBED2mBr9QFj4sIjsshETZJlIukypfTPQ0K-AjMtuTDad6CrWlgrK8cHckFyAjlgXyS_ZMODv0BHYOuxk4WNAyFOSTg_D13GFiVL3I/s320/listing_minted_macros.png\"></a></p>"},{"url":"/posts/20110511-blogspot-keeping-law-for-whose-sake/","relativePath":"posts/20110511-blogspot-keeping-law-for-whose-sake.md","relativeDir":"posts","base":"20110511-blogspot-keeping-law-for-whose-sake.md","name":"20110511-blogspot-keeping-law-for-whose-sake","frontmatter":{"title":"Keeping the Law — for whose sake?","template":"post","date":"2011-05-11T23:12:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2011/05/keeping-law-for-whose-sake.html","blogspot_url":"https://raphink.blogspot.com/2011/05/keeping-law-for-whose-sake.html","tags":["bible","god"]},"html":"<p>Ye hypocrites, well did Esaias prophesy of you, saying,  </p>\n<p>This people draweth nigh unto me with their mouth, and honoureth me with their lips; but their heart is far from me.  </p>\n<p>Matthew 15:7—8  </p>\n<p>Many times, Jesus called people — especially the Pharisees — hypocrites. This puzzled His disciples quite a bit, because the Pharisees where people who kept the Law strictly. How could people who kept the Law be wrong? Well, the problem is that they kept the Law <em>outwardly</em> but not <em>inwardly</em>.</p>\n<p>I heard many comments on the subject over the years, but something about this struck me today. Who were the Pharisees keeping the Law for? For whose sake do we act the way we do? Jesus said that when you desire a woman, you've already committed adultery with her in your heart, and that when you insult someone — even in your heart — you've already committed murder.  </p>\n<p>What is it that keeps me from acting the thoughts I have? Why do I sometimes let myself desire a woman, but I won't commit adultery in real life? Let's face it: even a lot of non believers have never committed adultery, let alone murder. I think the problem is here: for whose sake do you act the way you do? For God's sake, or for your own?  </p>\n<p>Many people act properly for their own sake ; they are afraid of what people will think about them, or do to them, if they don't act nicely. They keep the Law for their own sake — because of fear, or because of pride — and they have no reason to keep their thoughts as clean as their acts.  </p>\n<p>Jesus called us to go much further than that. He said we should actually keep even our thoughts clean. Why? For God's sake! It's not so much that I should look like a perfect person, that I should care about what people think about me — if they will judge me, hate me, stone me — if I don't act properly. No, there's someone — God — who knows more about myself than I even do, and for His sake, I should be perfect, because He said : \"Be ye therefore perfect, even as your Father which is in heaven is perfect\" (Matthew 5:48).  </p>\n<p>As Christians, we so often tend to think that we can do it the easy way: starting by cleaning ourselves outwardly, and finishing with the inside. It doesn't work that way, because the goal is not the same. If you care to clean the outside first, you're doing it for your own sake, but cleaning the inside requires doing it for God's sake, for the inward cleaning is the work of the Holy Spirit in our lives.  </p>\n<p>Your motivation defines where you're heading. If you act the way you do for your own sake, you will stay stuck with the outward issues of your life. If you want to give it all to God and change for His sake, asking the Holy Spirit to cleanse you from the inside — even if it might take longer and be more painful — He will do this work in you, and it will end up showing up outwardly in your life.  </p>\n<p>Father Almighty,  </p>\n<p>I realize my own efforts to purify myself are vain because they are only motivated by fear and pride, and they do not cleanse me internally — for Your sake.  </p>\n<p>I pray Lord that you send your Holy Spirit in my life to achieve the work within me, that I may be perfected to your image inwardly, and shine of your glory outwardly.  </p>\n<p>In Jesus' name. Amen.</p>"},{"url":"/posts/20110414-blogspot-fancytabs-latex-package/","relativePath":"posts/20110414-blogspot-fancytabs-latex-package.md","relativeDir":"posts","base":"20110414-blogspot-fancytabs-latex-package.md","name":"20110414-blogspot-fancytabs-latex-package","frontmatter":{"title":"The fancytabs LaTeX package","template":"post","date":"2011-04-14T12:30:00.004+02:00","canonical_url":"https://raphink.blogspot.com/2011/04/fancytabs-latex-package.html","blogspot_url":"https://raphink.blogspot.com/2011/04/fancytabs-latex-package.html","tags":["latex","ubuntu"]},"html":"<p>Lately, I've been using LaTeX quite a bit, and defining my own macros. Today is the occasion for me to release my first package: fancytabs.  </p>\n<p>This is a package that allows to add fancy tabs on the border of pages in a document.  </p>\n<p>The package can be downloaded <a href=\"http://www.ctan.org/tex-archive/macros/latex/contrib/fancytabs\">from the CTAN</a>.  </p>\n<h4>Installing</h4>\n<p>After downloading and unzipping the file, run latex on the fancytabs.ins file. This will produce a fancytabs.sty file which provides the package. You can also build the PDF documentation by running pdflatex on the fancytabs.dtx file.  </p>\n<h4>What does it look like?</h4>\n<p>Here is an example of a page featuring a fancy tab:  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKmUxecABMr-0z-cVXHFNwI6fM-VkOd5Nea1THT3XAXwGKlNQi1HxRsloIVcakbKAxeuS4o20bKvIOYOsEM4tBJvMY6O4e5uGvAphtrRU4Ag_IREkCo99U3_cItQdYbKdMJzHGn__GUhrX/s1600/fancytabs.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKmUxecABMr-0z-cVXHFNwI6fM-VkOd5Nea1THT3XAXwGKlNQi1HxRsloIVcakbKAxeuS4o20bKvIOYOsEM4tBJvMY6O4e5uGvAphtrRU4Ag_IREkCo99U3_cItQdYbKdMJzHGn__GUhrX/s320/fancytabs.png\"></a></p>"},{"url":"/posts/20110906-blogspot-bible-verses-in-mint-fortunes/","relativePath":"posts/20110906-blogspot-bible-verses-in-mint-fortunes.md","relativeDir":"posts","base":"20110906-blogspot-bible-verses-in-mint-fortunes.md","name":"20110906-blogspot-bible-verses-in-mint-fortunes","frontmatter":{"title":"Bible verses in Mint fortunes","template":"post","date":"2011-09-06T21:37:00.003+02:00","canonical_url":"https://raphink.blogspot.com/2011/09/bible-verses-in-mint-fortunes.html","blogspot_url":"https://raphink.blogspot.com/2011/09/bible-verses-in-mint-fortunes.html","tags":["bash","linux mint","ubuntu"]},"html":"<p>For my most recent computer, I've installed Linux Mint 11. In Mint, bash is set to display a fortune cookie every time you start a shell session.  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsuJr9pnVPK0RyTfU-2c5vQVXWuGFQq1aaKzjk28zNfn_QJVqyoOJ7AWGDnQ7PrKY2i-wfHLHPdjahTVs_AHsmUUXdrA9NZ61yIjki17Mzr5meaNCvBmNVcZkBCEysAS7ZH914EY_85zCr/s1600/mint_fortunes.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsuJr9pnVPK0RyTfU-2c5vQVXWuGFQq1aaKzjk28zNfn_QJVqyoOJ7AWGDnQ7PrKY2i-wfHLHPdjahTVs_AHsmUUXdrA9NZ61yIjki17Mzr5meaNCvBmNVcZkBCEysAS7ZH914EY_85zCr/s320/mint_fortunes.png\"></a></p>\n<p>This is a nice idea, however I would rather see daily Bible verses than random fortunes. Here is how to do that. First, install the verse package:  </p>\n<p><code>$ sudo apt-get install verse</code>  </p>\n<p>Then, edit /usr/bin/mint-fortune with sudo, and replace the line:  </p>\n<p><code>/usr/games/fortune | $command -f $cow</code>  </p>\n<p>with  </p>\n<p><code>/usr/bin/verse | sed -e 's@^ *@@' | $command -f $cow</code>  </p>\n<p>To be sure that the file won't be overridden when you upgrade your distribution, set it as a local diversion:  </p>\n<p><code>$ sudo dpkg-divert --local /usr/bin/mint-fortune</code>  </p>\n<p>and you're done!  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyr0sEeuXsQZPfwNXO7WWFLUOG814lJ33wX3q4nbRoULxjG7xU__DsN9-FclDC9E-VsjTUEFWYJN3igNnq3z8AJu6i-b0es0SbfyoALzL_1Ok0siZQPFWkSyhSIIL1kH93KVXbKVR3LFbU/s1600/mint-fortunes-bible-nospaces.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyr0sEeuXsQZPfwNXO7WWFLUOG814lJ33wX3q4nbRoULxjG7xU__DsN9-FclDC9E-VsjTUEFWYJN3igNnq3z8AJu6i-b0es0SbfyoALzL_1Ok0siZQPFWkSyhSIIL1kH93KVXbKVR3LFbU/s320/mint-fortunes-bible-nospaces.png\"></a></p>"},{"url":"/posts/20111203-blogspot-announcing-augeas-0100/","relativePath":"posts/20111203-blogspot-announcing-augeas-0100.md","relativeDir":"posts","base":"20111203-blogspot-announcing-augeas-0100.md","name":"20111203-blogspot-announcing-augeas-0100","frontmatter":{"title":"Announcing Augeas 0.10.0","template":"post","date":"2011-12-03T21:46:00.001+01:00","canonical_url":"https://raphink.blogspot.com/2011/12/announcing-augeas-0100.html","blogspot_url":"https://raphink.blogspot.com/2011/12/announcing-augeas-0100.html","tags":["augeas","config-augeas-exporter","ubuntu"]},"html":"<p><a href=\"http://augeas.net/\">Augeas</a> 0.10.0 has <a href=\"https://www.redhat.com/archives/augeas-devel/2011-December/msg00004.html\">just been released</a>, and it brings a good lot of changes with it.  </p>\n<p>Among them, the aug_to_xml method has been added. This is essentially an integration of the XML export function initially introduced in the <a href=\"http://www.raphink.info/2011/02/introducing-configaugeasexporter.html\">Config::Augeas::Exporter</a> Perl module. The port in C has seen some improvements in the XML schema, as well as in speed. There is not yet an aug_from_xml method, and it might take some time to come, as it brings up a lot of merging issues. This change introduces a dependency on libxml2.  </p>\n<p>Dominic Cleal has been working on the new aug_srun method, which he wishes for his  <a href=\"http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas\">Augeas module for Puppet</a>. In order to achieve this, he introduced a way to set the context of XPath expressions in /augeas/context in order to use relative paths. This is a well-known feature for the users of the Augeas module for Puppet, and it is now available for all Augeas users.  </p>\n<p>A lot of lenses have been fixed and improved. As a matter of fact, when I wrote the <a href=\"http://www.raphink.info/2011/12/unit-testing-your-configuration-files.html\">Config::Augeas::Validator</a> Perl module, the goal was initially to test our configuration files against the Augeas lenses. But after testing some 120.000 files, I've obviously found a few flaws in the lenses and had to fix them.  </p>\n<p>The package for Augeas 0.10.0 has entered Ubuntu Precise <a href=\"https://lists.ubuntu.com/archives/precise-changes/2011-December/004129.html\">this afternoon</a>. Versions for older releases are available <a href=\"https://launchpad.net/~raphink/+archive/augeas/+packages\">on my Augeas PPA</a>. Feedback is most welcome!</p>"},{"url":"/posts/20110826-blogspot-colored-initials-in-latex/","relativePath":"posts/20110826-blogspot-colored-initials-in-latex.md","relativeDir":"posts","base":"20110826-blogspot-colored-initials-in-latex.md","name":"20110826-blogspot-colored-initials-in-latex","frontmatter":{"title":"Colored initials in LaTeX","template":"post","date":"2011-08-26T19:01:00.001+02:00","canonical_url":"https://raphink.blogspot.com/2011/08/colored-initials-in-latex.html","blogspot_url":"https://raphink.blogspot.com/2011/08/colored-initials-in-latex.html","tags":["latex","ubuntu"]},"html":"<p>Georg Duffner, the creator of the <a href=\"http://www.georgduffner.at/ebgaramond/\">EB Garamond open-source font</a>, has been working on an initials font for EB Garamond based on a 16th century French Bible lately. A few days ago, he was thinking about producing colored initials with it, and had the idea of splitting the font in two: one font for the background ornament, and one font for the foreground letter to superimpose on it.  </p>\n<p>Base on this idea, I have hacked a little LaTeX module to typeset the initials in a simple way. The module can be found <a href=\"https://github.com/raphink/eblettrine\">on github</a> and is based on the lettrine LaTeX module.  </p>\n<p>It makes use of fontspec to load the fonts so it only works with XeTeX and LuaTeX.  </p>\n<p>The alphabet only contains 3 letters for now, so not really much can be achieved with it so far, but the code is there.  </p>\n<p>Here is an example reproducing parts of the 16th century Bible used to get the samples of the initials:  </p>\n<p><a href=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY5NXP489C8FD8qR9sfWMqQayRDzriRd9hxcct_gGxUACb4qi9vUL7i9UHE7y-vpI8o4oTRDqFidmvaN7hXU10BLtTmQqVr_S_8KPA_ANdeKUpC4JuV4XwVFZqaUtjpOwIM3uUleQhxeM_/s1600/eblettrine.png\"><img src=\"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY5NXP489C8FD8qR9sfWMqQayRDzriRd9hxcct_gGxUACb4qi9vUL7i9UHE7y-vpI8o4oTRDqFidmvaN7hXU10BLtTmQqVr_S_8KPA_ANdeKUpC4JuV4XwVFZqaUtjpOwIM3uUleQhxeM_/s320/eblettrine.png\"></a></p>"},{"url":"/posts/20120314-blogspot-from-openvz-to-vmware/","relativePath":"posts/20120314-blogspot-from-openvz-to-vmware.md","relativeDir":"posts","base":"20120314-blogspot-from-openvz-to-vmware.md","name":"20120314-blogspot-from-openvz-to-vmware","frontmatter":{"title":"From openvz to VMWare","template":"post","date":"2012-03-14T10:57:00.000+01:00","canonical_url":"https://raphink.blogspot.com/2012/03/from-openvz-to-vmware.html","blogspot_url":"https://raphink.blogspot.com/2012/03/from-openvz-to-vmware.html","tags":["debian","sysadmin","ubuntu","virtualization"]},"html":"<p>I was recently asked to convert an openvz container to a VMWare instance. I found a <a href=\"http://communities.vmware.com/message/1719787#1719787\">tutorial on a forum</a> which helped but had to be adapted slightly.  </p>\n<p>Here is how I eventually managed to do the conversion:  </p>\n<ol>\n<li>On the openvz host, create an image (in my case, I need 20GB):<br>\n<code>dd if=/dev/zero of=test.img bs=516096c count=40000</code></li>\n<li>Create a partition table in the image:<br>\n<code>fdisk test.img</code><br>\nType: <code>o n p 1 2048 w</code></li>\n<li>Mount the image using the offset corresponding to the partition table (in our case, 2048*512=1048576):<br>\n<code>losetup -o1048576 /dev/loop0 test.img mke2fs -b1024 /dev/loop0 tune2fs -j /dev/loop0 mount /dev/loop0 /mnt</code></li>\n<li>Retrieve the UUID of the partition. Note it somewhere, you will need it several times:<br>\n<code>blkid /dev/loop0</code></li>\n<li>Copy the container files to the image. I used rsync to exclude some data that were too big. Make sure to use <code>--numeric-ids</code> to prevent bad mapping of UIDs:<br>\n<code>rsync -av --exclude 'somedir' --numeric-ids /vz/root/1234/ /mnt</code></li>\n<li>Install grub on the disk:<br>\n<code>grub-install --root-directory /mnt /dev/loop0</code></li>\n<li>Chroot into the mounted partition:<br>\n<code>chroot /mnt</code></li>\n<li>Install a kernel and grub (using apt for example):<br>\n<code>apt-get install linux-image-foobar #adapt to your kernel version apt-get install grub-pc</code></li>\n<li>Give a password to root:<br>\n<code>passwd</code></li>\n<li>Change inittab to add the ttys:<br>\n<code>1:2345:respawn:/sbin/getty 38400 tty1 2:23:respawn:/sbin/getty 38400 tty2 3:23:respawn:/sbin/getty 38400 tty3 4:23:respawn:/sbin/getty 38400 tty4 5:23:respawn:/sbin/getty 38400 tty5 6:23:respawn:/sbin/getty 38400 tty6</code></li>\n<li>Create an /etc/fstab file (change the UUID with yours):<br>\n<code>proc /proc proc nodev,noexec,nosuid 0 0 UUID=7925e5b1-f2ad-4cdc-95f9-984d25378194 / ext4 errors=remount-ro 0 1</code></li>\n<li>Umount the image and convert it to a vmdk image:<br>\n<code>umount /mnt kvm-img convert -f raw test.img -O vmdk test.vmdk</code></li>\n<li>Boot the machine on the virtual disk with VMWare or Virtualbox. You should get a grub prompt (adapt with your UUID). In grub2, you can use autocompletion for the Linux kernel and the initrd image:<br>\n<code>grub> insmod ext2 grub> set root='(hd0,msdos1)' grub> linux /boot/vmlinuz-foobar root=UUID=7925e5b1-f2ad-4cdc-95f9-984d25378194 ro grub> initrd /boot/initrd-foobar grub> boot</code></li>\n<li>Log in to the machine and run <code>update-grub</code></li>\n<li>Adapt your network settings</li>\n</ol>"},{"url":"/posts/20111203-blogspot-unit-testing-your-configuration-files/","relativePath":"posts/20111203-blogspot-unit-testing-your-configuration-files.md","relativeDir":"posts","base":"20111203-blogspot-unit-testing-your-configuration-files.md","name":"20111203-blogspot-unit-testing-your-configuration-files","frontmatter":{"title":"Unit testing your configuration files","template":"post","date":"2011-12-03T21:12:00.001+01:00","canonical_url":"https://raphink.blogspot.com/2011/12/unit-testing-your-configuration-files.html","blogspot_url":"https://raphink.blogspot.com/2011/12/unit-testing-your-configuration-files.html","tags":["augeas","perl","sysadmin","ubuntu"]},"html":"<p>At work, we have a lot of different services running and we end up with hundreds of thousands of configuration files in our configuration repository (especially since we don't use templates - more on that in another upcoming post...).  </p>\n<p>In order to ensure the quality of these files (avoiding syntax errors and insecure configurations alike), we had the idea of writing a system to unit test the configuration files. Being involved in <a href=\"http://augeas.net/\">Augeas</a>, I thought it would make a great parsing backend to write the tests.  </p>\n<p>This gave birth to what is currently a Perl module called <a href=\"http://search.cpan.org/perldoc?Config::Augeas::Validator\">Config::Augeas::Validator</a>, which comes with a Perl script and an SVN wrapper (this is what we used, but wrappers for other VCS are welcome) to plug it to pre- and post-commit hooks.  </p>\n<p>The module relies on Augeas (and its lenses) to parse the configuration files. On top of that, you need to specify rules (which are essentially unit test scenarii), at least one for each type of file you wish to test. A minimal rule might look like this:  </p>\n<p><code>[DEFAULT] lens=Fstab pattern=.*/fstab</code>  </p>\n<p>This rule will tell augeas-validator to test all files whose full path matches .*/fstab against the Fstab.lns Augeas lens. If the file contains syntax errors (that is, the lens fails to parse it), the program will report it and exit with a status of 1.  </p>\n<p>Here is another simple example adding two unit tests with warnings:  </p>\n<p><code>[DEFAULT] lens=PHP pattern=.*/(php\\.ini|php[45]/conf\\.d/.*\\.ini) [file_uploads] name=file_uploads explanation=file_uploads should be set to 'Off' type=count expr=$file//file_uploads[. != 'Off'] value=0 level=warning tags=security [expose_php] name=expose_php explanation=expose_php should be set to 'Off' type=count expr=$file//expose_php[. != 'Off'] value=0 level=warning tags=security</code>  </p>\n<p>This test checks for php.ini files. Not only does it fail with status 1 if there is a syntax error, it also applies two unit tests called file_uploads and expose_php, which will make the program output a warning and exit with status 2 if they are not met. The essential part of the rules is the expr parameter, which is an <a href=\"http://augeas.net/page/Path_expressions\">Augeas XPath expression</a>. In this case, the expressions must not match any nodes in order to pass the tests (hence value=0).  </p>\n<p>These are just simple examples of what this module can do. You can find more examples shipped with the module itself. As a last example, here is one for Apache configuration files (in Debian/Ubuntu):  </p>\n<p><code>[DEFAULT] lens=Httpd pattern=.*/(sites-available/.*)|(apache2/.*\\.conf) [one_servername] name=One ServerName per VirtualHost * explanation=There should be only one ServerName per VirtualHost * entry type=count expr=$file[label() != \"default\"]/VirtualHost[arg =~ regexp(\"\\*(:80)?\")][count(directive[. = \"ServerName\"]) != 1] value=0 level=warning [bufferedlogs] name=BufferedLogs explanation=BufferedLogs must be set to Off type=count expr=$file//directive[. = \"BufferedLogs\"][arg = \"On\"] value=0 level=warning tags=security [timeout] name=Timeout explanation=Timeout must be at least 45 type=count expr=$file//directive[. = \"Timeout\"][int(arg) &#x3C; 45] value=0 level=warning tags=security</code></p>"},{"url":"/posts/20121030-blogspot-hp-deskjet-3520-e-all-in-one-and-ubuntu/","relativePath":"posts/20121030-blogspot-hp-deskjet-3520-e-all-in-one-and-ubuntu.md","relativeDir":"posts","base":"20121030-blogspot-hp-deskjet-3520-e-all-in-one-and-ubuntu.md","name":"20121030-blogspot-hp-deskjet-3520-e-all-in-one-and-ubuntu","frontmatter":{"title":"HP Deskjet 3520 e-All-in-One and Ubuntu Quantal","template":"post","date":"2012-10-30T21:23:00.001+01:00","canonical_url":"https://raphink.blogspot.com/2012/10/hp-deskjet-3520-e-all-in-one-and-ubuntu.html","blogspot_url":"https://raphink.blogspot.com/2012/10/hp-deskjet-3520-e-all-in-one-and-ubuntu.html","tags":["ubuntu"]},"html":"<p>I just got an <a href=\"http://h10010.www1.hp.com/wwpc/us/en/sm/WF05a/18972-18972-238444-1142650-410635-5162504.html?dnr=1\">HP Deskjet 3520 e-All-in-One</a> multifunction printer today, which works very well with Ubuntu Quantal.  </p>\n<p>Here are the steps to print on it through Wifi. I used WPS to associate it to my network:  </p>\n<ol>\n<li>Plug printer and turn it on. Follow the instructions on screen until it tells you to set up your computer;</li>\n<li>Turn on WPS on your wireless router. If it's available, it should be in the wireless security settings. You can choose either the “Push button” or the “Pin” option.</li>\n<li>Go to the “Wifi-Protected Setup” menu on the printer and choose the option you wish to use (according to what you chose on your wifi router);</li>\n<li>Associate the printer with your network;</li>\n<li>On your Ubuntu Quantal machine, go to the Printer Settings (in System Settings), add a printer, choose the “Network Printer” menu and the printer should appear. Just choose the HPLIP driver and you're done. Note that it also installs the scanner, so you can remotely scan using Simple Scan.</li>\n</ol>\n<p>I also had to setup another machine on Ubuntu Precise. Unfortunately, the driver for the 3520 printer was added in HPLIP  3.12.6. However, downloading the hplip packages for Quantal and installing them worked fine on Precise. The packages you need are:  </p>\n<ul>\n<li>hplip;</li>\n<li>hplip-data;</li>\n<li>libhpmud0;</li>\n<li>libsane-hpaio.</li>\n</ul>\n<p> Once the printer's network is setup, you can access it via an HTTP interface at its IP address.</p>"},{"url":"/posts/20200430-automating-freeipa-with-terraform-43b7/","relativePath":"posts/20200430-automating-freeipa-with-terraform-43b7.md","relativeDir":"posts","base":"20200430-automating-freeipa-with-terraform-43b7.md","name":"20200430-automating-freeipa-with-terraform-43b7","frontmatter":{"title":"Automating FreeIPA with Terraform","template":"post","date":"2020-04-30T10:48:36Z","excerpt":"The FreeIPA Terraform provider allows to automate creation and management of FreeIPA resources.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxterraform_bandeau.png.pagespeed.ic.neAGqH-_lX.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxterraform_bandeau.png.pagespeed.ic.neAGqH-_lX.webp","canonical_url":"https://www.camptocamp.com/actualite/automating-freeipa-with-terraform/","devto_url":"https://dev.to/camptocamp-ops/automating-freeipa-with-terraform-43b7"},"html":"<p><a href=\"https://www.terraform.io/\">Terraform</a> is great for cloud provisioning and has now become a standard tool to deploy infrastructures as code, in a DevOps fashion.</p>\n<p><a href=\"https://www.terraform.io/docs/providers/index.html\">Many plugins</a> exist to cover specific needs, from major cloud providers (AWS, GCP, Azure, etc.) to specific app APIs (Grafana, GitHub, or even PostgreSQL). The community provides and maintains <a href=\"https://www.terraform.io/docs/providers/type/community-index.html\">additional providers</a> which can be installed and used in any Terraform project as plugins.</p>\n<p>Camptocamp developed several providers over the last few years. Besides\nthe [official Rancher provider] (<a href=\"https://www.terraform.io/docs/providers/rancher/index.html\">https://www.terraform.io/docs/providers/rancher/index.html</a>) which was co-developed by our team and contributed to the community, we maintain providers to integrate Terraform with the <a href=\"https://github.com/camptocamp/terraform-provider-puppetca\">PuppetCA</a>, the <a href=\"https://github.com/camptocamp/terraform-provider-puppetdb\">PuppetDB</a>, as well as the <a href=\"https://github.com/camptocamp/terraform-provider-pass\">gopass password vault</a>.</p>\n<p>More recently, we started having a need to automate FreeIPA resources using Terraform, so we started <a href=\"https://github.com/camptocamp/terraform-provider-freeipa\">a new provider</a>.</p>\n<h1>Installing</h1>\n<p>Installing additional Terraform providers is <a href=\"https://www.terraform.io/docs/configuration/providers.html#third-party-plugins\">rather straightforward</a>.\nYou can simply download the binary from the <a href=\"https://github.com/camptocamp/terraform-provider-freeipa/releases\">releases page</a> and\ndrop it in your <code>~/.terraform.d/plugins</code> directory.</p>\n<h1>Usage</h1>\n<p>Like all other Terraform providers, you first need to configure the provider. You can do that using either hardcoded parameters or environment variables. In this second case, we strongly encourage you to make use of <a href=\"https://github.com/cyberark/summon\">summon</a> as a wrapper to dynamically expose the environment variables at call time.</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">provider freeipa {\n  host = &quot;ipa.example.test&quot; # or set $FREEIPA_HOST\n  username = &quot;admin&quot; # or set $FREEIPA_USERNAME\n  password = &quot;P@S5sw0rd&quot; # or set $FREEIPA_PASSWORD\n  insecure = true\n}</code>\n        </deckgo-highlight-code>\n<p>Next, you can start writing resources to manage FreeIPA hosts and DNS  records:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">resource freeipa_host &quot;foo&quot; {\n  fqdn = &quot;foo.example.test&quot;\n  description = &quot;This is my foo host&quot;\n  force = true\n  random = true\n  userpassword = &quot;abcde&quot;\n}\n\nresource freeipa_dns_record &quot;bar&quot; {\n  idnsname = &quot;bar&quot;\n  dnszoneidnsname = &quot;myzone&quot;\n  dnsttl = 20\n  records = [&quot;1.2.3.4&quot;]\n}</code>\n        </deckgo-highlight-code>\n<p>At the moment, this FreeIPA provider only features 2 resource types, to manage FreeIPA hosts and DNS records. Don't hesitate to <a href=\"https://github.com/camptocamp/terraform-provider-freeipa\">contribute to it</a> by providing more resource types!</p>\n<p><em>This post was originally published on <a href=\"https://www.camptocamp.com/actualite/automating-freeipa-with-terraform/\">https://www.camptocamp.com/actualite/automating-freeipa-with-terraform/</a></em></p>"},{"url":"/posts/20200429-cleaning-up-puppet-code-4da2/","relativePath":"posts/20200429-cleaning-up-puppet-code-4da2.md","relativeDir":"posts","base":"20200429-cleaning-up-puppet-code-4da2.md","name":"20200429-cleaning-up-puppet-code-4da2","frontmatter":{"title":"Cleaning up Puppet Code","template":"post","date":"2020-04-29T10:54:01Z","excerpt":"Code quality is important to ensure style consistency and easy maintenance. Puppet-lint, Onceover and puppet-ghostbuster help ensure Puppet code quality.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","canonical_url":"https://www.camptocamp.com/actualite/cleaning-up-puppet-code/","devto_url":"https://dev.to/camptocamp-ops/cleaning-up-puppet-code-4da2"},"html":"<p>After months and years of using <a href=\"https://puppet.com/\">Puppet</a>, the code base becomes increasingly complex and cluttered. How can you ensure its quality, as well as clean up unused code?</p>\n<h1>puppet-lint</h1>\n<p>In the Puppet world, <a href=\"http://puppet-lint.com/\">puppet-lint</a> is the reference for code quality. It is used as a standard to check that modules follow the <a href=\"https://puppet.com/docs/puppet/5.5/style_guide.html\">style guide</a>, ensuring consistency in coding style and practices. puppet-lint can also be used in your control repository, to check your private modules (such as <a href=\"https://puppet.com/docs/pe/latest/the_roles_and_profiles_method.html\">roles &#x26; profiles</a>).</p>\n<p>There's at least three ways of achieving this: using <a href=\"https://puppet.com/docs/pdk/1.x/pdk.html\">PDK</a>, a <code>Rakefile</code>, or <a href=\"https://github.com/dylanratcliffe/onceover\">onceover</a> along with its <a href=\"https://github.com/declarativesystems/onceover-codequality\">code quality plugin</a>.</p>\n<h2>PDK</h2>\n<p><a href=\"https://puppet.com/docs/pdk/1.x/pdk.html\">PDK</a>, the standard tool to manage Puppet modules in a standard way, also works with control repositories. Once <a href=\"https://puppet.com/docs/pdk/1.x/pdk_install.html\">installed</a>, you can convert your control repository and run validation tests with:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ pdk convert\n$ pdk validate</code>\n        </deckgo-highlight-code>\n<h2>Rakefile method</h2>\n<p>The <code>Rakefile</code> method is <a href=\"https://github.com/rodjek/puppet-lint#testing-with-puppet-lint-as-a-rake-task\">a easy way</a> to automate <code>puppet-lint</code>:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">Rake::Task[:lint].clear\nPuppetLint::RakeTask.new :lint do |config|\n  config.ignore_paths = [\n    &#39;modules/**/*.pp&#39;,\n    &#39;vendor/**/*&#39;,\n  ]\n  config.disable_checks = [\n    &#39;80chars&#39;,\n    &#39;documentation&#39;,\n  ]\n  config.fail_on_warnings = true\n  config.fix = true if ENV[&#39;PUPPETLINT_FIX&#39;] == &#39;yes&#39;\nend</code>\n        </deckgo-highlight-code>\n<p>Add a <code>Gemfile</code> in your repository to install <code>puppet-lint</code>:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">source ENV[&#39;GEM_SOURCE&#39;] || &quot;https://rubygems.org&quot;\n\ngroup :development, :test do\n  gem &#39;rake&#39;,                                             :require =&gt; false\n  gem &#39;puppet-lint&#39;,                                      :require =&gt; false\n  \n  # Other lint plugins (optional)\n  gem &#39;puppet-lint-spaceship_operator_without_tag-check&#39;, :require =&gt; false\n  gem &#39;puppet-lint-unquoted_string-check&#39;,                :require =&gt; false\n  gem &#39;puppet-lint-undef_in_function-check&#39;,              :require =&gt; false\n  gem &#39;puppet-lint-leading_zero-check&#39;,                   :require =&gt; false\n  gem &#39;puppet-lint-trailing_comma-check&#39;,                 :require =&gt; false\n  gem &#39;puppet-lint-file_ensure-check&#39;,                    :require =&gt; false\n  gem &#39;puppet-lint-version_comparison-check&#39;,             :require =&gt; false\n  \n  # You can also use the voxpupuli-test gem,\n  # which pulls rake, puppet-lint &amp; plugins as dependencies\n  gem &#39;voxpupuli-test&#39;,                                   :require =&gt; false\nend</code>\n        </deckgo-highlight-code>\n<p>You can then run the lint with:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ bundle install --path vendor/bundle $ bundle exec rake lint\n# And if you want to autofix the detected mistakes\n$ PUPPETLINT_FIX=yes bundle exec rake lint</code>\n        </deckgo-highlight-code>\n<h2>Onceover Code Quality</h2>\n<p><a href=\"https://github.com/dylanratcliffe/onceover\">Onceover</a> is a toolbox to automate tasks for Puppet control repositories. Among other things, its code quality plugin allows to run syntax checks and invoke <code>puppet-lint</code>.</p>\n<p>In order to use it, update your <code>Gemfile</code>, e.g.:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">source ENV[&#39;GEM_SOURCE&#39;] || &quot;https://rubygems.org&quot;\n\ngroup :development, :test do\n  gem &#39;voxpupuli-test&#39;,                                   :require =&gt; false\n  \n  gem &#39;onceover&#39;,                                         :require =&gt; false\n  gem &#39;onceover-codequality&#39;,                             :require =&gt; false\nend</code>\n        </deckgo-highlight-code>\n<p>Refresh your bundle and run onceover:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ bundle update $ bundle exec onceover run codequality --no-docs</code>\n        </deckgo-highlight-code>\n<p>Ideally, run this on every commit in a Continuous Integration/Continuous Deployment setup. At Camptocamp, we use a <a href=\"https://docs.gitlab.com/ee/ci/\">GitLab CI</a> pipeline to check our control repo using Onceover before deploying it with <a href=\"https://github.com/puppetlabs/r10k\">r10k</a> (also running a GitLab CI runner).</p>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/puppetmaster_pipeline.png\" alt=\"PuppetMaster Pipeline in GitLab CI\"></p>\n<h1>Getting rid of dead code</h1>\n<p>You've checked the quality of your existing code. Good! But what if you're actually maintaining and cleaning code that you don't use anymore? This would be quite the waste of time... At Camptocamp, we've built on <code>puppet-lint</code> to provide a system to detect unused code and help us clean it up. </p>\n<p>This is what the <a href=\"https://github.com/camptocamp/puppet-ghostbuster\">puppet-ghostbuster</a> project is for. Under the hood, <code>puppet-ghostbuster</code> is a collection of <code>puppet-lint</code> plugins, distributed in a single <code>puppet-ghostbuster</code> gem.</p>\n<p>These plugins analyze your Puppet code and then connect to your PuppetDB to check if that code is actually used for any known node. It can also check Hiera data for unused keys. Just as previously, you can set it up as a Rake task, but our current setup requires a <a href=\"https://github.com/rodjek/puppet-lint/pull/919\">patch</a> to <code>puppet-lint</code> in order to whitelist the <code>puppet-lint</code> checks activated (the current release of <code>puppet-lint</code> only supports blacklisting checks in Rake tasks).</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">source ENV[&#39;GEM_SOURCE&#39;] || &quot;https://rubygems.org&quot;\n\ngroup :development, :test do\n  gem &#39;rake&#39;,                                             :require =&gt; false\n  gem &#39;puppet-lint&#39;,                                      :require =&gt; false,\n    :git =&gt; &#39;https://github.com/raphink/puppet-lint&#39;,\n    :ref =&gt; &#39;2cac4fb&#39;   # Includes patch for whitelisting checks\n  gem &#39;puppet-ghostbuster&#39;,                               :require =&gt; false\nend</code>\n        </deckgo-highlight-code>\n<p>You can then set up a Rake task such as this one:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">PuppetLint::RakeTask.new :ghostbuster do |config|\n  config.pattern = [&#39;./site/**/*&#39;]\n  config.only_checks = [\n    &#39;ghostbuster_classes&#39;,\n    &#39;ghostbuster_defines&#39;,\n    &#39;ghostbuster_facts&#39;,\n    &#39;ghostbuster_files&#39;,\n    &#39;ghostbuster_functions&#39;,\n    &#39;ghostbuster_hiera_files&#39;,\n    &#39;ghostbuster_templates&#39;,\n    &#39;ghostbuster_types&#39;,\n  ]\n  config.fail_on_warnings = true\nend</code>\n        </deckgo-highlight-code>\n<p><code>puppet-ghostbuster</code> requires info to connect to your PuppetDB, so you need to provide the following environment variables:</p>\n<ul>\n<li><code>PUPPETDB_URL</code>: URLs to PuppetDB</li>\n<li><code>PUPPETDB_CERT_FILE</code>: path to the certificate to use to connect to\nPuppetDB</li>\n<li><code>PUPPETDB_KEY_FILE</code>: path to the key to use to connect to PuppetDB</li>\n<li><code>PUPPETDB_CACERT_FILE</code>: path to the Puppet CA certificate</li>\n<li><code>HIERA_YAML_PATH</code>: path to <code>hiera.yaml</code> to use</li>\n</ul>\n<p>If you don't want to provide certificates and keys, you can connect to the PuppetDB through the unencrypted port 8080, for example by forwarding it through SSH. At Camptocamp, we're automating this setup by using <a href=\"https://github.com/cyberark/summon\">summon</a> as a wrapper to launch the command.</p>\n<p>We store the certificates and keys to connect to PuppetDB in <a href=\"https://github.com/gopasspw/gopass\">gopass</a>, then provide a <code>secrets.yml</code> file like so:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">PUPPETDB_URL: https://puppetdb.example.com:8081\nPUPPETDB_CERT_FILE: !var:file path/to/secret:cert\nPUPPETDB_KEY_FILE: !var:file path/to/secret:key\nPUPPETDB_CACERT_FILE: !var:file path/to/secret:cacert\nHIERA_YAML_PATH: ./hiera.yaml</code>\n        </deckgo-highlight-code>\n<p>Which allows to run:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ summon bundle exec rake ghostbuster</code>\n        </deckgo-highlight-code>\n<p>This returns a list of classes, defines, files, templates, etc. that are unused in our code. We can then check these results and clean up our code! </p>\n<p>Do you have ideas to contribute to <code>puppet-ghostbuster</code>? <a href=\"https://github.com/camptocamp/puppet-ghostbuster\">Pull requests are welcome</a>! You can also <a href=\"https://www.camptocamp.com/contact/\">contact us</a> for quotes on Puppet consulting or <a href=\"https://www.camptocamp.com/formations/\">training</a>!</p>\n<p><em>This post was originally published on <a href=\"https://www.camptocamp.com/actualite/cleaning-up-puppet-code/\">https://www.camptocamp.com/actualite/cleaning-up-puppet-code/</a></em></p>"},{"url":"/posts/20200430-backup-your-container-data-2f3f/","relativePath":"posts/20200430-backup-your-container-data-2f3f.md","relativeDir":"posts","base":"20200430-backup-your-container-data-2f3f.md","name":"20200430-backup-your-container-data-2f3f","frontmatter":{"title":"Backup your Container Data","template":"post","date":"2020-04-30T10:48:01Z","excerpt":"Containers have become a great facility to easily deploy applications, whether locally or on orchestrated clusters. However, containers are ephemeral, meaning their data should be stored externally and should be backed up.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fbanner.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fbanner.png","canonical_url":"https://www.camptocamp.com/actualite/backup-your-container-data/","devto_url":"https://dev.to/camptocamp-ops/backup-your-container-data-2f3f"},"html":"<p>Containers have become a great facility to easily deploy applications, whether locally or on orchestrated clusters.</p>\n<p>However, containers are ephemeral, meaning their data should be stored externally. When possible, they can be stored using databases or object storage. Most often though, you will need to resort to using data volumes, mounted inside your containers. How then can be perform a backup of this data?</p>\n<h1>Data location is known</h1>\n<p>Contrarily to the traditional situation in application deployment, the location of critical data in containers is known, since it uses named volumes. We can thus connect to the Docker socket or the API managing the volumes to list them and perform the backups.</p>\n<h1>Introducing Bivac</h1>\n<p><a href=\"https://camptocamp.github.io/bivac/\">Bivac</a> is a tool created to do just that. It can be plugged to either a Docker socket, a Rancher API, or a Kubernetes server. It will then list the volumes on the platform and automatically back them up on a regular basis, using <a href=\"https://restic.net/\">Restic</a> to transfer the data to an object storage provider (e.g. AWS S3).</p>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/bivac-435x400.png\" alt=\"Bivac Logo\"></p>\n<p>In addition, Bivac can provide metrics on the backup statuses as it exposes a <a href=\"https://prometheus.io/\">Prometheus</a> endpoint.</p>\n<p>Using the REST client, backups can be listed, executed on demand, and it is also possible to restore volumes.</p>\n<h1>Installation</h1>\n<p>Bivac can easily be installed a binary or a container. Here are some examples, deploying it locally on Docker, or using Kubernetes.</p>\n<h2>Using Docker</h2>\n<p>The following <code>docker-compose.yml</code> file can be used to deploy the Bivac manager:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">---\nversion: &#39;3&#39;\nservices:\n  bivac:\n    image: camptocamp/bivac:2.2\n    command: &quot;manager -v&quot;\n    ports:\n      - &quot;8182:8182&quot;\n    volumes:\n      - &quot;/var/run/docker.sock:/var/run/docker.sock:ro&quot;\n    environment:\n      BIVAC_AGENT_IMAGE: camptocamp/bivac:2.1\n      BIVAC_SERVER_PSK: super-secret-psk\n      RESTIC_PASSWORD: not-so-good-password\n      BIVAC_TARGET_URL: s3:my-bucket\n      AWS_ACCESS_KEY_ID: XXXXX\n      AWS_SECRET_ACCESS_KEY: XXXXX</code>\n        </deckgo-highlight-code>\n<p>Additionally, you can also deploy a local Prometheus server to retrieve the metrics. See <a href=\"https://github.com/camptocamp/bivac/blob/master/contrib/examples/docker-compose/docker-compose.yml\">the full example</a>.</p>\n<h2>Using Kubernetes</h2>\n<p>The easiest way to deploy a Bivac manager on Kubernetes is to use Camptocamp's Helm chart:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ helm repo add camptocamp http://charts.camptocamp.com\n$ helm install camptocamp/bivac --version 1.0.0</code>\n        </deckgo-highlight-code>\n<h1>Using the CLI</h1>\n<p>The CLI can be downloaded from <a href=\"https://github.com/camptocamp/bivac/releases/tag/2.2.0\">the releases page</a>. Once the binary is installed, you can use it to list backups, perform backups, or restore data.</p>\n<h2>Connecting to the manager</h2>\n<p>The CLI needs to be connected to the Bivac manager, using its HTTP URL and PSK (defined in the deployment). This can be performed using either the <code>--remote.address</code> and <code>--server.psk</code> options, or by setting the <code>BIVAC_REMOTE_ADDRESS</code> and <code>BIVAC_SERVER_PSK</code>.</p>\n<h2>Listing backups</h2>\n<p>The <code>bivac volumes</code> command lets you list the volumes managed by Bivac:</p>\n<p><a href=\"https://gist.github.com/raphink/fe24bf6bc1205633471432f02ec13c15\">Gist — bivac-volumes.sh</a></p>\n<h2>Perform backups</h2>\n<p>While Bivac automatically performs backups at a regular interval, the CLI can also be used to trigger backups manually:</p>\n<p><a href=\"https://gist.github.com/raphink/fe24bf6bc1205633471432f02ec13c15\">Gist — bivac-backup.sh</a></p>\n<h2>Restore data</h2>\n<p>Bivac stores restic backups on object storage and lets you restore them using the <code>backup restore</code> command:</p>\n<p><a href=\"https://gist.github.com/raphink/fe24bf6bc1205633471432f02ec13c15\">Gist — bivac-restore.sh</a></p>\n<h1>Going further</h1>\n<p>More features are available, such as the ability to <a href=\"https://github.com/camptocamp/bivac/wiki/Usage#manage-a-remote-restic-repository\">manage a remote Restic repository</a>.  See the documentation for more information.</p>\n<h1>Conclusion</h1>\n<p>Bivac allows to easily backup data, monitor their status and restore them, whether you are using raw Docker, Rancher volumes or Kubernetes.</p>\n<p><em>This post was originally published on <a href=\"https://www.camptocamp.com/actualite/backup-your-container-data/\">https://www.camptocamp.com/actualite/backup-your-container-data/</a></em></p>"},{"url":"/posts/20200430-integrating-prometheus-with-puppetdb-aom/","relativePath":"posts/20200430-integrating-prometheus-with-puppetdb-aom.md","relativeDir":"posts","base":"20200430-integrating-prometheus-with-puppetdb-aom.md","name":"20200430-integrating-prometheus-with-puppetdb-aom","frontmatter":{"title":"Integrating Prometheus with PuppetDB","template":"post","date":"2020-04-30T10:48:51Z","excerpt":"Many applications are not containerized, and we still need to monitor their nodes. Prometheus PuppetDB SD allows to discover nodes in the PuppetDB and generate Prometheus configurations automatically.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxbanner-1.png.pagespeed.ic.-LxmyH1pjm.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxbanner-1.png.pagespeed.ic.-LxmyH1pjm.webp","canonical_url":"https://www.camptocamp.com/actualite/integrating-prometheus-with-puppetdb/","devto_url":"https://dev.to/camptocamp-ops/integrating-prometheus-with-puppetdb-aom"},"html":"<p>Most companies that have switched their deployments to containers have faced this issue: traditional monitoring systems just don't cut it when it comes to observability of containerized applications. Instead of focusing on nodes and applications running on them, the cluster approach to container orchestration systems requires to target application instances, which can run on multiple nodes —even several times on a single node— and typically have short life spans.</p>\n<h1>A new monitoring paradigm</h1>\n<p>Fortunately for us, Prometheus came early on in the ecosystem, providing an elegant solution to gather metrics from microservices and derive all sort of observability tools from them, including monitoring. Prometheus also allows to monitor the cluster nodes, by returning their metrics and aggregating them into views of their own. Problem solved, we can now get rid of our historical monitoring infrastructure. Or can we?</p>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/prometheus-550x120.png\"></p>\n<p>As much as we'd like to think all our applications are now containerized and all our machines are neutral cluster nodes in a large cattle, the reality is often very different. Most companies still have a large quantity of specialized machines---even snowflakes at times--- that are not taken into account by your latest Kubernetes cluster. Should we keep Nagios running for those, or is it possible to make them fit into the new paradigm?</p>\n<h1>Using PuppetDB</h1>\n<p>For those of us running Puppet, the PuppetDB has for years been a great source of information on nodes managed by Puppet. It contains facts, catalogs, reports and more for all the nodes in the fleet. Let's use this information to monitor the nodes and their services dynamically, using Prometheus!</p>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/puppet-2-400x400.png\"></p>\n<p><a href=\"https://github.com/camptocamp/prometheus-puppetdb-sd\">Prometheus PuppetDB SD</a> allows to link PuppetDB with your Prometheus infrastructure. At a regular interval, it queries the PuppetDB and retrieves a list of targets. It then outputs a scrape configuration which Prometheus can use. Sounds simple? It really is!</p>\n<h1>Puppet and Prometheus</h1>\n<p>The Vox Pupuli Puppet community has a great module to manage Prometheus. The <a href=\"https://forge.puppet.com/puppet/prometheus\">puppet-prometheus</a> module works out of the box and allows to install and configure a Prometheus server. It also provides the <code>prometheus::scrape_job</code> defined type to declare scrape jobs to be added to the server.</p>\n<p>Declaring these scrape jobs as exported resources tags them as such in the PuppetDB, and they can then be realized on the Prometheus server. This is extremely useful, but only works when the Prometheus server is managed by Puppet, not when it is containerized! Prometheus PuppetDB SD fills this gap by scraping the exported resources from the PuppetDB and generating the Prometheus configurations, so you can get the best of both worlds: scrape jobs declared in Puppet alongside your applications, and a containerized Prometheus server!</p>\n<h1>Installation and Configuration</h1>\n<p>There's several ways to install Prometheus PuppetDB SD, but let's face it: if you're using Prometheus, you probably have a Kubernetes cluster already, so let's install it using Helm:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ helm repo add camptocamp http://charts.camptocamp.com\n$ helm install camptocamp/prometheus-puppetdb-sd --version 2.0.3</code>\n        </deckgo-highlight-code>\n<p>The following values should be provided:</p>\n<ul>\n<li><code>prometheusPuppetdbSd.args.puppetdb.url</code>: the PuppetDB URI</li>\n<li><code>prometheusPuppetdbSd.args.prometheus.proxy-url</code>: the Prometheus\nPush-proxy URL</li>\n<li><code>prometheusPuppetdbSd.args.output.k8s-secret.secret-name</code>: the name\nof the k8s secret used to output the Prometheus configuration</li>\n<li><code>prometheusPuppetdbSd.args.output.k8s-secret.secret-key</code>: the key in\nthe k8s secret used for the output file name</li>\n<li><code>CACert</code>: the CA certificate used to authenticate the PuppetDB</li>\n<li><code>Cert</code>: the certificate used to connect to the PuppetDB</li>\n<li><code>Key</code>: the private key used to connect to the PuppetDB</li>\n</ul>\n<p>With the <a href=\"https://github.com/helm/charts/tree/master/stable/prometheus-operator\">official Prometheus Operator</a>, setting <code>prometheusSpec.additionalScrapeConfigsExternal</code> to <code>true</code> will automatically configure Prometheus to mount the secret called <code>{{ template \"prometheus-operator.fullname\" . }}-prometheus-scrape-confg</code> and use the <code>additional-scrape-configs.yaml</code> key in it as additional configuration. This is thus the easiest way to configure Prometheus PuppetDB SD.</p>\n<p>That's it, you're set!</p>\n<p>Don't hesitate to provide feedback and pull requests on the <a href=\"https://github.com/camptocamp/prometheus-puppetdb-sd\">GitHub repository</a>!</p>\n<p><a href=\"https://github.com/camptocamp/prometheus-puppetdb-sd\">GitHub — camptocamp/prometheus-puppetdb-sd</a></p>\n<p><em>This post was originally published on <a href=\"https://www.camptocamp.com/actualite/integrating-prometheus-with-puppetdb/\">https://www.camptocamp.com/actualite/integrating-prometheus-with-puppetdb/</a></em></p>"},{"url":"/posts/20200501-diffing-puppet-environments-1fno/","relativePath":"posts/20200501-diffing-puppet-environments-1fno.md","relativeDir":"posts","base":"20200501-diffing-puppet-environments-1fno.md","name":"20200501-diffing-puppet-environments-1fno","frontmatter":{"title":"Diffing Puppet Environments","template":"post","date":"2020-05-01T14:49:22Z","excerpt":"Puppet Catalog Diff helps to visualize the differences between two Puppet environments","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","canonical_url":"https://dev.to/camptocamp-ops/diffing-puppet-environments-1fno","devto_url":"https://dev.to/camptocamp-ops/diffing-puppet-environments-1fno"},"html":"<p><a href=\"https://puppet.com\">Puppet</a> is a great tool for configuration managements, allowing to automate hundreds to thousands of nodes at a time in an Infrastructure-as-Code approach.</p>\n<h1>Usual Puppet Control Repository Workflow</h1>\n<p>Good practice usually encourages to use multiple environments in a Puppet setup. Usually, critical nodes are pinned to the <code>production</code> environment, while less critical nodes can be associated with staging environments.</p>\n<p>Using Git along with a <a href=\"https://github.com/puppetlabs/control-repo\">Control Repository</a>, code changes are typically produced in feature branches which get turned to Puppet environments. Once features have been tested on said environment, the feature branch can be merged into a staging branch, where the changes will start affecting nodes pinned to that Puppet environment.</p>\n<p>Finally, once in a while, changes are merged from the staging branch into the production branch, thus affecting all nodes pinned to production.</p>\n<h1>Code Validation</h1>\n<p>While the workflow described is helpful, validating a branch is often a lacking process. Pointing all staging nodes to a feature branch is missing the point entirely, so validation is often done manually, by identifying nodes that <em>may</em> be impacted by the change and running Puppet manually on these nodes, preferably in dry-run mode (<code>--noop</code>).</p>\n<p>When deploying to hundreds of nodes, testing a few is hardly a guaranty that things will go well on all nodes once the branch is merged.</p>\n<p>Fortunately for us, there are tools which can help!</p>\n<h1>Puppet Catalog Diff</h1>\n<p>It might be hard to believe as this module is so poorly known, but the Puppet Catalog Diff project was started some 10 years ago by <a href=\"https://www.devco.net\">R.I. Pienaar</a>! <a href=\"https://github.com/acidprime/puppet-catalog-diff\">Adopted by Zack Smith</a>, it was maintained for a few years, but left mainly unmaintained since 2016.</p>\n<p>As we've used it for years (and GitHub's <a href=\"https://github.com/github/octocatalog-diff\">octocatalog_diff</a> never fit my need), we've adopted it and you will now find the latest version on our GitHub account:</p>\n<p><a href=\"https://github.com/camptocamp/puppet-catalog-diff\">GitHub — camptocamp/puppet-catalog-diff</a></p>\n<h1>Installing</h1>\n<p>Puppet Catalog Diff is a standard Puppet module. You can thus install it using <code>puppet module install</code>, r10k, or even just git.</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ git clone https://github.com/camptocamp/puppet-catalog-diff.git /etc/puppetlabs/code/modules/catalog_diff</code>\n        </deckgo-highlight-code>\n<h1>What does it do?</h1>\n<p>As its name implies, Puppet Catalog Diff allows to perform diffs between Puppet catalogs.</p>\n<p>The module provides three Puppet faces:</p>\n<ul>\n<li><code>puppet catalog seed</code> generates catalogs from a Puppet Master (or PuppetDB)</li>\n<li><code>puppet catalog pull</code> wraps around the <code>seed</code> face to retrieve catalogs from two environments for each node</li>\n<li><code>puppet catalog diff</code> analyzes multiple catalogs and returns the differences per node</li>\n</ul>\n<h2>Local Diff</h2>\n<p>To get started, you can diff local catalogs (in <code>.yaml</code>, <code>.pson</code>, or <code>.yaml</code> formats):</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ puppet catalog diff catalog1.pson catalog2.pson</code>\n        </deckgo-highlight-code>\n<p>will return the differences between the two catalogs.</p>\n<h2>Diff with Catalog Retrieval</h2>\n<p>Most often, you will want to use Puppet Catalog Diff to retrieve catalogs from Puppet Masters.</p>\n<h3>Set up</h3>\n<h4>Generate a Certificate</h4>\n<p>Everything the Puppet world uses OpenSSL for authentication. Setting up Puppet Catalog Diff will thus require an OpenSSL certificate. This can be any certificate signed by the Puppet CA. For example, you can use the <a href=\"https://puppet.com/docs/puppet/latest/puppet_server_ca_cli.html\"><code>puppetserver ca</code> command</a> to generate a certificate:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ puppetserver ca generate --certname catalog-diff</code>\n        </deckgo-highlight-code>\n<p>Retrieve the key and certificate.</p>\n<h4>Set up the Puppet Master</h4>\n<p>By default, Puppet Masters only deliver catalogs for the nodes requesting them. This is set up in the <a href=\"https://puppet.com/docs/puppetserver/latest/config_file_auth.html\"><code>auth.conf</code></a> configuration file, with a rule like:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">{\n    # Allow nodes to retrieve their own catalog\n    match-request: {\n        path: &quot;^/puppet/v3/catalog/([^/]+)$&quot;\n        type: regex\n        method: [get, post]\n    }\n    allow: &quot;$1&quot;\n    sort-order: 500\n    name: &quot;puppetlabs catalog&quot;\n},</code>\n        </deckgo-highlight-code>\n<p>You can deploy this rule using the <code>puppet_authorization::rule</code> defined type from the <a href=\"https://forge.puppet.com/puppetlabs/puppet_authorization\">puppet_authorization</a> Puppet module.</p>\n<p>To allow the <code>catalog-diff</code> certificate to access get any catalog from the Puppet Master, we can modify that rule:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">{\n    # Allow nodes to retrieve their own catalog\n    match-request: {\n        path: &quot;^/puppet/v3/catalog/([^/]+)$&quot;\n        type: regex\n        method: [get, post]\n    }\n    allow: [&quot;$1&quot;,&quot;catalog-diff&quot;]\n    sort-order: 500\n    name: &quot;puppetlabs catalog&quot;\n},</code>\n        </deckgo-highlight-code>\n<p>Even better yet, we can <a href=\"https://puppet.com/docs/puppet/latest/ssl_attributes_extensions.html\">add a certificate extension</a> to the catalog diff certificate, e.g. <code>pp_authorization: catalog</code> and allow this extension in <code>auth.conf</code>:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">{\n    # Allow nodes to retrieve their own catalog\n    match-request: {\n        path: &quot;^/puppet/v3/catalog/([^/]+)$&quot;\n        type: regex\n        method: [get, post]\n    }\n    allow: [\n        &quot;$1&quot;,\n        {\n            extensions: {\n                pp_authorization: &quot;catalog&quot;\n            }\n        }\n    ]\n    sort-order: 500\n    name: &quot;puppetlabs catalog&quot;\n},</code>\n        </deckgo-highlight-code>\n<h3>Comparing Environments</h3>\n<p>When comparing environments, Puppet Catalog Diff will connect to one or multiple Puppet Masters and get catalogs for each node.</p>\n<p>As you may have many nodes to test, it is easier to get the list of nodes to analyze from the PuppetDB. This can be achieved with the <code>--use_puppetdb</code>, along with <code>--filter_old_env</code>. This will select all the active nodes in the Puppet associated with the first environment.</p>\n<p>For example, if we run:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ puppet catalog diff \\\n     puppet.example.com/production \\\n     puppet.example.com/staging \\\n     --use_puppetdb --filter_old_env</code>\n        </deckgo-highlight-code>\n<p><em>Note (2020-05-07): Since the release of Puppet Catalog Diff 2.0.0, `--use</em>puppetdb<code>is now deprecated and</code>--filter<em>old</em>env` is the default._</p>\n<p>Puppet Catalog Diff will connect to the PuppetDB, get all the active nodes from the <code>production</code> environment, and then for each of them, retrieve a catalog for the node from:</p>\n<ul>\n<li>the <code>production</code> environment on the <code>puppet.example.com</code> Puppet Master</li>\n<li>the <code>staging</code> environment on the <code>puppet.example.com</code> Puppet Master</li>\n</ul>\n<p>It will then compute differences between each pair of catalogs and output them.</p>\n<h2>Testing Version Upgrades</h2>\n<p>One type of check that is very necessary is testing changes between two versions of Puppet Master installations. Puppet Catalog Diff allows you to specify different masters for the two environments to compare, so you can use the following command to compare catalogs from two Puppet Masters on the same Puppet environment (provided the environment is deployed to both masters):</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ puppet catalog diff \\\n    puppet5.example.com/production \\\n    puppet6.example.com/production \\\n    --use_puppetdb --filter_old_env</code>\n        </deckgo-highlight-code>\n<h1>Improving Performance</h1>\n<p>Retrieving and comparing catalogs can be resource-consuming. Very often, you will want to diff a new environment (staging or feature) against a more stable one. Since we can get the nodes associated to the stable environment from PuppetDB, we might as well get the cached catalogs from PuppetDB for this branch, too. This is possible using the <code>--old_catalog_from_puppetdb</code> flag:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ puppet catalog diff \\\n     puppet.example.com/production \\\n     puppet.example.com/staging \\\n     --use_puppetdb --filter_old_env --old_catalog_from_puppetdb</code>\n        </deckgo-highlight-code>\n<p>Catalogs from will retrieved from PuppetDB for the <code>production</code> environment, and from the Puppet Master for the <code>staging</code> environment.</p>\n<h2>Tuning the diff</h2>\n<p>Several options are available to tune the diff output:</p>\n<ul>\n<li><code>--show_resource_diff</code> will show the details of how each resource was modified</li>\n<li><code>--content_diff</code> will generate a separate content diff for file contents, in addition to the parameters diff</li>\n<li><code>--changed_depth 1000</code> sets the number of nodes to display at the end of the diff, sorted by amount of diffs</li>\n</ul>\n<h1>Trusted Facts and Certless Requests</h1>\n<p>Puppet provides a special variable named <code>$trusted</code> and called <a href=\"https://puppet.com/docs/puppet/latest/lang_facts_builtin_variables.html#trusted-facts\">Trusted Facts</a>. This variable contains information from the Puppet certificate. This allows the Puppet Master to get informations, such as the certname or the certificate extensions, and be sure that they could not be falsified.</p>\n<p>However, using these trusted facts in your Puppet code (or Hiera hierarchy) breaks compilation with Puppet Catalog Diff, since the catalog diff's certificate does not contain these trusted variables.</p>\n<p>If you are using Puppet 6.3 or up on your Puppet Master, you can make use of the new <a href=\"https://puppet.com/docs/puppetserver/latest/puppet-api/v4/catalog.html\">certless catalog API</a> to bypass this restriction.</p>\n<h2>Setup</h2>\n<p>Since this uses a different API endpoint, we need to set up <code>auth.conf</code> for it, for example:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">{\n    # Allow nodes to retrieve their own catalog\n    match-request: {\n        path: &quot;^/puppet/v4/catalog&quot;\n        type: regex\n        method: [post]\n    }\n    allow: [\n        {\n            extensions: {\n                pp_authorization: &quot;catalog&quot;\n            }\n        }\n    ]\n    sort-order: 500\n    name: &quot;puppetlabs certless catalog&quot;\n},</code>\n        </deckgo-highlight-code>\n<h2>Usage</h2>\n<p>The <code>--certless</code> flag will tell Puppet Catalog Diff to use the new certless catalog API in place of the standard one.</p>\n<p>For example, you can retrieve the old catalogs from PuppetDB and the new catalogs from the certless catalog API:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ puppet catalog diff \\\n     puppet.example.com/production \\\n     puppet.example.com/staging \\\n     --use_puppetdb --filter_old_env \\\n     --old_catalog_from_puppetdb --certless</code>\n        </deckgo-highlight-code>\n<h1>CI Integration</h1>\n<p>If you are using a Continuous Integration platform, you can get advantage of it by integrating your Puppet control repository into it with Puppet Catalog Diff.</p>\n<p>While general <a href=\"https://dev.to/camptocamp-ops/cleaning-up-puppet-code-4da2\">Code Quality tasks</a> can be launched in a pipeline before deploying the code, Puppet Catalog Diff is typically a task that can be lauched in a merge request.</p>\n<p>For example, you can launch the following command in a GitLab CI job:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ puppet catalog diff \\\n     puppet.example.com/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME} \\\n     puppet.example.com/${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} \\\n     --show_resource_diff --content_diff --changed_depth 1000 \\\n     --use_puppetdb --filter_old_env --old_catalog_from_puppetdb \\\n     --certless --threads 4 \\\n     --output_report /srv/catalog-diff/mr_${CI_MERGE_REQUEST_IID}_${CI_JOB_ID}.json</code>\n        </deckgo-highlight-code>\n<p>The <code>--output_report</code> option saves the output as a JSON document, which can be used later on.</p>\n<h1>Limitations</h1>\n<p>Puppet Catalog Diff compares Puppet catalogs. However, catalog changes do not account for all changes in a Puppet agent run. Plugins can play a role, too.</p>\n<p>If your change involves a change in agent-side plugins (facts, types &#x26; providers, augeas lenses), Puppet Catalog Diff won't allow you to predict the result of these changes.</p>\n<h1>Visualizing changes</h1>\n<p>Changes in Puppet code sometimes generate a lot of diff, which can be hard to parse in text form.</p>\n<p>The <a href=\"https://github.com/camptocamp/puppet-catalog-diff-viewer\">Puppet Catalog Diff Viewer</a> project allows to visualize Puppet Catalog Diff reports (as generated by <code>--output_report</code> option) in a Web UI.</p>\n<p><a href=\"https://github.com/camptocamp/puppet-catalog-diff-viewer\">GitHub — camptocamp/puppet-catalog-diff-viewer</a></p>\n<p>This interface is currently read-only, with no persistence.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/b73sm2n5ie2yip4hh427.png\" alt=\"Puppet Catalog Diff Viewer\"></p>\n<p>Let me know how you use Puppet Catalog Diff, and, as usual, we welcome Pull Request!</p>"},{"url":"/posts/20200502-generic-blog-17ni/","relativePath":"posts/20200502-generic-blog-17ni.md","relativeDir":"posts","base":"20200502-generic-blog-17ni.md","name":"20200502-generic-blog-17ni","frontmatter":{"title":"dev.to as a generic blog?","template":"post","date":"2020-05-02T18:41:25Z","excerpt":"Is it a good idea to use dev.to for other subjects than development?","canonical_url":"https://dev.to/raphink/generic-blog-17ni","devto_url":"https://dev.to/raphink/generic-blog-17ni"},"html":"<p>Do any of you use dev.to as a generic blog, besides development-related subjects (I'm thinking genealogy for example)?</p>\n<p>What would be the pros and cons?</p>"},{"url":"/posts/20200503-recognizing-faces-in-historical-photographs-3ikc/","relativePath":"posts/20200503-recognizing-faces-in-historical-photographs-3ikc.md","relativeDir":"posts","base":"20200503-recognizing-faces-in-historical-photographs-3ikc.md","name":"20200503-recognizing-faces-in-historical-photographs-3ikc","frontmatter":{"title":"Recognizing faces in historical photographs","template":"post","date":"2020-05-03T20:10:58Z","excerpt":"Using machine learning to identify people in historical photographs","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzkzysi903rs0m6w8rvy1.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzkzysi903rs0m6w8rvy1.png","canonical_url":"https://dev.to/raphink/recognizing-faces-in-historical-photographs-3ikc","devto_url":"https://dev.to/raphink/recognizing-faces-in-historical-photographs-3ikc"},"html":"<p>Genealogy has been one of my main personal activities for years. As part of my research, I've collected old pictures of family members that cousins I met were kind enough to send me (usually in scanned form, although at times I was actually given the custody of original photographs).</p>\n<h1>AI to Help with Identification</h1>\n<p>In the last few years, I've added these photographs to Google Photos to take advantage of the face recognition features. It's allowed me to quickly find pictures of people, and it has helped me to identify people in pictures with the help of AI.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/wtowmhpvyc51m79yef5j.png\" alt=\"Google Photos helps me keep track of known people in photographs\">\n<em>Google Photos helps me keep track of known people in photographs</em></p>\n<p>However useful this has been though, I've felt for some time that the scope was too narrow. I've found pictures of my great-grandfather and his associate in books and newspapers, and there's probably more I haven't seen yet.</p>\n<p>Furthermore, there's people in my collection that I haven't identified yet. Somewhere, somehow, I'm sure there's descendants of these people who have portraits of them, and would love to get more pictures of their ancestors. I have occasionally been able to identify them with notes in the back of pictures, but there's still a lot left to put a name on.</p>\n<h1>Thinking Broader</h1>\n<p>So I've been thinking… What if I could have a system similar to Google Photos face grouping and identification, but at a much more global scale?</p>\n<p>The time seems ripe for this:</p>\n<ul>\n<li>we have the algorithms to identify faces</li>\n<li>each new day brings hundreds of new historical pictures online —from family portraits to war pictures</li>\n<li>there's large genealogy databases that associate portraits with identity (<a href=\"http://ancestry.com/\">Ancestry</a>, <a href=\"https://geni.com\">Geni</a>, <a href=\"http://myheritage.com/\">MyHeritage</a>, <a href=\"https://geneanet.org/\">Geneanet</a>, etc.)</li>\n</ul>\n<h1>Starting Point</h1>\n<p>A few months ago, I've started playing with <a href=\"https://aws.amazon.com/rekognition/\">AWS Rekognition</a> to see what I could get out of my own personal collection. Encouraged by the results, I launched a little PoC project, which can be found at:</p>\n<p><a href=\"https://raphink.github.io/find-my-ancestor/\">https://raphink.github.io/find-my-ancestor/</a></p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/79uz5005ebjyex7knfwz.png\" alt=\"President Paul Kruger found by the AI among his family\">\n<em>President Paul Kruger found by the AI among his family</em></p>\n<p>In this project, I picked public Flickr collections featuring historical photographs from all over the world. I scanned a few million pictures and stored face metadata about them in AWS Rekognition. I then built a simple web UI to query this database from a given picture.</p>\n<p>The code (both Ruby scripts and web interface) can be found on GitHub:</p>\n<p><a href=\"https://github.com/raphink/find-my-ancestor\">GitHub — raphink/find-my-ancestor</a></p>\n<p>I've communicated about this project on various Genealogy websites and groups. Unfortunately, the results were not so great. Apart from celebrities and royalties, it's hard to identify random people in a database of \"only\" a million faces, though I am quite sure the algorithm did identify my great-great-uncle in two pictures from the Boer War.</p>\n<h1>The Vision</h1>\n<p>MyHeritage recently worked with AI developer <a href=\"https://twitter.com/citnaj\">Jason Antic</a> to provide an amazing colorization algorithm.</p>\n<p>Most of these genealogical websites provide some kind of hinting system, which send you regular notifications of:</p>\n<ul>\n<li>historical documents matching the names of people in your tree</li>\n<li>other trees with people matching yours</li>\n<li>DNA matches</li>\n</ul>\n<p>My goal would thus be to provide a new kind of hint, in the form of photographs matching known portraits of people in your tree.</p>\n<p>After giving it some thinking, I'm afraid though that the database I have built in AWK Rekognition won't be of much help. It seems I should be using another kind of algorithm to group faces by known person in order to improve matching.</p>\n<p>I'd love to this see project get somewhere. There's so many people who could be identified… soldiers in WWI/WWII pictures, lost family members in concentration camps, and many other unsolved mysteries…</p>\n<p>Do you AI experts have any tips to help me continue this project?</p>"},{"url":"/posts/20200504-automatic-renewal-of-puppet-certificates-28pm/","relativePath":"posts/20200504-automatic-renewal-of-puppet-certificates-28pm.md","relativeDir":"posts","base":"20200504-automatic-renewal-of-puppet-certificates-28pm.md","name":"20200504-automatic-renewal-of-puppet-certificates-28pm","frontmatter":{"title":"Automatic Renewal of Puppet Certificates","template":"post","date":"2020-05-04T06:36:04Z","excerpt":"Everyone who has been using Puppet with a self-signed CA for more than 5 years knows that dreaded time: the time when the CA must be renewed.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","canonical_url":"https://www.camptocamp.com/actualite/automatic-renewal-of-puppet-certificates/","devto_url":"https://dev.to/camptocamp-ops/automatic-renewal-of-puppet-certificates-28pm"},"html":"<p>Everyone who has been using <a href=\"https://puppet.com/\">Puppet</a> with a self-signed CA for over 5 years knows the dreaded time: the time when the CA must be renewed.</p>\n<h1>Renewing the CA</h1>\n<p>The traditional approach is to create a new CA, and then use another mean to renew the certificates for all the nodes (SSH, MCollective, Ansible, etc.).</p>\n<p>Another possibility is to keep the same CA keys and generate a new CA certificate. There is actually <a href=\"https://github.com/puppetlabs/puppetlabs-certregen\">an official module to do that</a>. This module allows you to easily revive a CA that is about to expire in such a way that the new CA certificate is valid for current node certificates. As a result, you don't need to renew the node certificates right away; you only need to distribute the new CA certificate to ensure the cached version does not expire!</p>\n<p>There is however a consequence: node certificates will start to expire. Nodes over 5 years old will expire as soon as the CA expires. And so they need to be renewed, too.</p>\n<h1>Renewing the node certificates</h1>\n<p>What if the Puppet agent itself could ensure its certificate is always\nvalid? This can be achieved using two things:</p>\n<ul>\n<li>an autosign policy;</li>\n<li>the <code>puppet_certificate</code> resource type.</li>\n</ul>\n<h2>Autosign</h2>\n<p>The former is a <a href=\"https://puppet.com/docs/puppet/5.3/ssl_autosign.html#policy-based-autosigning\">standard Puppet feature</a>, with a simple principle: embark a secret in the Puppet CSR, which will be checked by a script on the CA.</p>\n<p>This approach allows to easily automate node provisioning by making nodes automatically register into Puppet.</p>\n<p>The simplest form uses a shared password in the <code>csr_attributes.yaml</code> file on the Puppet node.</p>\n<h2>Managing certificates</h2>\n<p>Once you have put together a way to autosign certificates, let's see how to automatically renew these certificates. We'll use the <a href=\"https://github.com/reidmv/puppet-module-puppet_certificate\">puppet_certificate Puppet module</a> for that. Here is the kind of code you could use:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">class profile::puppet::certificate (\n  String $psk,\n) {\n  file { &#39;/etc/puppetlabs/puppet/csr_attributes.yaml&#39;:\n    ensure  =&gt; file,\n    owner   =&gt; &#39;root&#39;,\n    group   =&gt; &#39;root&#39;,\n    mode    =&gt; &#39;0440&#39;,\n    content =&gt; &quot;---\\ncustom_attributes:\\n  1.2.840.113549.1.9.7: &#39;${psk}&#39;\\n&quot;,\n  }\n  ~&gt; puppet_certificate { $::trusted[&#39;certname&#39;]:\n    ensure               =&gt; valid,\n    onrefresh            =&gt; &#39;regenerate&#39;,\n    waitforcert          =&gt; 60,\n    renewal_grace_period =&gt; 20,\n    clean                =&gt; true,\n  }\n}</code>\n        </deckgo-highlight-code>\n<p>This code will:</p>\n<ul>\n<li>manage the <code>csr_attributes.yaml</code> file to inject the psk into it;</li>\n<li>manage the Puppet certificate of the node.</li>\n</ul>\n<p>In addition:</p>\n<ul>\n<li>If the psk is modified, the certificate will be recreated;</li>\n<li>The certificate will automatically be renewed 20 days before it expires (using <code>ensure => valid</code>);</li>\n</ul>\n<p>Note that this only works if the certificate is cleaned from the Puppet CA before it gets regenerated. This is the point of the <code>clean => true</code> attribute. By default, however, the Puppet CA does not accept remote cleaning of certificates. You can allow nodes to clean their own certificates (and no other) by adding this to your Puppetserver's <code>auth.conf</code> file:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">{\n    name: &quot;Allow nodes to delete their own certificates&quot;,\n    match-request: {\n        path: &quot;^/puppet-ca/v1/certificate(_status|_request)?/([^/]+)$&quot;,\n        type: regex,\n        method: [delete]\n    },\n    allow: &quot;$2&quot;,\n    sort-order: 500\n}</code>\n        </deckgo-highlight-code>\n<h2>Better security</h2>\n<p>While it is possible to use a simple shared password in the <code>csr_attributes.yaml</code> for autosigning, it means all your nodes will contain that psk, which is valid to create new certificates on the Puppet CA. This is not very secure, and this can be improved by using unique tokens for each node, so that a token can only be used to generate a certificate for a specific node.</p>\n<p>You could achieve this with tools such as Vault. Another idea is to generate a composite secret on the Puppet master by mixing the psk with the certname and possibly any certificate extension you want to enforce. You then need to adapt your autosign policy script to generate the same composite secret, which ensures that each node can only generate its own certificate, without changing its extensions.</p>\n<p><em>This post was originally published on <a href=\"https://www.camptocamp.com/actualite/automatic-renewal-of-puppet-certificates/\">camptocamp.com</a></em></p>"},{"url":"/posts/20200507-automated-puppet-impact-analysis-1c1/","relativePath":"posts/20200507-automated-puppet-impact-analysis-1c1.md","relativeDir":"posts","base":"20200507-automated-puppet-impact-analysis-1c1.md","name":"20200507-automated-puppet-impact-analysis-1c1","frontmatter":{"title":"Automated Puppet Impact Analysis","template":"post","date":"2020-05-07T20:49:52Z","excerpt":"Using GitLab Pipelines and Catalog Diff to preview changes between two branches in a merge request","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","canonical_url":"https://dev.to/camptocamp-ops/automated-puppet-impact-analysis-1c1","devto_url":"https://dev.to/camptocamp-ops/automated-puppet-impact-analysis-1c1"},"html":"<p>In <a href=\"https://dev.to/camptocamp-ops/diffing-puppet-environments-1fno\">last week's post</a>, I presented how to set up <a href=\"https://github.com/camptocamp/puppet-catalog-diff\">Puppet Catalog Diff</a> to diff between two Puppet environments.</p>\n<p>Wouldn't it be great if this tool could be used to perform automatic impact analysis before merging a Git branch (aka Merge Request or Pull Request)? Well, it can.</p>\n<h1>The Setup</h1>\n<p>Our current set up is based on <a href=\"https://www.openshift.com/\">RedHat OpenShift</a> and <a href=\"https://gitlab.com/\">GitLab</a>.\nThis is however easily portable to other installation choices.</p>\n<h2>Puppet Infrastructure</h2>\n<p>The Puppet infrastructure is currently running in OpenShift, using our series of <a href=\"https://github.com/camptocamp/charts\">Puppet Helm Charts</a> for Puppetserver, PuppetDB, Puppetboard and Puppet Catalog Diff Viewer.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/dn3718sndgu4zyrtgklv.png\" alt=\"Puppet-related Pods\"></p>\n<p>We are in the process of migrating from Puppet 5 to Puppet 6, so we currently have two Puppetserver charts deployed, one for each version. The <code>puppetserver</code> service points to two Puppet 5 pods, while the <code>puppetserver6</code> service points to two Puppet 6 pods.</p>\n<p>We have passthrough OpenShift routes sitting in front of the services to expose them to the rest of the infra (on port 443 instead of 8140).</p>\n<h2>Lint and Deployment</h2>\n<p>Puppet code deployment is done using a GitLab Runner chart whose deployment mounts the Puppetcode volume (PVC from the Puppetserver deployment). We then run r10k in a GitLab pipeline every time a branch is pushed.</p>\n<p>We also lint the code before deploying it, using the <a href=\"https://github.com/declarativesystems/onceover-codequality\">Onceover Code Quality plugin</a>.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/t6o39hw6zpxdq27uhhmc.png\" alt=\"Deployment pipeline\"></p>\n<p>Here's what it looks like in <code>.gitlab-ci.yml</code>:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">---\nstages:\n  - lint\n  - deploy\n\n.create_r10k_yaml: &amp;create_r10k_yaml |\n  cat &lt;&lt; EOF &gt; /tmp/r10k.yaml\n  ---\n  :cachedir: /etc/puppetlabs/code/cache\n\n  :sources:\n    :main:\n      remote: $CI_PROJECT_DIR\n      basedir: /etc/puppetlabs/code/environments\n  EOF\n\nlinting-puppet-hiera:\n  image: camptocamp/onceover-codequality:latest\n  stage: lint\n  script:\n    - &#39;onceover run codequality  --no_docs&#39;\n  tags:\n    - puppetmaster\n  rules:\n    # Skip linting if the commit message contains &quot;[skip lint]&quot;\n    - if: &#39;$CI_COMMIT_MESSAGE !~ /\\[skip lint\\]/&#39;\n\nr10k-deploy:\n  image: puppet/r10k:3.1.0\n  stage: deploy\n  tags:\n    # Select GitLab runner from the Puppet OpenShift env (which mounts Puppetcode)\n    - puppetmaster\n  before_script:\n    - while [ -f /etc/puppetlabs/code/r10k.lock ]; do echo -n &quot;Waiting for lock from &quot;; cat /etc/puppetlabs/code/r10k.lock || echo; sleep 2; done\n    - hostname -f &gt; /etc/puppetlabs/code/r10k.lock\n  script:\n    - umask 0002\n    # Git https secrets are mounted in the GitLab runner\n    - ln -s /secrets/.netrc ~/\n    - *create_r10k_yaml\n    - git fetch --unshallow\n    - &#39;git branch -r | grep -v &quot;\\-&gt;&quot; | while read remote; do git branch --track &quot;${remote#origin/}&quot; &quot;$remote&quot;; done&#39;\n    - r10k deploy --color -c /tmp/r10k.yaml environment ${CI_COMMIT_REF_NAME} -p --verbose=debug\n    - puppet generate types --environment ${CI_COMMIT_REF_NAME}\n  after_script:\n    - rm -f /etc/puppetlabs/code/r10k.lock</code>\n        </deckgo-highlight-code>\n<h2>Catalog Diff</h2>\n<p>When a Merge Request is open, we want to analyse the impact it will have before we can merge it. This is where Catalog Diff plays a big role.</p>\n<p>Unless you have a huge Puppet infrastructure, Catalog Diff is quite heavy to launch, as it will request lots of catalogs in a small amount of time.</p>\n<p>The new <code>--old_catalog_from_puppetdb</code> option introduced in version 1.7.0 reduces the load by half by getting the \"from\" catalogs from PuppetDB, but it's still kind of a large batch of requests to the Puppet servers.</p>\n<p>For this reason, we run Catalog Diff only on demand, as a manual task. Lint and Deploy are run a second time, to make them mandatory passing steps before a merge can be validated.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/xlzb38uvg70hndubvrze.png\" alt=\"MR Pipeline\"></p>\n<p>Here's the setup:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">.create_puppetdb_conf: &amp;create_puppetdb_conf |\n  cat &lt;&lt; EOF &gt; /etc/puppetlabs/puppet/puppetdb.conf\n  [main]\n  server_urls = https://puppetdb:8081\n  EOF\n\n.create_csr_attributes_yaml: &amp;create_csr_attributes_yaml |\n  cat &lt;&lt; EOF &gt; /etc/puppetlabs/puppet/csr_attributes.yaml\n  ---\n  custom_attributes:\n    # Our autosign script uses hashed secrets based on a psk,\n    # the certname and the environment coded in the certificate\n    1.2.840.113549.1.9.7: &#39;$(echo -n &quot;$psk/$(puppet config print certname)/production&quot; | openssl dgst -binary -sha256 | openssl base64)&#39;\n  extension_requests:\n    # We use the pp_authorization=catalog extension to set up auth.conf for v4/catalog\n    1.3.6.1.4.1.34380.1.3.1: &#39;catalog&#39;\n    1.3.6.1.4.1.34380.1.1.12: &#39;production&#39;\n  EOF\n\n.cleanup_cert: &amp;cleanup_cert |\n  curl -s -X  DELETE \\\n  &quot;Accept:application/json&quot; -H &quot;Content-Type: text/pson&quot; \\\n  --cacert &quot;/etc/puppetlabs/puppet/ssl/certs/ca.pem&quot; \\\n  --cert &quot;/etc/puppetlabs/puppet/ssl/certs/$(puppet config print certname).pem&quot; \\\n  --key &quot;/etc/puppetlabs/puppet/ssl/private_keys/$(puppet config print certname).pem&quot; \\\n  &quot;https://puppetserver:8140/puppet-ca/v1/certificate_status/$(puppet config print certname)?environment=production&quot;\n\n\ncatalog-diff:\n  image: puppet/puppet-agent:6.15.0\n  stage: diff\n  tags:\n    # Select GitLab runner in Puppet OpenShift env to get direct access to services\n    - puppetmaster\n  script:\n    - apt update\n    - apt install -y locales puppetdb-termini\n    - locale-gen en_US.UTF-8\n    - *create_puppetdb_conf\n    - *create_csr_attributes_yaml\n    # Generate a certificate and get it signed\n    - puppet ssl submit_request --ca_server puppetserver --certificate_revocation=false\n    # We currently diff with puppetserver6 for the migration\n    - puppet catalog --environment ${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} --certificate_revocation=false diff puppetserver:8140/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME} puppetserver6:8140/${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} --show_resource_diff --changed_depth 1000 --content_diff --old_catalog_from_puppetdb --certless --threads 4 --output_report /catalog-diff/mr_${CI_MERGE_REQUEST_IID}_${CI_JOB_ID}.json\n  after_script:\n    # We have configured our auth.conf to allow nodes to clean their own cert, see https://dev.to/camptocamp-ops/automatic-renewal-of-puppet-certificates-28pm\n    - *cleanup_cert\n    - echo &quot;You can view the report details at https://puppetdiff.example.com/?report=mr_${CI_MERGE_REQUEST_IID}_${CI_JOB_ID}&quot;\n    # Post a comment on the Merge Request\n    - &#39;curl -k -X POST -H &quot;Private-Token: $CI_BOT_TOKEN&quot; -d &quot;body=You can view the Catalog Diff report details at https://puppetdiff.example.com/?report=mr_${CI_MERGE_REQUEST_IID}_${CI_JOB_ID}&quot; $CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes&#39;\n  # Allow failure so the Merge Request can be validated even without catalog diff\n  allow_failure: true\n  rules:\n    - if: &#39;$CI_MERGE_REQUEST_ID&#39;\n      when: manual\n  variables:\n    LANG: en_US.UTF-8\n    LC_ALL: en_US.UTF-8</code>\n        </deckgo-highlight-code>\n<p>A few notes on that setup:</p>\n<ol>\n<li>PuppetDB is accessed via SSL. Since we have valid certificates to access the Puppet server, we might as well, but 8080 is ok as well if you have that possibility.</li>\n<li>We use an <a href=\"https://puppet.com/docs/puppet/latest/ssl_autosign.html#enabling-policy-based-autosigning\">autosign script</a> to sign certificates using a PSK (which we hash). If it's easier for you, you could also inject a valid key and certificate into the build instead of a PSK.</li>\n<li>If you don't generate a certificate, you don't need the cleanup step either.</li>\n<li>The reports are saved to the <code>/catalog-diff</code> directory, which is mounted in the runner from the Puppet Catalog Diff Viewer PVC. This way, reports are accessible directly in the viewer by passing their name in the query string.</li>\n<li>The Merge Request curl request requires passing a <code>CI_BOT_TOKEN</code> variable to the build. We currently set one in the build variables, using a robot GitLab account. If you have a GitLab Silver or greater plan, you can use the <code>CI_JOB_TOKEN</code> variable instead.</li>\n</ol>\n<h2>What does it look like?</h2>\n<p>Here are some screenshots of a typical workflow.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/b7djk6oti87cf1iatsap.png\" alt=\"Validated Merge Request with comment\"></p>\n<p><em>The Merge Request validated, with the comment left by the bot after the Catalog Diff build was run (see the 3 steps on line 3)</em></p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/a87ep2w72mdbvkrdfsv8.png\" alt=\"Puppet Catalog Diff Viewer\"></p>\n<p><em>Viewing the report generated by the Puppet Catalog Diff run</em></p>\n<h2>Demo</h2>\n<p>Here's a video demo of the setup described above:</p>\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/6LOaHsQDsiI\" title=\"YouTube video\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n<h2>In summary</h2>\n<p>This set up allows us to:</p>\n<ul>\n<li>Validate code quality (lint) before deploying environments</li>\n<li>Check which changes will be brought to Puppet catalogs before accepting a Merge Request</li>\n</ul>\n<p>As stated in the previous blog post, this doesn't account for every change, since changes in plugins (facts, types &#x26; providers, Augeas lenses, etc.) can also impact servers but won't be seen in catalog diffs.</p>"},{"url":"/posts/20200508-deploying-public-keys-in-docker-containers-41cd/","relativePath":"posts/20200508-deploying-public-keys-in-docker-containers-41cd.md","relativeDir":"posts","base":"20200508-deploying-public-keys-in-docker-containers-41cd.md","name":"20200508-deploying-public-keys-in-docker-containers-41cd","frontmatter":{"title":"Deploying public keys in Docker containers","template":"post","date":"2020-05-08T07:57:33Z","excerpt":"One of the hard problems to solve when using Docker in production is deploying secrets. githut_pki makes SSH key deployment easy.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1doni1qp0l9lqk240myf.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1doni1qp0l9lqk240myf.png","canonical_url":"https://www.camptocamp.com/en/actualite/deploying-public-keys-in-docker-containers/","devto_url":"https://dev.to/camptocamp-ops/deploying-public-keys-in-docker-containers-41cd"},"html":"<p>One of the hard problems to solve when using Docker in production is deploying secrets. In particular, public keys are hard to deploy because they are multiline and there is usually one key per authorized user.</p>\n<p>Since all our users have accounts on GitHub with their SSH key, it made sense to us to use GitHub as a centralized PKI for SSH keys. Starting with a simple Ruby script connecting to the GitHub API, we soon realized we would need a generic way of deploying public keys from GitHub if we persisted in this approach.</p>\n<p>This gave birth to the <a href=\"https://github.com/camptocamp/github_pki\" target=\"_blank\">github_pki</a>, a generic command line tool using the GitHub API to deploy SSH and X509 keys from GitHub organizations, teams, and individual users.</p>\n<p>Installing can be done from source:</p>\n<deckgo-highlight-code language=\"dockerfile\"  >\n          <code slot=\"code\">FROM debian:jessie\n\nENV GOPATH=/go\nRUN apt-get update &amp;&amp; apt-get install -y golang-go git \\\n  &amp;&amp; go get github.com/camptocamp/github_pki \\\n  &amp;&amp; apt-get autoremove -y golang-go git \\\n  &amp;&amp; rm -rf /var/lib/apt/lists/*</code>\n        </deckgo-highlight-code>\n<p>Or by inheriting one of the <a href=\"https://hub.docker.com/r/camptocamp/github_pki/tags/\" target=\"_blank\">official Docker images</a>.</p>\n<p>The <tt>github_pki</tt> command can then simply be called from within an entrypoint script to deploy keys:</p>\n<deckgo-highlight-code language=\"bash\"  >\n          <code slot=\"code\">#!/bin/sh\n\n# Deploy users keys as X509 public keys to SSL_DIR\nSSL_DIR=/etc/puppetlabs/mcollective/clients /go/bin/github_pki\n\n# Deploy user keys as an authorized_keys file\nAUTHORIZED_KEYS=/root/.ssh/authorized_keys /go/bin/github_pki</code>\n        </deckgo-highlight-code>\n<p>Various <a href=\"https://github.com/camptocamp/github_pki#environment-variables\" target=\"_blank\">environment variables</a> can be used to tune which keys should be deployed:</p>\n<deckgo-highlight-code language=\"console\"  >\n          <code slot=\"code\">$ docker run -e AUTHORIZED_KEYS=/root/.ssh/authorized_keys \\\n             -e SSL_DIR=/etc/test/ssl \\\n             -e GITHUB_ORG=&quot;myorg&quot; \\\n             -e GITHUB_TEAM=&quot;mypals&quot; \\\n             -e GITHUB_USERS=&quot;otheruser&quot; \\\n             -e GITHUB_TOKEN=398d6d326a546d40f3f1ef93345d1fc5ee0f0j38 \\\n             mydockerimage\nrun-parts: executing /docker-entrypoint.d/25-populate-ssl-clients.sh\ntime=&quot;2016-03-22T09:45:52Z&quot; level=info msg=&quot;Adding users for team mypals&quot; \ntime=&quot;2016-03-22T09:45:52Z&quot; level=info msg=&quot;Adding user bob&quot; \ntime=&quot;2016-03-22T09:45:52Z&quot; level=info msg=&quot;Adding user alice&quot; \ntime=&quot;2016-03-22T09:45:52Z&quot; level=info msg=&quot;Adding individual user otheruser&quot; \ntime=&quot;2016-03-22T09:45:53Z&quot; level=info msg=&quot;Getting keys for user bob&quot; \ntime=&quot;2016-03-22T09:45:53Z&quot; level=info msg=&quot;Getting keys for user alice&quot; \ntime=&quot;2016-03-22T09:45:53Z&quot; level=info msg=&quot;Getting keys for user otheruser&quot;\ntime=&quot;2016-03-22T09:45:59Z&quot; level=info msg=&quot;Generating /root/.ssh/authorized_keys&quot; \ntime=&quot;2016-03-22T09:45:59Z&quot; level=info msg=&quot;Dumping X509 keys to /etc/puppetlabs/mcollective/clients&quot; \ntime=&quot;2016-03-22T09:45:59Z&quot; level=info msg=&quot;Converting key bob/1325852 to X509&quot; \ntime=&quot;2016-03-22T09:45:59Z&quot; level=info msg=&quot;Converting key alice/123756 to X509&quot; \ntime=&quot;2016-03-22T09:45:59Z&quot; level=info msg=&quot;Converting key alice/7845928 to X509&quot; \ntime=&quot;2016-03-22T09:45:59Z&quot; level=info msg=&quot;Converting key otheruser/8540586 to X509&quot;</code>\n        </deckgo-highlight-code>\n<p><em>This blog post was originally published on <a href=\"https://www.camptocamp.com/en/actualite/deploying-public-keys-in-docker-containers/\">camptocamp.com</a></em></p>"},{"url":"/posts/20200508-keep-an-eye-on-your-terraform-states-4lf5/","relativePath":"posts/20200508-keep-an-eye-on-your-terraform-states-4lf5.md","relativeDir":"posts","base":"20200508-keep-an-eye-on-your-terraform-states-4lf5.md","name":"20200508-keep-an-eye-on-your-terraform-states-4lf5","frontmatter":{"title":"Keep an eye on your Terraform states","template":"post","date":"2020-05-08T08:11:27Z","excerpt":"About 4 years ago, we started using Terraform. Many things we were doing manually in the cloud at the time are now coded.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxterraform_bandeau.png.pagespeed.ic.neAGqH-_lX.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxterraform_bandeau.png.pagespeed.ic.neAGqH-_lX.webp","canonical_url":"https://www.camptocamp.com/actualite/keep-an-eye-on-your-terraform-states/","devto_url":"https://dev.to/camptocamp-ops/keep-an-eye-on-your-terraform-states-4lf5"},"html":"<p><em>This blog post was originally published on <a href=\"https://www.camptocamp.com/actualite/keep-an-eye-on-your-terraform-states/\">camptocamp.com</a></em></p>\n<p>About 4 years ago, we started using Terraform. Many things we were doing manually in the cloud at the time are now coded. As a result, our <a href=\"http://www.terraform.io\">Terraform</a> base code now contains over a hundred states.</p>\n<h1>Terraform everything!</h1>\n<p>A lot of those resources already existed before, some managed by <a href=\"https://aws.amazon.com/cloudformation/\">CloudFormation</a>, others manually. Being able to import resources has helped a lot to integrate new Terraform code with existing infrastructure. We now have a unified system to control them, and most importantly to know who created them, how and why. Collaboration was made easier by using profiles instead of hardcoded credentials, the introduction of remote states stored on AWS S3, as well as state locks on DynamoDB.</p>\n<p>With all this, one thing remained: how do we keep an eye on all these states, resources and locks that are stored on AWS? Could there be a way to visualize and query them?</p>\n<h1>Introducing Terraboard</h1>\n<p><img src=\"https://raw.githubusercontent.com/camptocamp/terraboard/master/logo/terraboard_logo.png\" alt=\"Terraboard\"></p>\n<p><a href=\"https://github.com/camptocamp/terraboard\">Terraboard</a> was born in an attempt to bring an easy-to-use Web Interface for Terraform states.</p>\n<p>It currently supports states stored in AWS S3, as well as locks on DynamoDB. It features 4 views: overview, state view, compare view and search.</p>\n<p>Terraboard requires an S3 bucket with versioning activated (for history and comparison between versions), as well as a PostgreSQL database, where all S3 states will be stored as a data cache.</p>\n<p>Terraboard is comprised of two compontents:</p>\n<ul>\n<li>a server written in Go, which synchronizes the state files from the S3 bucket into the PostgreSQL database, and provides an API for the UI;</li>\n<li>a Web UI written in AngularJS which consumes the API data and serves the Web pages.</li>\n</ul>\n<h2>Overview</h2>\n<p>The overview is the landing page in Terraboard. It provides information about the most recent version of each state, along with the Terraform version used to apply it, its serial, the number of resources it features, and an activity sparkline. Clicking the sparkline lets you easily access any version of a state.</p>\n<p>Graphs present statistics on the main resource types and Terraform versions used, as well as the number of number of states locked (if DynamoDB is configured).</p>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/main-550x326.png\" alt=\"Main View\"></p>\n<h2>State view</h2>\n<p>The State view presents details about a state file's resources.  Resources are listed by module and can be filtered. A version selector lets you view historical data for the state.</p>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/state-550x328.png\" alt=\"State View\"></p>\n<h2>Compare view</h2>\n<p>While on the State view, you can pick a second version to compare with the current one. This computes differences between the two versions, which displays:</p>\n<ul>\n<li>A list of differences, displayed as a unified diff;</li>\n<li>A list of resources only in the first version;</li>\n<li>A list of resources only in the new version.</li>\n</ul>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/compare-550x330.png\" alt=\"Compare View\"></p>\n<h2>Search view</h2>\n<p>If you've ever wondered in which Terraform state a node was managed, you can easily find this out in the Search view. The Search view lets you filter resources and their attributes by type, name, key or value, as well as Terraform version used.</p>\n<p><img src=\"https://www.camptocamp.com/wp-content/uploads/search-1-550x330.png\" alt=\"Search View\"></p>\n<h1>I want to try it!</h1>\n<p>Are you ready to try Terraboard? If you're using Docker, this is very easy. All you need is a PostgreSQL database and your AWS credentials:</p>\n<deckgo-highlight-code language=\"console\"  >\n          <code slot=\"code\">docker run -d -p 8080:8080 \\\n   -e AWS_REGION=&lt;AWS_DEFAULT_REGION&gt; \\\n   -e AWS_ACCESS_KEY_ID=&lt;AWS_ACCESS_KEY_ID&gt; \\\n   -e AWS_SECRET_ACCESS_KEY=&lt;AWS_SECRET_ACCESS_KEY&gt; \\\n   -e AWS_BUCKET=&lt;terraform-bucket&gt; \\\n   -e AWS_DYNAMODB_TABLE=&lt;terraform-locks-table&gt; \\\n   -e DB_PASSWORD=&quot;mygreatpasswd&quot; \\\n   --link postgres:db \\\n   camptocamp/terraboard:latest</code>\n        </deckgo-highlight-code>\n<p>A Rancher template is also available in <a href=\"https://github.com/camptocamp/camptocamp-rancher-catalog\">Camptocamp's Rancher Catalog</a>, as well as a <a href=\"https://github.com/camptocamp/charts/tree/master/terraboard\">Helm Chart</a>.</p>\n<h3>I want to help!</h3>\n<p>Terraboard is an open-source project and we heartily welcome all contributions to it. Don't hesitate to submit <a href=\"https://github.com/camptocamp/terraboard\">Pull Requests on GitHub</a>.</p>\n<p>You are also welcome to <a href=\"https://gitter.im/camptocamp/terraboard\">join us on Gitter</a> to discuss new ideas.</p>\n<p>Happy Terraforming!</p>"},{"url":"/posts/20200508-unshallowing-a-git-repository-24nd/","relativePath":"posts/20200508-unshallowing-a-git-repository-24nd.md","relativeDir":"posts","base":"20200508-unshallowing-a-git-repository-24nd.md","name":"20200508-unshallowing-a-git-repository-24nd","frontmatter":{"title":"Unshallowing a Git repository","template":"post","date":"2020-05-08T07:36:38Z","excerpt":"GitLab allows to perform shallow repository clones (and it seems to be the default in recent versions...","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnsbbm80zgqqypxyqtx1d.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnsbbm80zgqqypxyqtx1d.png","canonical_url":"https://dev.to/camptocamp-ops/unshallowing-a-git-repository-24nd","devto_url":"https://dev.to/camptocamp-ops/unshallowing-a-git-repository-24nd"},"html":"<p>GitLab allows to <a href=\"https://docs.gitlab.com/ee/ci/yaml/#shallow-cloning\">perform shallow repository clones</a> (and it seems to be the default in recent versions from what I can tell).</p>\n<p>In order to run r10k, I need a full repository though, because r10k will copy it to cache and use this copy as a reference. This is what happens when you use a shallow repository:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\"> [2020-05-08 06:53:15 - DEBUG] Replacing /etc/puppetlabs/code/environments/modulesync_update and checking out modulesync_update\n [2020-05-08 06:53:56 - ERROR] Command exited with non-zero exit code:\n Command: git clone /builds/camptocamp/is/puppet/puppetmaster-c2c /etc/puppetlabs/code/environments/modulesync_update --reference /etc/puppetlabs/code/cache/-builds-camptocamp-is-puppet-puppetmaster-c2c\n Stderr:\n Cloning into &#39;/etc/puppetlabs/code/environments/modulesync_update&#39;...\n fatal: reference repository &#39;/etc/puppetlabs/code/cache/-builds-camptocamp-is-puppet-puppetmaster-c2c&#39; is shallow\n Exit code: 128\n [2020-05-08 06:53:56 - DEBUG] Purging unmanaged environments for deployment...</code>\n        </deckgo-highlight-code>\n<p>Git provides a <code>fetch --unshallow</code> command which solves the problem, so we just need to run <code>git fetch --unshallow</code> in the repository before running r10k.</p>\n<p>However, some of our (older) GitLab installs don't make shallow clones. Instead, they make full clones with a single detached branch, so we need <code>fetch --all</code> instead.</p>\n<p>In order to have it work in all configurations, I'm ending up running:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">git fetch --unshallow || git fetch --all</code>\n        </deckgo-highlight-code>\n<p>And then run r10k on the repository.</p>"},{"url":"/posts/20200509-git-markdown-to-write-a-novel-ag6/","relativePath":"posts/20200509-git-markdown-to-write-a-novel-ag6.md","relativeDir":"posts","base":"20200509-git-markdown-to-write-a-novel-ag6.md","name":"20200509-git-markdown-to-write-a-novel-ag6","frontmatter":{"title":"Git & Markdown to write a novel","template":"post","date":"2020-05-09T06:20:31Z","excerpt":"Using Git and Markdown to write a novel","canonical_url":"https://dev.to/raphink/git-markdown-to-write-a-novel-ag6","devto_url":"https://dev.to/raphink/git-markdown-to-write-a-novel-ag6"},"html":"<p>This year, I started writing a historical novel about a branch of my family. I quickly realized I needed some tooling to organize my data: what I know about the characters, the places, a general timeline, etc.</p>\n<h1>Manuskript</h1>\n<p>I looked for software to do that and found that the reference is <a href=\"https://www.literatureandlatte.com/scrivener/overview\">Scrivener</a>. However interesting it looked, I'd rather use Open Source software whenever possible, so I started using <a href=\"https://www.theologeek.ch/manuskript/\">Manuskript</a>, an Open Source software for writers similar to Scrivener.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/fj7xlois1xl9d7qtzyjl.png\" alt=\"Manuskript on Ubuntu\"></p>\n<p>I laid out some chapters, characters, plots… then I realized the saving format was binary. It's actually a Zip archive which contains all the information in flat files:</p>\n<deckgo-highlight-code language=\"console\"  >\n          <code slot=\"code\">        1  2020-05-09 08:05   MANUSKRIPT\n      137  2020-05-09 08:05   infos.txt\n        0  2020-05-09 08:05   summary.txt\n       46  2020-05-09 08:05   status.txt\n      147  2020-05-09 08:05   labels.txt\n      284  2020-05-09 08:05   characters/0-Samuel_L-on.txt\n      281  2020-05-09 08:05   characters/1-L-on_Grunberg.txt\n      261  2020-05-09 08:05   characters/2-Adolphe_Grunberg.txt\n      309  2020-05-09 08:05   characters/3-Elisabeth_Rau.txt\n      112  2020-05-09 08:05   characters/4-Victor_Grunberg.txt\n      109  2020-05-09 08:05   characters/5-Maria_Schorr.txt\n      224  2020-05-09 08:05   characters/6-Fred_Grunberg.txt\n      125  2020-05-09 08:05   characters/7-Colonel_de_Villebois-Mareuil.txt\n      107  2020-05-09 08:05   characters/8-Said_Pacha.txt\n      109  2020-05-09 08:05   characters/9-Isaac_Aghion.txt\n       92  2020-05-09 08:05   outline/00-Setup.md\n      322  2020-05-09 08:05   outline/01-Les_joyaux_-gyptiens/folder.txt\n     3753  2020-05-09 08:05   outline/01-Les_joyaux_-gyptiens/0-Une_rencontre_inattendue.md\n     1706  2020-05-09 08:05   outline/01-Les_joyaux_-gyptiens/1-Office_de_chabbat.md\n      715  2020-05-09 08:05   outline/01-Les_joyaux_-gyptiens/2-Un_d-ner_chez_Isaac.md\n      482  2020-05-09 08:05   outline/01-Les_joyaux_-gyptiens/3-Proc-s_1.md\n      166  2020-05-09 08:05   outline/01-Les_joyaux_-gyptiens/4-Sc-ne_5.md\n       83  2020-05-09 08:05   outline/02-Paris/folder.txt\n      102  2020-05-09 08:05   outline/02-Paris/0-Sc-ne_1.md\n       97  2020-05-09 08:05   outline/03-Etudes_des_enfants/folder.txt\n      103  2020-05-09 08:05   outline/03-Etudes_des_enfants/0-Sc-ne_1.md\n      103  2020-05-09 08:05   outline/03-Etudes_des_enfants/1-Sc-ne_2.md\n       95  2020-05-09 08:05   outline/04-Premiers_emplois/folder.txt\n      103  2020-05-09 08:05   outline/04-Premiers_emplois/0-Sc-ne_1.md\n       96  2020-05-09 08:05   outline/05-Vers_le_Transvaal/folder.txt\n      103  2020-05-09 08:05   outline/05-Vers_le_Transvaal/0-Sc-ne_1.md\n      334  2020-05-09 08:05   outline/21-Epilogue.md\n    40074  2020-05-09 08:05   revisions.xml\n      275  2020-05-09 08:05   world.opml\n     1669  2020-05-09 08:05   plots.xml\n     2499  2020-05-09 08:05   settings.txt</code>\n        </deckgo-highlight-code>\n<p>This is rather good news, and there's ways to automate PDF creation from this within the software, good news again!</p>\n<p>However, some things were problematic to me:</p>\n<ul>\n<li>I like to tune my pandoc/LaTeX rendering, and the options in Manuskript were pretty limited for that</li>\n<li>I'd rather not commit a single Zip file in Git, so I would have preferred to have all the files in the current directory</li>\n<li>Manuskript cannot be automated to compile the PDF, you need to go through the GUI and click on buttons. That's a blocker for me.</li>\n</ul>\n<h1>Going the full-git way</h1>\n<p>So I decided I would do everything in Git + Markdown, without the help of a third-party application.</p>\n<h2>Chapters</h2>\n<p>I'm currently storing the chapters in their own directories, with numbered Markdown files:</p>\n<deckgo-highlight-code language=\"console\"  >\n          <code slot=\"code\">chapitres/\n├── 01_les_joyaux_egyptiens\n│   ├── 00_titre.md\n│   ├── 01_une_rencontre_innatendue.md\n│   ├── 02_office_de_chabbat.md\n│   ├── 03_brody.md\n│   ├── 04_chabbat_2.md\n│   ├── 05_diner_chez_isaac.md\n│   └── 06_audience.md\n├── 02_les_enfants_grunberg\n│   ├── 00_titre.md\n│   ├── 01_leon.md\n│   ├── 02_retour.md\n│   ├── 03_coffre.md\n│   ├── 04_burtaux.md\n│   ├── 05_article.md\n│   └── 06_article2.md\n├── 03_annees_noires\n│   └── 00_titre.md\n├── 04_etudes\n│   └── 00_titre.md\n├── 05_premier_travail\n│   └── 00_titre.md\n├── 06_le_creusot\n│   └── 00_titre.md\n└── 21_epilogue.md</code>\n        </deckgo-highlight-code>\n<h2>Characters &#x26; Places</h2>\n<p>The characters documentation is stored in Markdown files as well, and links can be made between them when necessary:</p>\n<deckgo-highlight-code language=\"console\"  >\n          <code slot=\"code\">personnages/\n├── Adolphe_Grunberg.md\n├── Charlotte_Grunberg.md\n├── Elisabeth_Rau.md\n├── Emilie_Grunberg.md\n├── Felix_Zottier.md\n├── Frederic_Grunberg.md\n├── Isaac_Aghion.md\n├── Jacques_Grunberg.md\n├── Leon_Grunberg.md\n├── Lucie_Leon.md\n├── Marc_Leon.md\n├── Mirel_Schorr.md\n├── Paul_Grunberg.md\n└── Samuel_Leon.md</code>\n        </deckgo-highlight-code>\n<p>Same goes for places:</p>\n<deckgo-highlight-code language=\"console\"  >\n          <code slot=\"code\">lieux/\n├── Alexandrie.md\n├── Brody.md\n├── Dubno.md\n├── Grunberg_Boulogne.md\n├── Paris.md\n├── Petite_Jonchere.md\n└── Vienne.md</code>\n        </deckgo-highlight-code>\n<p>Links can easily be made between these documentation files:</p>\n<deckgo-highlight-code language=\"markdown\"  >\n          <code slot=\"code\"># Mirel Schorr\n\n\n## État civil\n\n* Prénoms : Mirel, dite Maria\n* Nom : Grünberg\n* Nom de naissance : Schorr\n\n\n## Portrait\n\nInconnu\n\n\n## Description physique\n\nInconnu\n\n\n\n## Evénements\n\n\n* ca 1800 : ° à [Dubno](../lieux/Dubno.md) (Russie)\n* Enfance à [Brody](../lieux/Brody.md) (Autriche)\n  avec ses parents [Schachne](Schachne_Schorr.md)\n  et [Sarah](Sarah_Bick.md)\n  et ses frères [Naphtali](Naphtali_Mendel_Schorr.md)\n  et [Osias](Osias_Heschel_Schorr.md)\n\n* 1821: Mariage à [Brody](../lieux/Brody.md)\n\n* 1870 : habite à [Vienne](../lieux/Vienne.md) (testament [Adolphe](Adolphe_Grunberg.md))\n\n* 1877 : habite à [Paris](../lieux/Paris.md) (+)\n* 1877 : + à [Vienne](../lieux/Vienne.md)\n\n\n## Habillement\n\nVoir sa garde-robe en 1853 dans l&#39;inventaire de [Victor](Victor_Grunberg.md)</code>\n        </deckgo-highlight-code>\n<h2>Section stats</h2>\n<p>Manuskript provides stats about the number of words in a section. I've added a Make target for that:</p>\n<deckgo-highlight-code language=\"Makefile\"  >\n          <code slot=\"code\">stats:\n\tfind chapitres/ -name &quot;*.md&quot; -not -name &#39;00_titre.md&#39; -print0 | sort -z | xargs -0 wc -w</code>\n        </deckgo-highlight-code>\n<p>Caveat: it also lists the words in the comments…</p>\n<h1>Building the project</h1>\n<p>I'm using Pandoc to build the project with my own LaTeX template.</p>\n<deckgo-highlight-code language=\"Makefile\"  >\n          <code slot=\"code\">%.md:\n\tcat meta.md &gt; $@\n\tfind chapitres/ -name &quot;*.md&quot;  -print0 | sort -z | xargs -0 cat &gt;&gt; $@\n\n%.tex: %.md\n\tpandoc --pdf-engine lualatex  --template extended.tex \\\n\t\t   --variable numbersections --toc --variable toc-depth=2 \\\n\t\t   --variable documentclass=memoir --variable fontsize=12pt \\\n\t\t   --filter pandoc-citeproc \\\n\t\t   --verbose \\\n\t\t   $&lt; -o $@\n\n%.pdf: %.tex\n\tOSFONTDIR=$(FONTSDIR) lualatex $&lt;\n\tmakeindex $*.idx\n\tOSFONTDIR=$(FONTSDIR) lualatex $&lt;</code>\n        </deckgo-highlight-code>\n<p>The novel project can be found in this GitHub repository:</p>\n<p><a href=\"https://github.com/raphink/genearoman\">GitHub — raphink/genearoman</a></p>"},{"url":"/posts/20200511-representing-technical-skills-on-a-timeline-1mk1/","relativePath":"posts/20200511-representing-technical-skills-on-a-timeline-1mk1.md","relativeDir":"posts","base":"20200511-representing-technical-skills-on-a-timeline-1mk1.md","name":"20200511-representing-technical-skills-on-a-timeline-1mk1","frontmatter":{"title":"Representing technical skills on a timeline","template":"post","date":"2020-05-11T15:19:14Z","excerpt":"Several ways to display technical skills on a timeline","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0kwh14busu2ckvvmofwp.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0kwh14busu2ckvvmofwp.png","canonical_url":"https://dev.to/raphink/representing-technical-skills-on-a-timeline-1mk1","devto_url":"https://dev.to/raphink/representing-technical-skills-on-a-timeline-1mk1"},"html":"<p>CVs and other websites presenting technical skills often lack a time dimension, allowing to know when and for how long a technology has been used.</p>\n<h1>Timeline on CV</h1>\n<p>About 8 years ago, I wanted to add a visual representation of my experience on my PDF CV.</p>\n<p>Since I already used LaTeX with the excellent <a href=\"https://ctan.org/pkg/moderncv\">moderncv class</a>, I wanted the solution to extend on that class. <a href=\"https://tex.stackexchange.com/questions/29725/putting-a-timeline-for-dates-in-moderncv\">TeX StackExchange did not disappoint</a> (they never do) and this gave birth to the <a href=\"https://ctan.org/pkg/moderntimeline\"><code>moderntimeline</code> LaTeX package</a> which I have been maintaining since.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/4ae3bwwiymnffrljjypc.png\" alt=\"Moderntimeline example\"></p>\n<p>To this day I still use this solution <a href=\"https://github.com/raphink/CV\">on my CV</a>.</p>\n<p>Since then, a template has even been added to <a href=\"https://www.overleaf.com/latex/examples/moderncv-with-modern-timeline/prmmmvtvfxsn\">Overleaf</a> to make it easier!</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/hvcmi91fhd8eaqex8u8c.png\" alt=\"Template on Overleaf\"></p>\n<h1>Technology timeline</h1>\n<p>The CV timeline is still not enough to present the data I wish to display, which is the temporal evolution of technical skills.</p>\n<h2>OpenHub</h2>\n<p>Among the many websites which analyze public code repositories to get metrics out of them, <a href=\"https://www.openhub.net/\">OpenHub</a> (previously Ohloh) is very interesting because it presents a timeline of languages used in projects.</p>\n<p>Here's an example with <a href=\"https://www.openhub.net/accounts/raphink\">my profile</a>, where you can identify clear periods: a lot of LaTeX (dark blue) in the first years (when I edited books), then Augeas (light grey), mostly Ruby (red) between 2012 and 2015, then mainly Go (purple).</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/0kwh14busu2ckvvmofwp.png\" alt=\"OpenHub Languages View\"></p>\n<h2>A broader approach</h2>\n<p>Not every tech skill can be measured with a number of code lines though.\nSo in 2013, I switched <a href=\"https://raphink.info/\">my main CV page</a> to a temporal skills view.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/8ld9549ukxidnb7i7jjv.png\" alt=\"Skills View\"></p>\n<p>This uses <a href=\"https://visjs.org/\">vis.js</a> to build a table of skills from <a href=\"https://github.com/raphink/CV/blob/gh-pages/items.json\">a JSON file</a>, e.g.:</p>\n<deckgo-highlight-code language=\"json\"  >\n          <code slot=\"code\">[\n  {&quot;id&quot;: &quot;Orange&quot;, &quot;content&quot;: &quot;&lt;img src=&#39;img/orange.png&#39; class=&#39;logo&#39; /&gt;&lt;b&gt;Orange Portails&lt;/b&gt;&lt;br /&gt;Systems Engineer&quot;, &quot;start&quot;: &quot;2006-06-01&quot;, &quot;end&quot;: &quot;2012-03-01&quot;, &quot;type&quot;: &quot;background&quot;, &quot;className&quot;: &quot;orange&quot;},\n  {&quot;id&quot;: &quot;Camptocamp&quot;, &quot;content&quot;: &quot;&lt;img src=&#39;img/camptocamp.png&#39; class=&#39;logo&#39; /&gt;&lt;b&gt;Camptocamp&lt;/b&gt;&lt;br /&gt;Infrastructure Developer&quot;, &quot;start&quot;: &quot;2012-03-01&quot;, &quot;type&quot;: &quot;background&quot;, &quot;className&quot;: &quot;camptocamp&quot;},\n\n  {&quot;group&quot;: &quot;provisioning&quot;, &quot;content&quot;: &quot;Debian FAI&quot;, &quot;start&quot;: &quot;2006-06-01&quot;, &quot;end&quot;: &quot;2012-03-01&quot;, &quot;className&quot;: &quot;contributed&quot;},\n  {&quot;group&quot;: &quot;provisioning&quot;, &quot;content&quot;: &quot;Kickstart&quot;, &quot;start&quot;: &quot;2006-06-01&quot;, &quot;className&quot;: &quot;implemented&quot;},\n  {&quot;group&quot;: &quot;provisioning&quot;, &quot;content&quot;: &quot;Terraform&quot;, &quot;name&quot;: &quot;terraform&quot;, &quot;start&quot;: &quot;2016-05-01&quot;, &quot;className&quot;: &quot;contributed&quot;}\n]</code>\n        </deckgo-highlight-code>\n<p>This JSON file is parsed and displayed on the page. Each skill can be assigned an icon as well as additional information. The skill bar can be clicked to display this information, taken from the <code>skills/</code> directory and <a href=\"https://github.com/raphink/CV/blob/gh-pages/skills/go/details.md\">documented in Markdown</a>.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/xe2j9wktc28leawnu9ee.png\" alt=\"Details View\"></p>\n<p>The code is open-source and can be forked on GitHub. Just check the <code>gh-pages</code> branch:</p>\n<p><a href=\"https://github.com/raphink/CV\">GitHub — raphink/CV</a></p>\n<p>As usual, pull requests are welcome if you find nice ways to improve this!</p>"},{"url":"/posts/20200513-error-console-highlighting-1c60/","relativePath":"posts/20200513-error-console-highlighting-1c60.md","relativeDir":"posts","base":"20200513-error-console-highlighting-1c60.md","name":"20200513-error-console-highlighting-1c60","frontmatter":{"title":"Error console highlighting","template":"post","date":"2020-05-13T20:47:24Z","excerpt":"When composing posts on dev.to, is there a way to dispensary display error messages so they appear as...","canonical_url":"https://dev.to/raphink/error-console-highlighting-1c60","devto_url":"https://dev.to/raphink/error-console-highlighting-1c60"},"html":"<p>When composing posts on dev.to, is there a way to dispensary display error messages so they appear as console output, but in red?</p>\n<p>I've tried things like:</p>\n<pre class=\"highlight markdown\">\n<code>\n```error\nMy error message\n```\n</code>\n</pre>\n<p>or</p>\n<deckgo-highlight-code language=\"html\"  >\n          <code slot=\"code\">&lt;pre&gt;\n&lt;code style=&quot;color:red;font-weight:bold&quot;&gt;\nMy error message\n&lt;/code&gt;\n&lt;/pre&gt;</code>\n        </deckgo-highlight-code>\n<p>to no avail…</p>\n<p>Is there a way to achieve this?</p>"},{"url":"/posts/20200515-taming-puppetserver-6-pt-ii-garbage-collection-2oh2/","relativePath":"posts/20200515-taming-puppetserver-6-pt-ii-garbage-collection-2oh2.md","relativeDir":"posts","base":"20200515-taming-puppetserver-6-pt-ii-garbage-collection-2oh2.md","name":"20200515-taming-puppetserver-6-pt-ii-garbage-collection-2oh2","frontmatter":{"title":"Taming Puppetserver 6 Pt II: Garbage Collection","template":"post","date":"2020-05-15T10:49:37Z","excerpt":"PuppetServer can be spending a lot of time doing gargage collection, which impacts its performance","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1wtluh7qosm01n29xmgq.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1wtluh7qosm01n29xmgq.png","canonical_url":"https://dev.to/camptocamp-ops/taming-puppetserver-6-pt-ii-garbage-collection-2oh2","devto_url":"https://dev.to/camptocamp-ops/taming-puppetserver-6-pt-ii-garbage-collection-2oh2"},"html":"<p>Now that our internal Puppet Infrastructure is <a href=\"https://dev.to/camptocamp-ops/taming-puppetserver-6-a-grafana-story-3c4f\">migrated to Puppet 6 and tuned</a>, it was time to switch a second infra to it.</p>\n<p>Yesterday, I migrated our second infrastructure, and started seeing more issues. The rules-of-thumb from last post were useful, but I still needed to upgrade available memory to make up for a lack of computing power (probably to be imputed to the underlying IaaS throttling virtual CPUs).</p>\n<p>And then, a Puppetserver crashed with a <code>GC overhead limit exceeded</code> error. This error happens when the CPU spends more than 98% performing garbage collection.</p>\n<h1>Analyzing Garbage Collection Data</h1>\n<p>Looking at our Grafana dashboard, I realized we had no metrics about garbage collection, so I added a graph with two metrics:</p>\n<ul>\n<li>mean time per GC: the average time taken by each garbage collection request to complete, calculated as a rate over 1 minute (since <code>jvm_gc_collection_seconds_sum</code> is a cumulative counter)</li>\n<li>GC time: the percentage of time spent by the CPUs doing GC, over 1 minute (since <code>jvm_gc_collection_seconds_count</code> is also a cumulative counter)</li>\n</ul>\n<p>The formulas are as follow:</p>\n<ul>\n<li><code>time per request {{gc}}</code>: <code>rate(jvm_gc_collection_seconds_sum{job=\"puppetserver\"}[1m])/rate(jvm_gc_collection_seconds_count{job=\"puppetserver\"}[1m])</code></li>\n<li><code>rate {{gc}}</code>: <code>rate(jvm_gc_collection_seconds_sum{job=\"puppetserver\"}[1m])</code></li>\n</ul>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/euwyfd4972ggylucm4n3.png\" alt=\"Graph queries\"></p>\n<h1>GC time</h1>\n<p>I then looked at the graphs around the time when the <code>GC overhead limit exceeded</code> error happened:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/w6k51xxcgoucexyieap3.png\" alt=\"GC overhead limit exceeded\"></p>\n<p>Yes, I had a problem indeed. I restarted the Puppetservers and this hasn't happened since. However, the rates for PS MarkSweep have kept pretty high still. Here's the last 15 minutes as I'm writing:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/y93wzup9e7wglf4ectng.png\" alt=\"Standard activity for Puppet Infra 2\"></p>\n<p>In comparison, the infrastructure I upgraded last week is faring much better, with GC rates well under 10%:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/466p0q75k2irwheuze22.png\" alt=\"Standard activity for Puppet Infra 1\"></p>\n<h1>Mean time per GC</h1>\n<p>In addition to a high rate spent performing garbage collection on the PS MarkSweep GC threads, I also noticed that the mean GC time for PS MarkSweep is pretty high, too, at around 2 to 3s. The values are slightly lower (a bit under 2s) on my first infrastructure.</p>\n<h1>Getting rid of PS MarkSweep</h1>\n<p>All in all, it seems PS MarkSweep garbage collection is to blame. It tends to take lots of CPU, for long periods of time.</p>\n<p>The good news is: <a href=\"https://stackoverflow.com/questions/39929758/ps-marksweep-is-which-garbage-collector/44923227#44923227\">PS MarkSweep is a legacy garbage collector</a>, and it's not too hard to get rid of it, since <a href=\"https://blog.idrsolutions.com/2019/05/java-8-vs-java-11-what-are-the-key-changes/\">OpenJDK 11 replaces it with the G1 Young Generation garbage collector by default</a>.</p>\n<p>The <a href=\"https://hub.docker.com/r/puppet/puppetserver\">official puppetserver Docker image</a> installs the <code>puppetserver</code> package, which pulls <code>openjdk-8-jre-headless</code> as a dependency. <a href=\"https://puppet.com/docs/puppetserver/latest/install_from_packages.html#java-support\">OpenJDK 11 is also officially supported</a> starting with Puppet 6.6, but the package doesn't allow to install it instead of OpenJDK 8. So for now, I'll just derive an image and install <code>openjdk-11-jre-headless</code> in addition to OpenJDK8 and let Ubuntu update the alternative for Java automatically.</p>\n<p>The following graph shows the difference in GC time between PS MarkSweep and G1, following the upgrade to OpenJDK 11:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/1wtluh7qosm01n29xmgq.png\" alt=\"PS MarkSweep vs G1\"></p>\n<p>And here's what GC looks like after a good warm up:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/hvr49m18eeidpxt31u7k.png\" alt=\"New GC graph\"></p>\n<p>From 2s to 80ms, that's a great improvement if you ask me!</p>"},{"url":"/posts/20200513-taming-puppetserver-6-a-grafana-story-3c4f/","relativePath":"posts/20200513-taming-puppetserver-6-a-grafana-story-3c4f.md","relativeDir":"posts","base":"20200513-taming-puppetserver-6-a-grafana-story-3c4f.md","name":"20200513-taming-puppetserver-6-a-grafana-story-3c4f","frontmatter":{"title":"Taming Puppetserver 6: a Grafana story","template":"post","date":"2020-05-13T08:32:41Z","excerpt":"Using Grafana & Catalog Diff to tune the Puppet Server","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft2lnmj23y7z3cvgo0b1t.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ft2lnmj23y7z3cvgo0b1t.png","canonical_url":"https://dev.to/camptocamp-ops/taming-puppetserver-6-a-grafana-story-3c4f","devto_url":"https://dev.to/camptocamp-ops/taming-puppetserver-6-a-grafana-story-3c4f"},"html":"<p>After some time preparing for the migration, yesterday was finally the time to switch our first production Puppetserver to Puppet 6.</p>\n<p>Everything was ready: we had been running both versions of the server alongside each other for some time, <a href=\"https://dev.to/camptocamp-ops/automated-puppet-impact-analysis-1c1\">performing catalog diffs</a>, and nothing seemed to be getting in the way as I went into ArgoCD and deployed the new version of the stack.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/4yegip5ggdh0k7npdz8b.png\" alt=\"Deploying the Puppetserver in ArgoCD\"></p>\n<p>The first 30 minutes went fine. But then catalogs started failing compilation, and other services colocated on the OpenShift cluster became slow.</p>\n<h1>The Problem</h1>\n<p>In retrospect, I should have known something was wrong. Two weeks ago when I started my tests with Puppet 6 on another platform, I noticed that the server would die of OOM quite rapidly. Our Puppetserver 5 pods had been running happily for years with the following settings:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">max_active_instances: &#39;4&#39;\njava_xmx: &#39;2g&#39;\n\nrequests:\n  cpu: 10m\n  memory: 2Gi\nlimits:\n  cpu: 3\n  memory: 4Gi</code>\n        </deckgo-highlight-code>\n<p>When I started new Puppet 6 instances with these settings, they would die. Initially, I thought the Xmx wasn't high enough so I set it to <code>3g</code> and everything seemed fine again, for the duration of the catalog-diff tests.</p>\n<p>But when the servers started crashing in production yesterday, it was clear there was another problem. And upgrading the Xmx to a higher value didn't help.</p>\n<p>So we looked at the graphs.</p>\n<h1>The Graphs</h1>\n<p>For years, we have been gathering internal metrics from our Puppetservers. Our <a href=\"https://github.com/camptocamp/charts/tree/master/puppetserver\">Puppetserver Helm chart</a> comes equipped with a JMX Exporter pod to gather the data and send them to Prometheus. We then have a Grafana dashboard presenting all the Puppetserver metrics in a useful manner.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/wxq2pi5b4jn5knp4pn71.png\" alt=\"Puppetserver metrics in Grafana\"></p>\n<p>Looking at the graphs from even before the switch showed that something was indeed aloof. </p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/63f6ewg4uheojxp1abv3.png\" alt=\"JVM Metrics for Puppetserver 5 and 6\"></p>\n<p>Clearly, the Puppetserver 6 instances had a very different memory ramp up (blue and yellow \"teeth\" lines), even during the tests phase. I just hadn't noticed then.</p>\n<p>We started a series of tests, using Puppet Catalog Diff runs to test load the servers, and playing on all various parameters of our stack:</p>\n<ul>\n<li>memory requests</li>\n<li>memory limits</li>\n<li>cpu limits</li>\n<li>Java Xmx</li>\n<li>max active instances</li>\n</ul>\n<p>It quickly became clear that the main factor in our problem was that the memory request was too low.</p>\n<p>The official <a href=\"https://puppet.com/docs/puppetserver/latest/tuning_guide.html#jvm-heap-size\">Puppet documentation</a> gives a rule of thumb for tuning the Puppetserver memory. It indicates that each active instance requires 512MB, but another 512MB should be provided for non-heap needs:</p>\n<blockquote>\n<p>You’ll also want to allocate a little extra heap to be used by the rest of the things going on in Puppet Server: the web server, etc. So, a good rule of thumb might be 512MB + (max-active-instances * 512MB).</p>\n</blockquote>\n<p>Our graphs clearly showed that the non-heap memory of the instances stabilized a little bit over 512MB (around 550MB as I'm writing this).</p>\n<p>Since we requested 4 JRuby instances, we should ensure at least (4+1)*512MB of RAM, so 2.5GB. And while our limit was set to 4GB, the requests were only set to 2GB. Changing the requested memory to a higher value showed that this was what was making our servers misbehave.</p>\n<h1>Further tuning</h1>\n<h2>CPU limit</h2>\n<p>We originally set the containers to a CPU limit of 3 because or compute nodes have 4 CPUs and we wanted to leave one free.</p>\n<p>We actually noticed that Puppetserver was using closer to 2.5 CPUs as a mean. So we set the limit to 4 and saw that the Puppetserver seemed to use even less CPU, down to a mean of 2.</p>\n<p>Note that limiting CPUs is necessary when running Java in containers, otherwise Java believes it runs on a single CPU.</p>\n<h2>Max JRuby Instances</h2>\n<p>The number of JRubies recommended changed between Puppet 5 and 6, as stated in the documentation. Up to Puppet 4, it was recommended to set it to <code>num-cpus + 2</code>, but the docs now state:</p>\n<blockquote>\n<p>As of Puppet Server 1.0.8 and 2.1.0, if you don’t provide an explicit value for this setting, we’ll default to num-cpus - 1, with a minimum value of 1 and a maximum value of 4.</p>\n</blockquote>\n<p>We ran load tests with different values of max JRuby instances and found that <code>num-cpus - 1</code> was indeed the best good value.</p>\n<p>Most importantly, we found that setting up the max JRuby instances higher than the number of CPUs made compilation time quite slower (supposedly as it would increase context-switch between the instances).</p>\n<h2>Final settings</h2>\n<p>Following the guidelines and our tests, we ended up with:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">max_active_instances: &#39;3&#39;   # since we have 4 CPUs\njava_xmx: &#39;2g&#39;              # (3+1)*512MB\n\nrequests:\n  cpu: 10m\n  memory: 3Gi               # 1.5*XmX\nlimits:\n  cpu: 4                    # Use all available CPUs as limit\n  memory: 3.3Gi             # 1.1*request just in case</code>\n        </deckgo-highlight-code>\n<p>On this graph from the last 2 days, we can clearly see the situations before production time, during crisis, and after proper tuning:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/t2lnmj23y7z3cvgo0b1t.png\" alt=\"JVM Metrics evolution\"></p>\n<h1>Kubernetes-related issues</h1>\n<p>So our Puppetserver was back under control, with pretty similar memory settings to what Puppet 5 used.</p>\n<p>Keeping the two pods we had been running, with their new tuned settings, we ran stress tests by setting the number of parallel threads in the Catalog Diff run.</p>\n<p>When running 12 threads in parallel (far more than what 2 pods with 3 JRubies each can take), we noticed something I had seen before but not understood:</p>\n<deckgo-highlight-code language=\"css\"  >\n          <code slot=\"code\">Failed to retrieve catalog for foo.example.com from puppetserver in environment catalog_diff: Failed to open TCP connection to puppetserver:8140 (No route to host - connect(2) for &quot;puppetserver&quot; port 8140)</code>\n        </deckgo-highlight-code>\n<p>I had initially thought this was what happened by the Puppetserver was too busy and started rejecting connections. But no, this was clearly another problem, linked to Kubernetes networking and readiness probes.</p>\n<p>As we kept an eye on the Pods readiness during the run, we noticed the pods were going on and off, and thus being taken out of the Kubernetes service on a regular basis. The TCP connection issues happened when both pods were taken out of the service at the same time, since the service ended up with no endpoints left.</p>\n<p>So we turned to the pod's readiness probe to tune it.</p>\n<p>This is the default readiness probe we use in our Helm chart:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">readinessProbe:\n  httpGet:\n    path: /production/status/test\n    port: http\n    scheme: HTTPS\n    httpHeaders:\n      - name: Accept\n        value: pson\n  initialDelaySeconds: 30</code>\n        </deckgo-highlight-code>\n<p>The initial delay lets the Puppetserver start its first JRuby instance before sending a probe. The default in Kubernetes would be <code>0</code> otherwise, which would clearly fail for a Puppetserver.</p>\n<p>For all other settings, we relied on the defaults. As per <a href=\"https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes\">Kubernetes docs</a>:</p>\n<blockquote>\n<ul>\n<li>initialDelaySeconds: Number of seconds after the container has started before liveness or readiness probes are initiated. Defaults to 0 seconds. Minimum value is 0.</li>\n<li>periodSeconds: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.</li>\n<li>timeoutSeconds: Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1.</li>\n<li>successThreshold: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness. Minimum value is 1.</li>\n<li>failureThreshold: When a Pod starts and the probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the container. In case of readiness probe the Pod will be marked Unready. Defaults to 3. Minimum value is 1.</li>\n</ul>\n</blockquote>\n<p>The <code>timeoutSeconds</code> parameter looks very low at 1 second by default. Indeed, we know that a busy Puppetserver could take more than 1 second to respond and that would be perfectly acceptable. So we've set it to <code>5</code> instead and the service has been much more stable since.</p>\n<p>We've also set <code>failureThreshold</code> to <code>5</code>.</p>\n<h1>Conclusion</h1>\n<p>A small fine-tuning goes a long way!</p>\n<p>Be sure to gather enough data about your Puppetserver so you have the tools to debug its behavior when you need.</p>\n<p>Do you have Puppet, Kubernetes, or observability needs? You can <a href=\"https://www.camptocamp.com/contact/\">contact  us</a> and we'll be happy to put our expertise at your service!</p>"},{"url":"/posts/20200602-getting-puppet-report-metrics-from-puppetdb-6bp/","relativePath":"posts/20200602-getting-puppet-report-metrics-from-puppetdb-6bp.md","relativeDir":"posts","base":"20200602-getting-puppet-report-metrics-from-puppetdb-6bp.md","name":"20200602-getting-puppet-report-metrics-from-puppetdb-6bp","frontmatter":{"title":"Getting Puppet Report Metrics from PuppetDB","template":"post","date":"2020-06-02T09:22:19Z","excerpt":"Instead of sending metrics from the Puppetserver to Prometheus, they can be retrieved using the PuppetDB Metrics API.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjczukvqf1fs7dau9jnfp.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjczukvqf1fs7dau9jnfp.png","canonical_url":"https://dev.to/camptocamp-ops/getting-puppet-report-metrics-from-puppetdb-6bp","devto_url":"https://dev.to/camptocamp-ops/getting-puppet-report-metrics-from-puppetdb-6bp"},"html":"<p>Puppet agent run reports contain useful metrics, such as the number of resources that were modified or failed to apply, or how much time each step of the run took.</p>\n<p>The traditional way of retrieving these metrics is using a report processor on the Puppet master.</p>\n<p>Since Prometheus is now a <em>de facto</em> standard in metrics collection, there exists a <a href=\"https://github.com/voxpupuli/puppet-prometheus_reporter\">Prometheus reporter, maintained by the VoxPupuli community</a>. However, it uses a dropzone directory of yaml files with a local node exporter, so it's not a very clean approach.</p>\n<p>On top of this, reports and their metrics are already exported to the PuppetDB, which provides its own API to access this data.</p>\n<h1>Prometheus PuppetDB Exporter</h1>\n<p><a href=\"https://github.com/camptocamp/prometheus-puppetdb-exporter\">GitHub — camptocamp/prometheus-puppetdb-exporter</a></p>\n<p>Prometheus PuppetDB Exporter is a simple go binary that can scrape the PuppetDB for report metrics for Prometheus. It runs independently of the Puppet stack, and can be tuned to collect various types of metrics:</p>\n<ul>\n<li>resources</li>\n<li>time</li>\n<li>changes</li>\n<li>events</li>\n</ul>\n<p>The exporter provides metrics in the form <code>puppet_report_&#x3C;type></code> for each of these types.</p>\n<deckgo-highlight-code language=\"prometheus\"  >\n          <code slot=\"code\"># HELP puppetdb_exporter_build_info puppetdb exporter build informations\n# TYPE puppetdb_exporter_build_info gauge\npuppetdb_exporter_build_info{build_date=&quot;2019-02-18&quot;,commit_sha=&quot;XXXXXXXXXX&quot;,golang_version=&quot;go1.11.4&quot;,version=&quot;1.0.0&quot;} 1\n# HELP puppetdb_node_report_status_count Total count of reports status by type\n# TYPE puppetdb_node_report_status_count gauge\npuppetdb_node_report_status_count{status=&quot;changed&quot;} 1\npuppetdb_node_report_status_count{status=&quot;failed&quot;} 1\npuppetdb_node_report_status_count{status=&quot;unchanged&quot;} 1</code>\n        </deckgo-highlight-code>\n<p>This makes it fully compatible with Vox Pupuli's reporter implementation.</p>\n<h1>Deploying</h1>\n<p>The exporter is provided as a <a href=\"https://hub.docker.com/r/camptocamp/prometheus-puppetdb-exporter\">Docker image</a>, and is included by default in <a href=\"https://github.com/camptocamp/charts/tree/master/puppetdb\">Camptocamp's PuppetDB Helm chart</a>.</p>\n<h1>Usage in Grafana</h1>\n<p>Coupled with (a slightly modified version of) <a href=\"https://grafana.com/grafana/dashboards/700\">Julien Pivotto's Puppet Report dashboard</a>, you can make some pretty graphs from these metrics:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/jczukvqf1fs7dau9jnfp.png\" alt=\"Node graphs\"></p>"},{"url":"/posts/20200603-tracing-x-to-my-4th-great-grandmother-2af9/","relativePath":"posts/20200603-tracing-x-to-my-4th-great-grandmother-2af9.md","relativeDir":"posts","base":"20200603-tracing-x-to-my-4th-great-grandmother-2af9.md","name":"20200603-tracing-x-to-my-4th-great-grandmother-2af9","frontmatter":{"title":"Tracing X to my 4th great-grandmother","template":"post","date":"2020-06-03T22:23:33Z","excerpt":"X chromosomes have a specific inheritance pattern which often allow to narrow family branches when looking for relationships hypothesis","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2F5%2F59%2FIdeogram_house_mouse_chromosome_X.svg%2F1280px-Ideogram_house_mouse_chromosome_X.svg.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2F5%2F59%2FIdeogram_house_mouse_chromosome_X.svg%2F1280px-Ideogram_house_mouse_chromosome_X.svg.png","canonical_url":"https://dev.to/raphink/tracing-x-to-my-4th-great-grandmother-2af9","devto_url":"https://dev.to/raphink/tracing-x-to-my-4th-great-grandmother-2af9"},"html":"<p>DNA tests are fun. They can give you a hint on your origins (though the results depend a lot on the data sets from the company providing them), get you in touch with cousins (or even closer relatives) you didn't know about, confirm genealogy hypothesis, and much more...</p>\n<p>One thing that is interesting to do with DNA results is to trace known DNA segments to a known ancestor. This is not always possible, and usually requires several triangulated matches on that segment to ensure where it comes from.</p>\n<h1>X chromosome inheritance</h1>\n<p>In my DNA results, I have a rather large match on my X chromosome. This match is about 40cM long (for a total length of about <a href=\"https://isogg.org/wiki/CentiMorgan#cm_values_per_chromosome\">196cM at FamilyTreeDNA</a>).</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/690rgl67j0nnunn5shm0.png\" alt=\"A 40cM match on X\"></p>\n<p>Where could this segment have come from? Is there a way to tell?</p>\n<p>In autosomal tests, the X chromosome is often included, but it plays by\nits own rules. This is because X is transmitted in a funny way, due to\nthe fact that males only have one X chromosome (and one Y chromosome),\nwhile females have two (and no Y chromosome).</p>\n<p>Here's an illustration <a href=\"https://en.wikipedia.org/wiki/X_chromosome#Inheritance_pattern\">from Wikipedia</a> explaining how this affects the inheritance rules of that special chromosome:</p>\n<p><img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ed/X_chromosome_ancestral_line_Fibonacci_sequence.svg/1920px-X_chromosome_ancestral_line_Fibonacci_sequence.svg.png\" alt=\"X chromosome inheritance\"></p>\n<p>As you can see, the consequence is pretty simple:</p>\n<ul>\n<li>men get their only X chromosome from their mother (I'm leaving aside the very rare XXY cases)</li>\n<li>women get one X chromosome from each of their parents.</li>\n</ul>\n<p>As a result, a segment of an X chromosome can travel through both men and women (unlike segments on the Y chromosome, which travel only through men), but can never be carried by two men in a row.  </p>\n<p>In other words, since I am a man, I cannot have gotten this segment from:</p>\n<ul>\n<li>my father (or his side ancestors, obviously)</li>\n<li>my grand-father's father</li>\n<li>my grand-mother's father's father</li>\n<li>or any other relationship involving a father and his son</li>\n</ul>\n<p>Now I'm quite lucky, as I happen to know the person I match on 40cM on that X chromosome, even though the match is quite distant:</p>\n<ul>\n<li>First off, that person is Jewish, an ethnic group that matches my mother's side exclusively. Check ✅</li>\n<li>Then, this person comes from a very special family branch from Egypt, which happens to be on my grand-father's mother's side. Check again ✅</li>\n<li>I don't know any other possible (close enough) relation with that person that could explain this X match, and we both have quite developed family trees ✅</li>\n</ul>\n<p>So, unless a closer path can be found in the future (which is unlikely), the inheritance hypothesis checks with both sides of the tree, confirming the chain of ancestors:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/yd2eyu0ihscnz7emcd51.png\" alt=\"Tracing the X segment\"></p>\n<p>You can see how that X segment was transmitted from my 4th great-grandmother Luna Mondolfo (born around 1790) all the way to myself (on the left) and my DNA match (on the right).</p>\n<p>Once this is confirmed, it also allows to consider that any other match triangulating on this X segment is more likely linked to this side of the family, known and verified until the 18th century.</p>\n<h1>Keeping track of segments</h1>\n<p>A great way of tracing known DNA segments is to use <a href=\"https://dnapainter.com/\">DNA Painter</a>, a free website where you can gather segment information from various companies (FTDNA, Ancestry, GedMatch, MyHeritage, etc.) to \"paint\" your chromosomes.</p>\n<p>Here's an example showing my maternal labels, with chromosome X at the bottom:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/vejbspsiy6tmsg8wcpu1.png\" alt=\"DNA Painter\"></p>\n<p>Did you make interesting finds in DNA tests as well?</p>"},{"url":"/posts/20200522-bitten-by-ha-puppetdb-postgresql-1eld/","relativePath":"posts/20200522-bitten-by-ha-puppetdb-postgresql-1eld.md","relativeDir":"posts","base":"20200522-bitten-by-ha-puppetdb-postgresql-1eld.md","name":"20200522-bitten-by-ha-puppetdb-postgresql-1eld","frontmatter":{"title":"Bitten by HA: PuppetDB & PostgreSQL","template":"post","date":"2020-05-22T14:32:17Z","excerpt":"When PuppetDB started misbehaving, it took us quite a while to realize the problem was somewhere else…","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fux2ltllgi7mjem72mf2z.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fux2ltllgi7mjem72mf2z.png","canonical_url":"https://dev.to/camptocamp-ops/bitten-by-ha-puppetdb-postgresql-1eld","devto_url":"https://dev.to/camptocamp-ops/bitten-by-ha-puppetdb-postgresql-1eld"},"html":"<p>Last Wednesday morning, a colleague informed me that our internal Puppet infrastructure was performing slowly. Looking at the Puppetboard, I quickly realized there was another issue: all the nodes were marked as unreported, with the last report dating from more than 24 hours in the past.</p>\n<p>I checked the PuppetDB logs and saw that the reports were coming fine and being saved, so something else was wrong.</p>\n<h2>PuppetDB Upgrade</h2>\n<p>After a few hours of debugging, I still had no clue so I resorted to the option of upgrading the PuppetDB. I ideally wanted to stay with PuppetDB 5.x to avoid a major upgrade, but there was no available official Docker image for PuppetDB 5.x above 5.2.7 (which we are using).</p>\n<p>So I upgraded to PuppetDB 6.9.1 and then PuppetDB 6.10.1.</p>\n<h2>PostgreSQL SSL</h2>\n<p>At first, the connection with PostgreSQL failed as PuppetDB now defaults to fully verifying the PostgreSQL SSL certificate against the Puppet CA. So I had to modify the PostgreSQL connection string to include <code>?sslmode=require</code> and restart the PuppetDB.</p>\n<h2>Jolokia Authorization</h2>\n<p>The logs seemed fine at first, but the Puppetboard kept returning an error 500. That's when I realized that PuppetDB now required an access file to allow hosts by IP range. I modified our Helm chart to include <a href=\"https://github.com/voxpupuli/puppetboard/issues/566#issuecomment-622234110\">a new configuration file</a>, but Puppetboard still wasn't happy.</p>\n<h2>JDBC errors</h2>\n<p>I restarted the PuppetDB and started seeing weird log lines looping infinitely, right after the migration step in startup:</p>\n<deckgo-highlight-code language=\"logs\"  >\n          <code slot=\"code\">2020-05-20 18:55:40,169 WARN  [clojure-agent-send-off-pool-1] [p.p.jdbc] Caught exception. Last attempt, throwing exception.</code>\n        </deckgo-highlight-code>\n<p>At this point, <a href=\"https://twitter.com/csharpsteen\">Charlie Sharpsteen</a> started helping me to debug the issue.</p>\n<p>I tried manual <code>curl</code> requests to the PuppetDB and the logs started sprouting stack traces mentioning both JDBC connection issues as well as the database schema version being wrong:</p>\n<deckgo-highlight-code language=\"logs\"  >\n          <code slot=\"code\">...\nCaused by: java.sql.SQLTransientConnectionException: PDBReadPool - Connection is not available, request timed out after 3000ms.\n...\nCaused by: com.zaxxer.hikari.pool.PoolBase$ConnectionSetupException: org.postgresql.util.PSQLException: ERROR: Please run PuppetDB with the migrate option set to true\n                 to upgrade your database. The detected migration level 66 is\n                 out of date.\n...</code>\n        </deckgo-highlight-code>\n<p>I connected to the PostgreSQL database and checked it. Everything looked fine, and <code>select max(version) from schema_migrations;</code> returned <code>74</code> as expected, not <code>66</code>. So where did this number come from? Charlie started suspecting that there was another database involved…</p>\n<p>Totally out of other options, I decided to remove the lines with versions above 66 in the <code>schema_migrations</code> table and see if restarting the PuppetDB would finalize the migration. That was a huge failure, as the migration scripts are not idempotent, as could be expected.</p>\n<p>I was left with only one option: dropping the database and restoring it.\nBut then PostgreSQL refused to drop the database, saying it was read-only. I tried forcing read-write, but the database was marked in recovery.</p>\n<p>That's when I gave up for the day (as it was already past 23:00). I turned off the PuppetDB service entirely (scaled the deployment to 0 replicas actually) and went to bed, letting the nodes apply catalogs from cache for the next 30 hours (since Thursday was off).</p>\n<h2>DNS Issue</h2>\n<p>This morning, we got back to debugging this problem, and things started making more sense.</p>\n<p>First off, it turned out I was trying to drop the database on a slave cluster. I had ended up on the slave by using a production CNAME DNS entry which pointed to both the master and slave in round-robin…</p>\n<p>Once my colleague <a href=\"https://github.com/Vampouille\">Julien</a> had helped me realize that, he was able to drop the database on the master. We restarted the PuppetDB in version 6.10.1. But the errors were still there…</p>\n<h2>The Data is still there</h2>\n<p>We rolled back to PuppetDB 5.2.7 and a clean database… Everything started fine, but the Puppetboard still showed all nodes as unreported! Where could it get these nodes if the database had been wiped‽</p>\n<p>This led us to the conclusion that the data was still somewhere else… on the slave…</p>\n<h1>The problem</h1>\n<p>So here's what happened.</p>\n<h2>The root cause</h2>\n<p>Earlier this week, there was an outage with the object storage facility we're using for wal-g, our PostgreSQL backup tool.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/ux2ltllgi7mjem72mf2z.png\" alt=\"PostgreSQL cluster losing synchronization\"></p>\n<p>This led to a disk full on our master PostgreSQL machine. The disk full was very short as PostgreSQL restarted and removed all its WALs so it went unnoticed. However, this also broke replication, so the slave PostgreSQL database ended up stuck on that date. For some reason, we missed the replication alert.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/ayytz0tufaf6mrh83q29.png\" alt=\"Wal segments lost\"></p>\n<h2>The PuppetDB symptoms</h2>\n<p>PuppetDB is configured to write to master and read from slave. This is why all our nodes were unreported in Puppetboard (since they came from slave), even though PuppetDB kept writing the reports properly (in master)! This also explains the weird errors after upgrading to PuppetDB 6, since migration was properly done on the master (to schema v74) but read requests went to the slave (stuck in schema v66).</p>\n<h1>The solution</h1>\n<p>Since we had wiped the master's database this morning, we ended up restoring from the slave's version, and going back to PuppetDB 5.2.7 until we can properly solve the Jolokia potential issues with external access to the PuppetDB API.</p>\n<p>All nodes in Puppetboard have now returned to normal.</p>"},{"url":"/posts/20200608-how-to-manage-files-with-puppet-55e4/","relativePath":"posts/20200608-how-to-manage-files-with-puppet-55e4.md","relativeDir":"posts","base":"20200608-how-to-manage-files-with-puppet-55e4.md","name":"20200608-how-to-manage-files-with-puppet-55e4","frontmatter":{"title":"All the ways to manage files with Puppet","template":"post","date":"2020-06-08T21:12:50Z","excerpt":"Puppet has many tools to manage configuration files. Knowing them can help you choose the one that best fits your needs.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllt1le7b54wy0459shs1.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllt1le7b54wy0459shs1.png","canonical_url":"https://dev.to/camptocamp-ops/how-to-manage-files-with-puppet-55e4","devto_url":"https://dev.to/camptocamp-ops/how-to-manage-files-with-puppet-55e4"},"html":"<p>\"<a href=\"https://en.wikipedia.org/wiki/Everything_is_a_file\">Everything is a file</a>\" is a very famous Unix principle. And because of this, most of configuration management on Unix/Linux revolves around managing files.</p>\n<h1>Know your Tools <a name=\"tools\"></a></h1>\n<p>Puppet, as a configuration management tool, is no exception to this. As a consequence, there are many ways to manage configuration files with Puppet. They all have a reason to exist, and a purpose to fulfill.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/rd2gpnxaq87rvgl5vz0j.jpg\" alt=\"Know your tools\"></p>\n<p>Knowing your tools is the object of this blog post, with the following topics:</p>\n<ul>\n<li><a href=\"#tools\">Know your Tools</a></li>\n<li><a href=\"#approaches\">File Management Approaches</a></li>\n<li>\n<p><a href=\"#whole-cfg\">Managing Whole Configurations</a></p>\n<ul>\n<li><a href=\"#whole-static\">Static Content</a></li>\n<li><a href=\"#whole-static-scripts\">Deploying Scripts and Binary Data</a></li>\n<li><a href=\"#whole-static-source\">Beware of Source</a></li>\n<li><a href=\"#whole-dynamic\">Dynamic Content</a></li>\n<li><a href=\"#whole-dynamic-purge\">Purgeable Types</a></li>\n<li><a href=\"#whole-dynamic-onescope\">Content from One Scope</a></li>\n<li>\n<p><a href=\"#whole-dynamic-multiscope\">Content from Multiple Scopes</a></p>\n<ul>\n<li><a href=\"#whole-dynamic-multiscope-includes\">Includes</a></li>\n<li><a href=\"#whole-dynamic-multiscope-concat\">Concat</a></li>\n</ul>\n</li>\n<li><a href=\"#whole-dynamic-example\">Content from an Example File</a></li>\n</ul>\n</li>\n<li>\n<p><a href=\"#partial-cfg\">Managing Partial Configurations</a></p>\n<ul>\n<li><a href=\"#partial-native\">Native Types</a></li>\n<li><a href=\"#partial-includes\">Includes</a></li>\n<li><a href=\"#partial-augeas\">The Augeas Type</a></li>\n<li><a href=\"#partial-fileline\">File line</a></li>\n</ul>\n</li>\n<li><a href=\"#conclusion\">Conclusion</a></li>\n</ul>\n<h1>File Management Approaches <a name=\"approaches\"></a></h1>\n<p>Let's start with a first choice when managing files:</p>\n<ul>\n<li>managing the whole configuration</li>\n<li>managing partial configurations</li>\n</ul>\n<p>Many practitioners I've met consider that managing whole configurations is the only acceptable way of proceeding, since managing partial configuration does not lead to a predictable final state. </p>\n<p>However, managing whole configurations often leads to managing defaults which were carefully chosen for your GNU/Linux distribution and were perfectly fine to keep. It also leads to maintaining lots of different defaults in modules to try and stay as close as possible to the distribution standards when using default values.</p>\n<p>So both approaches have pros and cons.</p>\n<h1>Managing Whole Configurations <a name=\"whole-cfg\"></a></h1>\n<p>This is the approach you take when you want to control the full content of the software configuration. This does not mean however that everything has to fit into a single file; the configuration might be split, and splitting often makes configuration management more flexible.</p>\n<h2>Static Content <a name=\"whole-static\"></a></h2>\n<p>The easiest case is without doubt managing static content, when your file is always the same.</p>\n<p>However simple this might seem, there can still be tricks.</p>\n<h3>Deploying Scripts and Binary Data <a name=\"whole-static-scripts\"></a></h3>\n<p>For example, scripts and binary blobs can easily be managed this way in Puppet, and we often see code such as:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">file { &#39;/usr/local/bin/myscript.sh&#39;:\n  ensure =&gt; file,\n  source =&gt; &quot;puppet:///modules/${module_name}/myscript.sh&quot;,\n}</code>\n        </deckgo-highlight-code>\n<p>That works fine, but if you're trying to deploy a software (maybe even with a tarball, using the <a href=\"https://forge.puppet.com/puppet/archive\"><code>puppet-archive</code></a> module), it's probably better to package it for your distribution and use your package manager (apt/yum/etc.) as the deployment layer. You'll get a much simpler Puppet code, usually better performance, and you'll rely on the package manager's metadata for idempotence:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">package { &#39;myscript&#39;:\n  ensure =&gt; present,\n}</code>\n        </deckgo-highlight-code>\n<p>Another possibility is using the <a href=\"https://forge.puppet.com/puppetlabs/vcsrepo\"><code>puppetlabs-vcsrepo</code></a> resource type. The VCS (e.g. Git) will then provide the metadata to ensure idempotence, e.g.:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">vcsrepo {&#39;/usr/src/ceph-rbd-backup&#39;:\n  ensure   =&gt; latest,\n  provider =&gt; &#39;git&#39;,\n  source   =&gt; &#39;https://github.com/camptocamp/ceph-rbd-backup&#39;,\n  revision =&gt; &#39;master&#39;,\n}</code>\n        </deckgo-highlight-code>\n<h3>Beware of Source <a name=\"whole-static-source\"></a></h3>\n<p>When using the <code>file</code> type to deploy static content, it is quite common to use the <code>source</code> attribute to specify the file to copy:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">file { &#39;/srv/foo&#39;:\n  ensure =&gt; file,\n  source =&gt; &quot;puppet:///modules/${module_name}/foo&quot;,\n}</code>\n        </deckgo-highlight-code>\n<p>This makes use of the Puppetserver's <a href=\"https://puppet.com/docs/puppet/latest/config_file_fileserver.html\">fileserver feature</a>. When using this syntax, every Puppet run will result in a <code>file_metadata</code> HTTP request to the Puppetserver for each file managed, just to get the metadata necessary to decide whether the file needs to be replaced or not.</p>\n<p>When many files are managed this way on many agents, this results in lots of HTTP requests being made during catalog application, which will saturate the Puppetserver's JRuby threads and prevent them from processing catalog compilations and reports instead.</p>\n<p>Instead, when deploying non-binary content, you can use the <code>file()</code> function with a relative path:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">file { &#39;/srv/foo&#39;:\n  ensure  =&gt; file,\n  content =&gt; file(&quot;${module_name}/foo&quot;),\n}</code>\n        </deckgo-highlight-code>\n<p>This is totally equivalent to the previous syntax, except the whole file will be included in the catalog, instead of just a pointer to the fileserver.</p>\n<h2>Dynamic Content <a name=\"whole-dynamic\"></a></h2>\n<p>Very often, static content is not enough to configure your software. You need variables, and a more flexible approach.</p>\n<h3>Purgeable Types <a name=\"whole-dynamic-purge\"></a></h3>\n<p>Native Puppet Ruby types are probably the most flexible way of managing a configuration, as they provide a very fine-grained interface to edit configuration files.</p>\n<p>However, they do not manage the whole configuration by default. That is, unless you can use purging with them.</p>\n<p>Purging resources in Puppet requires two conditions:</p>\n<ul>\n<li>a type which supports listing instances (at least one provider has a <code>self.instances</code> method defined)</li>\n<li>a parameter that can ensure the resource's absence</li>\n</ul>\n<p>When both these conditions are met, Puppet can purge the resources it doesn't explicitly manage by:</p>\n<ul>\n<li>listing all known resources (using the <code>self.instances</code> method)</li>\n<li>setting all of them to be absent by default</li>\n<li>overriding the presence with the catalog's explicit resource parameters</li>\n</ul>\n<p>There are two main ways of achieving this:</p>\n<ul>\n<li>using the standard <code>resources</code> type</li>\n<li>using the <a href=\"https://forge.puppet.com/crayfishx/purge\"><code>crayfishx-purge</code></a> module</li>\n</ul>\n<p>The <code>resources</code> type fits basic needs, by allowing to purge all resources not managed by Puppet. For example:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">host { &#39;localhost&#39;:\n  ensure =&gt; present,\n  ip     =&gt; &#39;127.0.0.1&#39;,\n}\n\nresources { &#39;host&#39;:\n  purge =&gt; true,\n}</code>\n        </deckgo-highlight-code>\n<p>will purge all entries in <code>/etc/hosts</code> except for localhost.</p>\n<p>The <code>resources</code> resource type also allows to set exceptions, though only for the <code>user</code> type:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">resources { &#39;user&#39;:\n  purge              =&gt; true,\n  unless_system_user =&gt; true,\n}</code>\n        </deckgo-highlight-code>\n<p>This is a hard limitation, which the <a href=\"https://forge.puppet.com/crayfishx/purge\"><code>purge</code> type</a> fixes by providing a more flexible interface, allowing to set:</p>\n<ul>\n<li>fine conditions for purging resources</li>\n<li>which parameter and value to use for purging.</li>\n</ul>\n<p>For example:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">purge { &#39;mount&#39;:\n  state  =&gt; unmounted,\n  unless =&gt; [&#39;name&#39;, &#39;==&#39;, [&#39;/&#39;, &#39;/var&#39;, &#39;/home&#39;]],\n}</code>\n        </deckgo-highlight-code>\n<p>will unmount all file systems that are not managed by Puppet, unless they are mounted on <code>/</code>, <code>/var</code> or <code>/home</code>.</p>\n<p>In order to manage configurations in full, the <code>purge</code> type can be used with native types that manage configuration file stanzas and know how to list instances.</p>\n<p>This is the case of:</p>\n<ul>\n<li>the <a href=\"https://puppet.com/docs/puppet/5.5/types/host.html\"><code>host</code> type</a></li>\n<li>the <a href=\"https://puppet.com/docs/puppet/5.5/types/mailalias.html\"><code>mailalias</code> type</a></li>\n<li>most <a href=\"http://augeasproviders.com/\">Augeasproviders types</a></li>\n</ul>\n<p>For example, you can manage <code>sshd_config</code> in full using the <a href=\"https://forge.puppet.com/herculesteam/augeasproviders_ssh\"><code>herculesteam-augeasproviders_ssh</code></a> module with code such as:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">sshd_config {\n  &#39;X11Forwarding&#39;:\n    value =&gt; &#39;yes&#39;,\n    ;\n\n  &#39;UsePAM&#39;:\n    value =&gt; &#39;no&#39;,\n    ;\n}\n\npurge { &#39;sshd_config&#39;: }</code>\n        </deckgo-highlight-code>\n<h3>Content from One Scope <a name=\"whole-dynamic-onescope\"></a></h3>\n<p>When there are no purgeable types for your configuration file type, and you need to manage the content from a single scope (a single Puppet class), the most obvious option is to use a simple <code>file</code> resource with a template. Prefer <a href=\"https://puppet.com/docs/puppet/5.5/lang_template_epp.html\">EPP templates</a> these days, as they are easier and safer than ERB templates:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">file { &#39;/path/to/foo&#39;:\n  ensure  =&gt; file,\n  content =&gt; epp(\n    &quot;${module_name}/foo.epp&quot;,\n    {\n      var1 =&gt; &#39;value1&#39;,\n      var2 =&gt; &#39;value2&#39;,\n    }\n  ),\n}</code>\n        </deckgo-highlight-code>\n<h3>Content from Multiple Scopes <a name=\"whole-dynamic-multiscope\"></a></h3>\n<p>When your content needs to come from multiple scopes, a single <code>file</code> resource won't suffice.</p>\n<h4>Includes <a name=\"whole-dynamic-multiscope-includes\"></a></h4>\n<p>If you're lucky and your configuration format supports include statements, this is the easiest way to go. For example:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\"># Deploy a static file to perform the inclusion\nfile { &#39;/etc/sudoers&#39;:\n  ensure  =&gt; file,\n  content =&gt; &#39;#includedir /etc/sudoers.d&#39;,\n}\n\n# Deploy each rule as a separate file in the directory\nfile { &#39;/etc/sudoers.d/defaults_env&#39;:\n  ensure  =&gt; file,\n  content =&gt; &#39;Defaults env_reset&#39;,\n}\n\nfile { &#39;/etc/sudoers.d/foo&#39;:\n  ensure  =&gt; file,\n  content =&gt; &#39;foo ALL=(ALL:ALL) ALL&#39;,\n}\n\n# Let Puppet purge the directory of all unknown files\nfile { &#39;/etc/sudoers.d&#39;:\n  ensure =&gt; directory,\n  purge  =&gt; true,\n}</code>\n        </deckgo-highlight-code>\n<h4>Concat <a name=\"whole-dynamic-multiscope-concat\"></a></h4>\n<p>Many configuration formats don't support includes: everything has to be in a single file. Managing such a file from multiple scopes requires the use of a concat module.</p>\n<p>The most used concat module is the official <a href=\"https://forge.puppet.com/puppetlabs/concat\"><code>puppetlabs-concat</code></a>. It lets you declare a target file where all fragments will be concatenated, and then deploy multiple fragments tagged for this target. For example, the sudoers example above is roughly equivalent to:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">concat { &#39;/etc/sudoers&#39;:\n  ensure =&gt; present,\n}\n\nconcat::fragment { &#39;defaults_env&#39;:\n  target  =&gt; &#39;/etc/sudoers&#39;,\n  content =&gt; &#39;Defaults env_reset&#39;,\n  order   =&gt; &#39;01&#39;,\n}\n\nconcat::fragment { &#39;foo&#39;:\n  target  =&gt; &#39;/etc/sudoers&#39;,\n  content =&gt; &#39;foo ALL=(ALL:ALL) ALL&#39;,\n  order   =&gt; &#39;10&#39;,\n}</code>\n        </deckgo-highlight-code>\n<p>Each fragment is deployed separately to the agent, then concatenated to generate the final file.</p>\n<h3>Content from an Example File <a name=\"whole-dynamic-example\"></a></h3>\n<p>A few years ago, I experimented with yet another option to manage full dynamic content, without losing the benefit of sane distribution defaults.</p>\n<p>The <a href=\"https://forge.puppet.com/camptocamp/augeas_file\"><code>camptocamp-augeas_file</code></a> resource type allows to use a local file on the Puppet agent as a template on which Augeas changes are applied to generate the final file:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">augeas_file { &#39;/etc/apt/sources.list.d/jessie.list&#39;:\n  lens    =&gt; &#39;Aptsources.lns&#39;,\n  base    =&gt; &#39;/usr/share/doc/apt/examples/sources.list&#39;,\n  changes =&gt; [&#39;setm ./*[distribution] distribution jessie&#39;],\n}</code>\n        </deckgo-highlight-code>\n<p>Every time the Puppet agent runs, it will use <code>/usr/share/doc/apt/examples/sources.list</code> as a template and apply the <code>changes</code> using Augeas to generate <code>/etc/apt/sources.list.d/jessie.list</code>. The target file is only written if any changes occur, making it idempotent. If the template changes (e.g. after a package upgrade), the target will be regenerated.</p>\n<h1>Managing Partial Configurations <a name=\"partial-cfg\"></a></h1>\n<p>Partial configurations have less options to be managed. They're essentially light versions of the options cited above:</p>\n<h2>Native types <a name=\"partial-native\"></a></h2>\n<p>Just as <a href=\"#whole-dynamic-purge\">for full configurations</a>, you can use native puppet types (<code>host</code>, <code>mailalias</code>, Augeasproviders types, etc.), without purging them.</p>\n<p>In addition, since you don't mind managing files partially, you can also use types which don't support purging, such as <a href=\"https://forge.puppet.com/puppetlabs/inifile\"><code>ini_setting</code></a> for INI file types:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">ini_setting { &quot;sample setting&quot;:\n  ensure  =&gt; present,\n  path    =&gt; &#39;/tmp/foo.ini&#39;,\n  section =&gt; &#39;bar&#39;,\n  setting =&gt; &#39;baz&#39;,\n  value   =&gt; &#39;quux&#39;,\n}</code>\n        </deckgo-highlight-code>\n<p>or the <a href=\"https://forge.puppet.com/herculesteam/augeasproviders_shellvar\"><code>shellvar</code> type</a> for shell configuration files:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">shellvar { &quot;ntpd options&quot;:\n  ensure   =&gt; present,\n  target   =&gt; &quot;/etc/sysconfig/ntpd&quot;,\n  variable =&gt; &quot;OPTIONS&quot;,\n  value    =&gt; &quot;-g -x -c /etc/myntp.conf&quot;,\n}</code>\n        </deckgo-highlight-code>\n<h2>Includes <a name=\"partial-includes\"></a></h2>\n<p>Includes work just as for <a href=\"#whole-dynamic-multiscope-includes\">whole configurations</a>, but without purging the directory.</p>\n<h2>The Augeas Type <a name=\"partial-augeas\"></a></h2>\n<p>If no Augeasproviders exists for your resource type, but Augeas has an <a href=\"https://augeas.net/stock_lenses.html\">available lens</a> for your configuration format, then you can most likely use the <a href=\"https://puppet.com/docs/puppet/5.5/types/augeas.html\"><code>augeas</code> resource type</a> to manipulate it.</p>\n<p>This is often used to manipulate XML configurations for example:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">augeas {&#39;foo.xml&#39;:\n  incl    =&gt; &#39;/tmp/foo.xml&#39;,\n  context =&gt; &#39;/files/tmp/foo.xml/foo&#39;,\n  lens    =&gt; &#39;Xml.lns&#39;,\n  changes =&gt; [\n    &#39;set bar/#text herp&#39;,\n  ],\n}</code>\n        </deckgo-highlight-code>\n<h2>File_line <a name=\"partial-fileline\"></a></h2>\n<p>I've kept <code>file_line</code> for the end of this list, because this is really the last option you might want to consider (just like <code>exec</code>) since has many downfalls.</p>\n<p>However, if you got this far, you're probably either:</p>\n<ul>\n<li>trying to patch a packaged software, which is a very nasty thing to do; it's much better to repackage it properly (and send the patch to the maintainer 😁, that's how Open-Source works!)</li>\n<li>edit a weird configuration file such as <code>.bashrc</code> (which the <code>shellvar</code> type usually parses rather fine) or some kind of PHP or Perl configuration… I don't envy you if you have no option of using templates/concat for that!</li>\n</ul>\n<h1>Conclusion <a name=\"conclusion\"></a></h1>\n<p>There are many tools to manage files in Puppet.</p>\n<p>Do you have other modules/resource types you like to use for this? Let me know in the comments!</p>"},{"url":"/posts/20200610-configuration-surgery-with-go-structure-tags-12a4/","relativePath":"posts/20200610-configuration-surgery-with-go-structure-tags-12a4.md","relativeDir":"posts","base":"20200610-configuration-surgery-with-go-structure-tags-12a4.md","name":"20200610-configuration-surgery-with-go-structure-tags-12a4","frontmatter":{"title":"Configuration surgery with Go structure tags","template":"post","date":"2020-06-10T20:55:35Z","excerpt":"Narcissus is a reflection library letting you edit configuration files in Go","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp9blragy3jsbexp72cs0.jpg","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp9blragy3jsbexp72cs0.jpg","canonical_url":"https://dev.to/raphink/configuration-surgery-with-go-structure-tags-12a4","devto_url":"https://dev.to/raphink/configuration-surgery-with-go-structure-tags-12a4"},"html":"<p>From Docker to Kubernetes, from Consul to Terraform, Go has been used increasingly in system tools these last years.</p>\n<p>Since most of these system tools manage systems running on Unix systems, one of their core tasks is to deal with files, and <a href=\"https://dev.to/camptocamp-ops/how-to-manage-files-with-puppet-55e4\">configuration files in particular</a>.</p>\n<h1>Augeas: the configuration management scalpel</h1>\n<p><a href=\"https://augeas.net/\">Augeas</a> is a C library to modify configuration files. It allows to parse files with many different syntax (over 300 by default), modify the configuration using a tree accessed with an XPath-like language, and write back the configuration.</p>\n<p>It tries hard to modify only what you mean to, keeping all details (spaces, indentations, new lines, comments) unchanged.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/xij7ocuklz6agg2rm95h.png\" alt=\"Augeas\"></p>\n<p>Because of its history, Augeas is mainly known in the Puppet world. However, there are also plugins for <a href=\"https://github.com/paluh/ansible-augeas\">Ansible</a>, <a href=\"https://github.com/nhuff/chef-augeas\">Chef</a>, <a href=\"https://docs.saltstack.com/en/latest/ref/states/all/salt.states.augeas.html\">SaltStack</a>, <a href=\"https://github.com/RexOps/rex-augeas\">(R)?ex</a> and more tools… Augeas is also used directly in C libraries such as libvirt and Nut.</p>\n<h1>Augeasproviders</h1>\n<p>In the Puppet world, the <a href=\"http://augeasproviders.com/\">Augeasproviders project</a> was created to develop native Puppet types and providers (in Ruby) based on Augeas.</p>\n<p>These providers use the Augeas Ruby bindings to draw on Augeas' power, all the while providing a simple interface for users, without the need to know how Augeas works.</p>\n<p>At the core of the Augeasproviders project, there is a base provider shipped in the <a href=\"https://github.com/hercules-team/augeasproviders_core\">hearculesteam-augeasproviders_core</a> Puppet module, which provides an interface to build more providers, in a declarative way.</p>\n<p>For example, you can set the location of the node corresponding to the Puppet resource to manage in the Augeas tree:</p>\n<deckgo-highlight-code language=\"ruby\"  >\n          <code slot=\"code\">resource_path do |resource|\n  service = resource[:service]\n  type = resource[:type]\n  mod = resource[:module]\n  control_cond = (resource[:control_is_param] == :true) ? &quot;and \n  control=&#39;#{resource[:control]}&#39;&quot; : &#39;&#39;\n  if target == &#39;/etc/pam.conf&#39;\n    &quot;$target/*[service=&#39;#{service}&#39; and type=&#39;#{type}&#39; and module=&#39;#{mod}&#39; #{control_cond}]&quot;\n  else\n    &quot;$target/*[type=&#39;#{type}&#39; and module=&#39;#{mod}&#39; #{control_cond}]&quot;\n  end\nend</code>\n        </deckgo-highlight-code>\n<p>The <code>create</code> and <code>destroy</code> methods, as well as the getters and setters for the Puppet resource properties, can also be described in a similar fashion, making it simpler to <a href=\"https://github.com/hercules-team/augeasproviders/blob/master/docs/development.md\">develop new providers based on Augeas</a>.</p>\n<h1>Go bindings</h1>\n<p>As for many other languages, there are Go bindings for Augeas: </p>\n<p><a href=\"https://github.com/dominikh/go-augeas\">GitHub — dominikh/go-augeas</a></p>\n<p>Much like the Ruby bindings, the go library lets you manipulate an Augeas handler to query the Augeas tree, modify it, and save it.</p>\n<h1>Go structure tags</h1>\n<p>In the Go world, structures have optional tags which can be used for parsing and writing to external formats.</p>\n<p>This is used to reflect structures as JSON, YAML, XML, or specify library options to manage the structure fields: </p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">// Version is an S3 bucket version\ntype Version struct {\n  ID           uint      `sql:&quot;AUTO_INCREMENT&quot; gorm:&quot;primary_key&quot; json:&quot;-&quot;`\n  VersionID    string    `gorm:&quot;index&quot; json:&quot;version_id&quot;`\n  LastModified time.Time `json:&quot;last_modified&quot;`\n}</code>\n        </deckgo-highlight-code>\n<p>They are also used to build program interfaces by <a href=\"https://github.com/jessevdk/go-flags\">specifying configuration options</a>:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">type config struct {\n  Version        bool     `short:&quot;V&quot; long:&quot;version&quot; description:&quot;Display version.&quot;`\n  Token          string   `short:&quot;t&quot; long:&quot;token&quot; description:&quot;GitHub token&quot; env:&quot;GITHUB_TOKEN&quot;`\n  Users          []string `short:&quot;u&quot; long:&quot;users&quot; description:&quot;GitHub users to include (comma separated).&quot; env:&quot;GITHUB_USERS&quot; env-delim:&quot;,&quot;`\n  Manpage        bool     `short:&quot;m&quot; long:&quot;manpage&quot; description:&quot;Output manpage.&quot;`\n}</code>\n        </deckgo-highlight-code>\n<p>The tags above (<code>sql</code>, <code>gorm</code>, <code>json</code>, <code>short</code>, <code>long</code>, <code>description</code>, <code>env</code>, <code>env-delim</code>) are used by Go libraries through the <a href=\"https://golang.org/pkg/reflect/\">Go reflection library</a> to provide dynamic features for structures.</p>\n<h1>Narcissus: Augeasproviders for the Go world</h1>\n<p>While Hercules is known in Greek mythology for his works —including cleaning the stables of King Augeas—, Narcissus is famous for gazing at his reflection in the water.</p>\n<p><a href=\"https://github.com/raphink/narcissus\">GitHub — raphink/narcissus</a></p>\n<p>The Narcissus project is a Go library providing structure tags to manage configuration files with Augeas. It then maps structure tags to the Augeas tree dynamically, allowing you to expose any configuration file (or file stanza) known to Augeas as a Go structure.</p>\n<h2>Example of <code>/etc/group</code></h2>\n<p>The Unix <code>group</code> file is very simple and well-known. It features one group per line, with fields separated by colons:</p>\n<deckgo-highlight-code   highlight-lines=\"undefined\">\n          <code slot=\"code\">root:x:0:\ndaemon:x:1:\nbin:x:2:\nsys:x:3:\nadm:x:4:syslog,raphink</code>\n        </deckgo-highlight-code>\n<h3>Parsing with Augeas</h3>\n<p>Augeas parses it by storing each group name as a node key in the tree, and exposing each field by its name:</p>\n<deckgo-highlight-code language=\"console\"  >\n          <code slot=\"code\">$ augtool print /files/etc/group\n/files/etc/group\n/files/etc/group/root\n/files/etc/group/root/password = &quot;x&quot;\n/files/etc/group/root/gid = &quot;0&quot;\n/files/etc/group/daemon\n/files/etc/group/daemon/password = &quot;x&quot;\n/files/etc/group/daemon/gid = &quot;1&quot;\n/files/etc/group/bin\n/files/etc/group/bin/password = &quot;x&quot;\n/files/etc/group/bin/gid = &quot;2&quot;\n/files/etc/group/sys\n/files/etc/group/sys/password = &quot;x&quot;\n/files/etc/group/sys/gid = &quot;3&quot;\n/files/etc/group/adm\n/files/etc/group/adm/password = &quot;x&quot;\n/files/etc/group/adm/gid = &quot;4&quot;\n/files/etc/group/adm/user[1] = &quot;syslog&quot;\n/files/etc/group/adm/user[2] = &quot;raphink&quot;</code>\n        </deckgo-highlight-code>\n<p>Modifying any of these fields and saving the tree will result in an updated <code>/etc/group</code> file. Adding new entries in the tree will result in additional entries in <code>/etc/group</code>, provided the tree is valid for the <code>Group.lns</code> Augeas lens.</p>\n<h2>Parsing with Narcissus</h2>\n<p>In our Go code, we can map a <code>group</code> structure to entries in the <code>/etc/group</code> file easily by using the Narcissus package:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">import (\n\t&quot;log&quot;\n\n\t&quot;honnef.co/go/augeas&quot;\n\t&quot;github.com/raphink/narcissus&quot;\n)\n\ntype group struct {\n\taugeasPath string\n\tName       string   `narcissus:&quot;.,value-from-label&quot;`\n\tPassword   string   `narcissus:&quot;password&quot;`\n\tGID        int      `narcissus:&quot;gid&quot;`\n\tUsers      []string `narcissus:&quot;user&quot;`\n}\n\nfunc main() {\n\taug, err := augeas.New(&quot;/&quot;, &quot;&quot;, augeas.None)\n\tif err != nil {\n\t\tlog.Fatal(&quot;Failed to create Augeas handler&quot;)\n\t}\n\tn := narcissus.New(&amp;aug)\n\n\tgroup := &amp;group{\n\t\taugeasPath: &quot;/files/etc/group/docker&quot;,\n\t}\n\terr = n.Parse(group)\n\tif err != nil {\n\t\tlog.Fatalf(&quot;Failed to retrieve group: %v&quot;, err)\n\t}\n\n\tlog.Printf(&quot;GID=%v&quot;, group.GID)\n\tlog.Printf(&quot;Users=%v&quot;, strings.Join(group.Users, &quot;,&quot;))\n}</code>\n        </deckgo-highlight-code>\n<p>The <code>augeasPath</code> field is necessary to store the location of the file in the Augeas tree, in our case <code>/files/etc/group/docker</code> to manage the <code>docker</code> group in the file.</p>\n<p>Then each structure field is linked to the corresponding node name in the Augeas tree:</p>\n<ul>\n<li>Name is taken from the node label, so we use the special value <code>.,value-from-label</code>, where <code>.</code> refers to the current node, and <code>value-from-label</code> tells Narcissus how to get the value</li>\n<li><code>password</code> for the Password</li>\n<li><code>gid</code> for the GID</li>\n<li><code>user</code> for the Users, parsed as a slice of strings (i.e. the <code>user</code> label might appear more than once in the Augeas tree)</li>\n</ul>\n<p>Note that all fields must be capitalized in order for Go reflection to work.</p>\n<p>Once we call the <code>Parse()</code> method on the Narcissus handler, the structure is dynamically filled with the values in the Augeas tree, so we can access the gid with <code>group.GID</code> and the users with <code>group.Users</code>.</p>\n<h2>Modifying files</h2>\n<p>The main point of the Augeas library is not just to parse, but also to modify configuration files in a versatile way.</p>\n<p>In Narcissus, this is done by calling the <code>Write()</code> method on the Narcissus handler. Narcissus then transforms the structure back to the Augeas tree and saves it.</p>\n<p>For example, using the <code>PasswdUser</code> type provided by default in the <code>narcissus</code> package:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">user := n.NewPasswdUser(&quot;raphink&quot;)\n\n// Modify UID\nuser.UID = 42\n\nif err := n.Write(user); err != nil {\n  log.Fatalf(&quot;Failed to save user: %v&quot;, err)\n}</code>\n        </deckgo-highlight-code>\n<h2>Included formats</h2>\n<p>Narcissus comes with a few structures already mapped:</p>\n<ul>\n<li><code>/etc/fstab</code>, with the <code>NewFstab()</code> method</li>\n<li><code>/etc/hosts</code> with the <code>NewHosts()</code> method</li>\n<li><code>/etc/passwd</code> with <code>NewPasswd()</code> and <code>NewPasswdUser()</code> methods</li>\n<li><code>/etc/services</code> with <code>NewServices()</code> and <code>NewService()</code> methods</li>\n</ul>\n<p>Which structures will you map with it? Which tool could benefit from this library?</p>\n<p>Let me know in the comments!</p>"},{"url":"/posts/20200610-visibility-comments-b65/","relativePath":"posts/20200610-visibility-comments-b65.md","relativeDir":"posts","base":"20200610-visibility-comments-b65.md","name":"20200610-visibility-comments-b65","frontmatter":{"title":"How to encourage interaction on dev.to posts?","template":"post","date":"2020-06-10T19:19:35Z","excerpt":"After a few years of being inactive on dev.to, I've started actively posting about a month ago.  I...","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw14l9yddz1nqrnvo3xfe.jpg","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw14l9yddz1nqrnvo3xfe.jpg","canonical_url":"https://dev.to/raphink/visibility-comments-b65","devto_url":"https://dev.to/raphink/visibility-comments-b65"},"html":"<p>After a few years of being inactive on dev.to, I've started actively posting about a month ago.</p>\n<p>I see quite a few visits to some of my posts (up to nearly 400 views for some), but I get absolutely no comments (unless it's a small post with the <code>#discuss</code> tag).</p>\n<p>Is that normal? Is there a way on dev.to to drive more reactions/interactions? Is it the subject (mainly DevOps/SRE-related) that is not core to this platform?</p>\n<p>Am I missing something?</p>"},{"url":"/posts/20200612-add-puppet-tag-142l/","relativePath":"posts/20200612-add-puppet-tag-142l.md","relativeDir":"posts","base":"20200612-add-puppet-tag-142l.md","name":"20200612-add-puppet-tag-142l","frontmatter":{"title":"Add #puppet tag","template":"post","date":"2020-06-12T06:08:13Z","excerpt":"It would be great to attract more DevOps-related content to dev.to. With a few other people, I've...","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","canonical_url":"https://dev.to/raphink/add-puppet-tag-142l","devto_url":"https://dev.to/raphink/add-puppet-tag-142l"},"html":"<p>It would be great to attract more DevOps-related content to dev.to. With a few other people, I've started blogging about Puppet and would really appreciate if it could become an official tag!</p>"},{"url":"/posts/20200702-github-sponsors-and-dev-to-posts-51b1/","relativePath":"posts/20200702-github-sponsors-and-dev-to-posts-51b1.md","relativeDir":"posts","base":"20200702-github-sponsors-and-dev-to-posts-51b1.md","name":"20200702-github-sponsors-and-dev-to-posts-51b1","frontmatter":{"title":"💡 GitHub Sponsors and dev.to posts","template":"post","date":"2020-07-02T05:52:51Z","excerpt":"GitHub Sponsors could be leveraged on dev.to to generate revenue","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqdp4swhb2kngx4cmsowd.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqdp4swhb2kngx4cmsowd.png","canonical_url":"https://dev.to/raphink/github-sponsors-and-dev-to-posts-51b1","devto_url":"https://dev.to/raphink/github-sponsors-and-dev-to-posts-51b1"},"html":"<p>Yesterday, I read <a href=\"https://calebporzio.com/i-just-hit-dollar-100000yr-on-github-sponsors-heres-how-i-did-it\">a blog post by Caleb Porzio</a> about making money from Open Source projects, in particular by leveraging the GitHub sponsors program.</p>\n<p>His conclusion is that the best way to use GitHub Sponsors is to make both free and paid educational content, such that free content attracts an audience, and then they want to support you to access the advanced, paid, content.</p>\n<p>It seems to me that the DEV community, being already connected to GitHub, would be a perfect place to implement this idea.</p>\n<p>There could be a tag in the meta parameters of a post, which would restrict its access (fully or partly, e.g. by hiding the end of the article like lots of newspapers do) to the author's GitHub sponsors:</p>\n<deckgo-highlight-code   highlight-lines=\"undefined\">\n          <code slot=\"code\"># Restrict to sponsors of the raphink GitHub account\nrestrict_sponsors: raphink\n# Restrict to sponsors of tier $10 or above\nrestrict_sponsors: raphink/10</code>\n        </deckgo-highlight-code>\n<p>What do you think? Is that a feature that would be interesting for the DEV community?</p>"},{"url":"/posts/20200629-enhance-and-colorize-old-pictures-5c9g/","relativePath":"posts/20200629-enhance-and-colorize-old-pictures-5c9g.md","relativeDir":"posts","base":"20200629-enhance-and-colorize-old-pictures-5c9g.md","name":"20200629-enhance-and-colorize-old-pictures-5c9g","frontmatter":{"title":"Enhance, Colorize, and Animate Old Pictures","template":"post","date":"2020-06-29T16:45:38Z","excerpt":"MyHeritage in Color allows to fine-tune automatically colorized and enhanced photographs","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb78xjnsl62d9by6bkyoh.jpg","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb78xjnsl62d9by6bkyoh.jpg","canonical_url":"https://dev.to/raphink/enhance-and-colorize-old-pictures-5c9g","devto_url":"https://dev.to/raphink/enhance-and-colorize-old-pictures-5c9g"},"html":"<p>Over the last 2 years, Machine Learning has brought impressive breakthrough to image processing techniques. In particular, photography colorization has seen amazing progress, thanks mainly to the work of two developers: <a href=\"https://twitter.com/citnaj\">Jason Antic</a> and <a href=\"https://twitter.com/danasday\">Dana Kelley</a>.</p>\n<h2>MyHeritage in Color</h2>\n<p>Their model is so precise that MyHeritage hired them to include a colorization tool directly on their website. As a result, you can colorize pictures for free at <a href=\"https://www.myheritage.fr/incolor\">https://www.myheritage.fr/incolor</a>. Paid MyHeritage members are not limited in the number colorizations, and don't get the MyHeritage watermark on the result pictures.</p>\n<p>Additionally, MyHeritage recently added a new AI-based feature to this tool, by allowing users to enhance faces in their pictures:</p>\n<p><a href=\"https://twitter.com/i/web/status/1276608740884721665\">Tweet</a></p>\n<p>Using the tool from your MyHeritage picture collection is extremely simple. You just need to upload a picture and use the \"enhance\" and \"colorize\" buttons one after the other.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/wt2uwlf0t0dhu2w53w91.png\" alt=\"Colorization and Enhance tools\"></p>\n<h2>Tuning colorization</h2>\n<p>In the last few weeks, I've seen lots of people using MyHeritage In Color on their pictures, and they're usually quite content with the result. However, most of them have no idea they could get even better results by tuning the rendering.</p>\n<p>Once a picture is colorized, a gear icon appears to let you fine-tune the result:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/4prlyuwyikgo0qhi9v69.png\" alt=\"Tuning gear icon\"></p>\n<p>There are 4 parameters which can be tuned using this icon:</p>\n<ul>\n<li>Contrast enhancement: leave it checked usually. It is mainly useful if your input picture is already very contrasted, which is not the case of most pictures.</li>\n<li>Saturation: use this to keep the same colors, but make them \"stronger\"</li>\n<li>Automatic vs Manual rendering: this allows you to set a rendering factor manually. Generally, a lower factor brings more colors, but also more colorization \"mistakes\" (such a purple zones)</li>\n<li>Colorization model: this lets you use an alternate model, which at times looks better.</li>\n</ul>\n<h2>General observations</h2>\n<p>Generally speaking, this is what I have found from colorizing hundreds of pictures, essentially portraits of people:</p>\n<ul>\n<li>If clothes (in particular dark ones) have purple spots over them but the faces are rendered well, try switching to the \"Alternative\" model</li>\n<li>If faces are colorized properly but legs are grey, try the \"Alternative\" model as well</li>\n<li>If the colors are fine, but the colors aren't coming through strong enough, increase the saturation</li>\n<li>If the colors are too grey-ish (especially with low-res pictures), try reducing the rendering factor to 16.</li>\n<li>If the picture is high-res enough and renders rather well but has uncolorized spots, you can try increasing the rendering factor. Note that rendering will generally be longer with a higher factor.</li>\n</ul>\n<p>Here is an example:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/of5je7h1ou8nd48dtugh.png\" alt=\"Default settings\"></p>\n<p>This colorized (and enhanced) picture doesn't look bad, but the clothes have lots of purple spots all over them. Switching to the alternative model reduces this, without affecting the faces too much:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/epmwc8x4r4zp90nu6jju.png\" alt=\"Alternative model\"></p>\n<p>There's still some weird zones though, so I want to try and reduce these by increasing the rendering factor. After playing around, I ended up with a factor of 64 for that picture:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/sqv6a9c7s7rrb2gtb9l1.png\" alt=\"Render factor\"></p>\n<p>However, the colors are getting slightly dim now, so I've increased the saturation to 1.2 to counteract that:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/l6ttuj25eh7jxtzolp28.png\" alt=\"Saturation\"></p>\n<p>Granted, the results on this picture are far from perfect, as it is hard to colorize (which is why I chose it!).</p>\n<h2>Methodology</h2>\n<p>When tuning a colorization, every time you modify a parameter, you can preview the changes, and then choose to save them.</p>\n<p>In a very practical fashion, MyHeritage lets you click on the colorized picture to compare with the previously saved version.</p>\n<p>However, it doesn't save the results for each combination, so when you play around with parameters, you can end up waiting a long time to go back to previous combinations.</p>\n<p>For this reason, I use A/B testing when colorizing. My process is:</p>\n<ol>\n<li>Colorize the picture a first time</li>\n<li>Open the tuning interface</li>\n<li>Tune one parameter, preview and compare</li>\n<li>If the result is better than the previously saved picture, save it</li>\n<li>Repeat from step 2 until you can't improve the result</li>\n</ol>\n<p>The drawback of this method is that you need to re-open the tuning interface every time, but in the end, you'll save lots of rendering time and you know you're saving the best version you could get.</p>\n<h2>Fixing other color issues</h2>\n<p>While the settings in MyHeritage in Color clearly help, they can't —yet— get you to a perfect result most of the time.</p>\n<p>In many cases, some parameters will have better results on people, while others will improve objects or the background.</p>\n<p>One thing I've started doing is downloading multiple versions of the colorization, importing them all as layers in Gimp, and cutting them so as to get the best result in every zone.</p>\n<p>In other cases, the tint on some objects may not be perfect despite trying to tune the engine. In these cases, you can duplicate these zones in Gimp and tune their tint/saturation/hue individually until you get the result you want.</p>\n<h2>Improving Face Enhancing</h2>\n<p>Face enhancing can lead to very impressive results by recreating realistic faces that match the shadow of faces in the blur pictures.</p>\n<p>For example, in the picture I used before, some of the children's faces look amazing:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/2svy7z72gnqo6cl90oql.jpg\" alt=\"Face enhancement and colorization\"></p>\n<p>Yes, the original is on the left!</p>\n<p>At times however, little dots or other issues with the original picture can lead the face enhancer astray and generate some very disturbing results. Here for example, there's a line on the face in the original picture, which gets \"interpreted\" as a scar:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/i8d5qjc1ysxlegxooylj.jpg\" alt=\"Scar mouth\"></p>\n<p>And in this one, the left eye looks too dark in the original picture, leading to a strange color dissymetry:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/dbzchaqre1c8eyy3zc6e.jpg\" alt=\"Eye color dissymetry\"></p>\n<p>Fortunately, these aren't too hard to fix. In the case of the \"scar\", erasing it (using Gimp with simple editing methods such as the stamp tool) fixed the issue:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/b78xjnsl62d9by6bkyoh.jpg\" alt=\"Fixed scar mouth\"></p>\n<p>The eyes weren't much harder to fix. Since the face is straight, I just copied the right pupil (just the pupil, not the entire eye) and pasted it on the left eye. The difference is hardly noticeable in the B&#x26;W picture, but it's enough for the AI algorithm to pick it up properly:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/2xxhxugvmo4wqnxdj2qa.jpg\" alt=\"Fixed eye color\"></p>\n<h2>Edit (2021-02-26): Animate the Portrait</h2>\n<p>As of February 2021, MyHeritage now allows to animate portraits that were enhanced.</p>\n<p>From the picture view, simply click the animation button and choose the face to animate. You can choose from 10 different animations. If the face was colorized, the animation will honor it.</p>\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/JVm5dEa9VlY\" title=\"YouTube video\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n<h2>How about you?</h2>\n<p>Have you colorized pictures with DeOldify or MyHeritage in Color? Have you found useful ways to get good results? Share them in the comments!</p>"},{"url":"/posts/20200721-templating-puppet-control-repositories-3pk7/","relativePath":"posts/20200721-templating-puppet-control-repositories-3pk7.md","relativeDir":"posts","base":"20200721-templating-puppet-control-repositories-3pk7.md","name":"20200721-templating-puppet-control-repositories-3pk7","frontmatter":{"title":"Templating Puppet Control Repositories","template":"post","date":"2020-07-21T08:45:28Z","excerpt":"When managing multiple Puppet Control Repositories, modulesync is a very useful tool to keep files in sync.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","canonical_url":"https://dev.to/camptocamp-ops/templating-puppet-control-repositories-3pk7","devto_url":"https://dev.to/camptocamp-ops/templating-puppet-control-repositories-3pk7"},"html":"<p>Puppet code is usually deployed using a <a href=\"https://github.com/puppetlabs/control-repo\">Control Repository</a>, a single Git repository used by R10k (or Code Manager on Puppet Enterprise) to manage Puppet environments on Puppet Masters.</p>\n<h1>Why multiple Control Repositories?</h1>\n<p>On complex infrastructures with multiple independent Puppet Masters, you might have the need to use multiple control repositories. For example at Camptocamp, we have specific clients with enough nodes to have their own Puppet infrastructure each.</p>\n<p>For these clients, we do not want to use a shared Puppet Control Repository. However, we do want to keep the code as similar as possible between the infrastructures, and make sure some parameters and settings (admin accounts, ssh keys, etc.) are synchronized.</p>\n<h1>Modulesync to the rescue</h1>\n<p>Modulesync is a piece of software initially created by Puppet Inc. to synchronize files between Git repositories for Puppet modules. Nowadays, this feature is being served by PDK for Puppet modules, so modulesync is now <a href=\"https://github.com/voxpupuli/modulesync/\">managed by the Vox Pupuli community</a>.</p>\n<p>For years, we have been using it at Camptocamp to keep our Control Repositories synchronized.</p>\n<p>In order to achieve this, we use a template repository, which we call <code>puppetmaster-common</code>. </p>\n<p>Each of our clients has their own GitLab instance with their Puppet Control Repository, and this template repository brings it all together. </p>\n<p>This repository is set as follows.</p>\n<h2>modulesync.yml</h2>\n<p>This file contains the general settings for modulesync:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">---\n# default namespace in GitLab instances\nnamespace: &#39;camptocamp&#39;\n# Branch to synchronize\nbranch: &#39;msync&#39;\n# Default Merge Request title\npr_title: &#39;Modulesync [autodiff]&#39;\n# Default Merge Request target branch\npr_target_branch: &#39;staging&#39;</code>\n        </deckgo-highlight-code>\n<p>On all our Control Repositories, we have locked the <code>stable</code> and <code>staging</code> branches to prevent pushes to them. This forces us to create Merge Requests for new features, ensuring quality <a href=\"https://dev.to/camptocamp-ops/automated-puppet-impact-analysis-1c1\">through our CI pipeline</a>.</p>\n<p>For this reason, we use a separate branch, called <code>msync</code>, to perform the synchronizations.</p>\n<h2>managed_modules.yml</h2>\n<p>Since we use several GitLab instances and we want to be able to automate Merge Request creation, this file contains GitLab API URLs and tokens per managed Control Repository. It looks similar to this:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">puppetmaster-c2c:\n  :remote: &#39;ssh://git@gitlab1/camptocamp/is/puppet/puppetmaster-c2c.git&#39;\n  :namespace: &#39;camptocamp/is/puppet&#39;\n  :gitlab:\n    :token: &#39;abc123def456&#39;\n    :base_url: &#39;https://gitlab1/api/v4&#39;\n\npuppetmaster-client1:\n  :remote: &#39;ssh://git@gitlab-client1/puppet/puppetmaster-client1.git&#39;\n  :namespace: &#39;puppet&#39;\n  :gitlab:\n    :token: &#39;someOtherToken&#39;\n    :base_url: &#39;https://gitlab-client1/api/v4&#39;</code>\n        </deckgo-highlight-code>\n<h2>moduleroot</h2>\n<p>The <code>moduleroot</code> directory contains the files we want to synchronize, as ERB templates. In our case:</p>\n<deckgo-highlight-code   highlight-lines=\"undefined\">\n          <code slot=\"code\">moduleroot/\n├── doc\n│   ├── architecture.md.erb\n│   └── before_after.md.erb\n├── environment.conf.erb\n├── Gemfile.erb\n├── .gitignore.erb\n├── .gitlab-ci.yml.erb\n├── hieradata\n│   └── cross-site\n│       ├── common-cross-site.yaml.erb\n│       ├── README.md.erb\n│       ├── .travis.yml.erb\n│       └── verify-key-length.erb\n├── hiera-eyaml-gpg.recipients.erb\n├── Puppetfile.erb\n├── .puppet-lint.rc.erb\n├── Rakefile.erb\n├── README.md.erb\n└── scripts\n    ├── bolt.erb\n    ├── docker.erb\n    ├── node_deactivate.erb\n    ├── puppetca.erb\n    └── puppet-query.erb</code>\n        </deckgo-highlight-code>\n<p>A few notes here on the files here.</p>\n<h3>Static files</h3>\n<p>Most of these files (e.g. the scripts, <code>Gemfile</code>, or <code>environment.conf</code>) are actually static, but they need to be named <code>.erb</code> nonetheless, otherwise <code>modulesync</code> will ignore them.</p>\n<h3>hiera-eyaml-gpg.recipients</h3>\n<p><code>hiera-eyaml-gpg.recipients.erb</code> works essentially as a filter on the <code>hiera-eyaml-gpg.recipients</code> file at the top of the repository, taking every admin key, as well as one <code>puppet@</code> key specified in the <code>.sync.yml</code> of the control repository with the <code>master_gpg_key</code> setting:</p>\n<deckgo-highlight-code language=\"erb\"  >\n          <code slot=\"code\">&lt;%=\nbasedir = File.expand_path(&#39;..&#39;, File.dirname(__FILE__))\nrecipients_file = File.expand_path(File.join(basedir, &#39;hiera-eyaml-gpg.recipients&#39;))\n\nFile.readlines(recipients_file).map { |l|\n  r = l.strip\n  if r =~ /^puppet@/\n    r if @configs[&#39;master_gpg_key&#39;] == r\n  else\n    r\n  end\n}.compact.join(&quot;\\n&quot;)\n%&gt;</code>\n        </deckgo-highlight-code>\n<h3>Puppetfile</h3>\n<p>Similar to <code>hiera-eyaml-gpg.recipients</code>, <code>Puppetfile</code> is managed as a filter. We keep a full <code>Puppetfile</code> at the top of the repository, with all the modules we use on all Puppet Infrastructures, and the default versions we want. Then each Control Repository can pick which module to include and optionally override versions.</p>\n<p>The <code>Puppetfile.erb</code> template uses Augeas to cleanly filter and rewrite the target <code>Puppetfile</code>:</p>\n<deckgo-highlight-code language=\"erb\"  >\n          <code slot=\"code\">###############################################\n# This file is managed in puppetmaster-common #\n# Do not edit locally                         #\n###############################################\n\n&lt;%= require &#39;augeas&#39;\nbasedir = File.expand_path(&#39;..&#39;, File.dirname(__FILE__))\nbase_pf = File.expand_path(File.join(basedir, &#39;Puppetfile&#39;))\nbase_pf_content = File.read(base_pf)\nlens_dir = File.expand_path(File.join(basedir, &#39;lenses&#39;))\n\ndef mod_regexp(name)\n  &quot;*[label()!=&#39;#comment&#39; and .=~regexp(&#39;([^/-]+[/-])?#{name}&#39;)]&quot;\nend\n\nAugeas.open(nil, lens_dir, Augeas::NO_MODL_AUTOLOAD) do |aug|\n  aug.set(&#39;/input&#39;, base_pf_content)\n  unless aug.text_store(&#39;Puppetfile.lns&#39;, &#39;/input&#39;, &#39;/parsed&#39;)\n      msg = aug.get(&#39;/augeas//error&#39;)\n      fail &quot;Failed to parse common Puppetfile: #{msg}&quot;\n  end\n  aug.set(&#39;/augeas/context&#39;, &#39;/parsed&#39;)\n  all_modules = aug.match(&#39;*[label()!=&quot;#comment&quot;]&#39;).map { |m| aug.get(m).split(%r{[/-]}).last }\n\n  whitelist = @configs[&#39;modules&#39;].keys if @configs[&#39;modules&#39;]\n  not_in_all = whitelist - all_modules if whitelist\n  fail &quot;Module(s) #{not_in_all.join(&#39;, &#39;)} not found in common Puppetfile&quot; if not_in_all and !not_in_all.empty?\n\n  # Remove unnecessary modules\n  (all_modules - whitelist).each do |m|\n    aug.rm(mod_regexp(m))\n  end if whitelist\n\n  # Amend\n  modified = @configs[&#39;modules&#39;].reject { |m, v| v.nil? } if @configs[&#39;modules&#39;]\n  modified.each do |m, c|\n    aug.set(mod_regexp(m), &quot;#{c[&#39;user&#39;]}/#{m}&quot;) if c[&#39;user&#39;]\n    if c[&#39;version&#39;]\n      aug.rm(&quot;#{mod_regexp(m)}/git&quot;)\n      aug.rm(&quot;#{mod_regexp(m)}/ref&quot;)\n      aug.set(&quot;#{mod_regexp(m)}/@version&quot;, c[&#39;version&#39;])\n    else\n      aug.rm(&quot;#{mod_regexp(m)}/@version&quot;)\n      aug.set(&quot;#{mod_regexp(m)}/git&quot;, c[&#39;git&#39;]) if c[&#39;git&#39;]\n      aug.set(&quot;#{mod_regexp(m)}/ref&quot;, c[&#39;ref&#39;]) if c[&#39;ref&#39;]\n    end\n  end if modified\n\n  aug.text_retrieve(&#39;Puppetfile.lns&#39;, &#39;/input&#39;, &#39;/parsed&#39;, &#39;/output&#39;)\n  unless aug.match(&#39;/augeas/text/parsed/error&#39;).empty?\n    fail &quot;Failed to generate Puppetfile: #{aug.get(&#39;/augeas/text/parsed/error&#39;)}\n  #{aug.get(&#39;/augeas/text/parsed/error/message&#39;)}&quot;\n  end\n  aug.get(&#39;/output&#39;)\nend -%&gt;</code>\n        </deckgo-highlight-code>\n<h3>.gitlab-ci.yml.erb</h3>\n<p>This file defines the CI/CD pipelines for our Control Repositories, extending our <a href=\"https://github.com/camptocamp/puppet-gitlabci-pipelines\">generic Puppet pipelines rules</a>. It takes variables to control catalog-diff.</p>\n<h3>cross-site hieradata</h3>\n<p>The cross-site hieradata level contains common system accounts with their UID, shell &#x26; SSH key. We then use <a href=\"https://forge.puppet.com/camptocamp/accounts\">our accounts module</a> to deploy these accounts. </p>\n<h1>Sample .sync.yml</h1>\n<p>Each Control Repository features a <code>.sync.yml</code> file to provide overrides for variables. Here's an example:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">---\nRakefile:\n  master_gpg_key: &#39;puppet@client1&#39;\n.gitlab-ci.yml:\n  puppetdb_urls: &#39;https://puppetdb.client1.ch&#39;\n  puppet_server: &#39;puppet.client1.ch&#39;\n  puppetdiff_url: &#39;https://puppetdiff.client1.ch&#39;\nPuppetfile:\n  modules:\n    # include accounts module, with default version\n    accounts:\n    # include letsencrypt module, override version\n    letsencrypt:\n      git: &#39;https://github.com/saimonn/puppet-letsencrypt&#39;\n      ref: &#39;default_cert_name&#39;</code>\n        </deckgo-highlight-code>\n<h1>Usage</h1>\n<p>Since <code>managed_modules.yml</code> contains secret tokens for the various GitLabs, we don't want to commit it to the Git repository. Instead, the content of this file is stored in <a href=\"https://github.com/gopasspw/gopass\"><code>gopass</code></a> and retrieved dynamically with <a href=\"https://github.com/cyberark/summon\"><code>summon</code></a>.</p>\n<p>In order to use <code>summon</code>, we have a local <code>secrets.yml</code> pointing to the location of the <code>managed_modules.yml</code> file in <code>gopass</code>:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">---\nMSYNC_MANAGED_MODULES: !var:file puppet/msync/managed_modules</code>\n        </deckgo-highlight-code>\n<p>and use a <code>msync_update</code> wrapper to launch <code>modulesync</code>:</p>\n<deckgo-highlight-code language=\"bash\"  >\n          <code slot=\"code\">#!/bin/bash\n\nbundle exec msync update --managed_modules_conf=$MSYNC_MANAGED_MODULES &quot;$@&quot;</code>\n        </deckgo-highlight-code>\n<p>This then allows to test the changes with:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ summon ./msync_update -m &quot;Update module foo&quot; --noop</code>\n        </deckgo-highlight-code>\n<p>and then deploy on a single site (or all without the filter):</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ summon ./msync_update -m &quot;Update module foo&quot; -f c2c --pr</code>\n        </deckgo-highlight-code>\n<p><em>Do you have specific Puppet needs? <a href=\"https://www.camptocamp.com/contact\">Contact us</a>, we can help you!</em></p>"},{"url":"/posts/20200723-decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk/","relativePath":"posts/20200723-decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk.md","relativeDir":"posts","base":"20200723-decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk.md","name":"20200723-decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk","frontmatter":{"title":"Decomissioning with Puppet: report & purge unmanaged resources","template":"post","date":"2020-07-23T14:42:09Z","excerpt":"Puppet can let you purge resources you do not manage explicitely","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fwww.camptocamp.com%2Fwp-content%2Fuploads%2Fxformations_puppet1-720x400.png.pagespeed.ic.UU2oY1Zlj8.webp","canonical_url":"https://dev.to/camptocamp-ops/decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk","devto_url":"https://dev.to/camptocamp-ops/decomissioning-with-puppet-report-purge-unmanaged-resources-1jgk"},"html":"<p>Puppet lets you manage resources explicitely. But did you know you can also dynamically purge unmanaged resources using Puppet?</p>\n<h1>Why?</h1>\n<p>A user in your organization just left, and you need to remove their account from all nodes. If you were managing their account with Puppet —whether with a <code>user</code> resource type or using an <a href=\"https://forge.puppet.com/modules?q=accounts\">accounts module</a>—, you need to make sure this user is absent:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">user { &#39;jdoe&#39;:\n  ensure =&gt; absent,\n}</code>\n        </deckgo-highlight-code>\n<p>Great. Job done. Now, how long should this resource be kept in your code? One hour? One week? One year? What if an old node that was turned off wakes up months from now with this account activated?</p>\n<p>To be honest, if a node turned off for months suddenly wakes up, you'll probably have more issues than just old users if your Puppet code base is quite active…\nHowever, purging all unknown users would be a much easier approach than managing them explicitely!</p>\n<h1>How?</h1>\n<p>As explained <a href=\"https://dev.to/camptocamp-ops/how-to-manage-files-with-puppet-55e4#whole-dynamic-purge\">in a previous post about managing files in Puppet</a>, Puppet has the ability of purging unmanaged resources. I'll let you see the post for more explanations on how this works:</p>\n<p><a href=\"https://dev.to/camptocamp-ops/how-to-manage-files-with-puppet-55e4#whole-dynamic-purge\">https://dev.to/camptocamp-ops/how-to-manage-files-with-puppet-55e4#whole-dynamic-purge</a></p>\n<h1>What if I don't want to purge?</h1>\n<p>What if instead of purging, I'd just like Puppet to report the unmanaged resources but not do anything about them?</p>\n<p>Luckily for us, <code>noop</code> works fine with the <code>purge</code> type, so you can use something like:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">purge { &#39;user&#39;:\n  noop   =&gt; true,\n  unless =&gt; [\n    [&#39;uid&#39;, &#39;&lt;&#39;, &#39;1000&#39;],\n    [&#39;name&#39;, &#39;==&#39;, &#39;nobody&#39;],\n  ],\n}</code>\n        </deckgo-highlight-code>\n<p>This code will mark all users with a UID above 999 (except the <code>nobody</code> user) to be purged, but it won't do it. As a result, you'll get <code>noop</code> resources in your reports, for example in Puppetboard:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/3xu0q5i3e98bv27ih117.png\" alt=\"Noop resources\"></p>\n<p>And then in the report, you'll see the unmanaged users:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/ckpsim9bx6igwp09it4l.png\" alt=\"Report view\"></p>\n<h1>Forcing purge</h1>\n<p>If you see users that should be purged, you can add again a <code>user</code> resource in your Puppet code to ensure their absence:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">user { &#39;iperf&#39;:\n  ensure =&gt; absent,\n}</code>\n        </deckgo-highlight-code>\n<p>Another option is to make it a bit more dynamic. I've added an option in my <code>accounts</code> base class to use a dynamic fact to purge users on demand:</p>\n<deckgo-highlight-code language=\"puppet\"  >\n          <code slot=\"code\">class osbase::accounts (\n  Boolean $purge_users = str2bool($facts[&#39;purge_users&#39;]),\n) {\n  purge { &#39;user&#39;:\n    noop   =&gt; !$purge_users,\n    unless =&gt; [\n      [&#39;uid&#39;, &#39;&lt;&#39;, &#39;1000&#39;],\n      [&#39;name&#39;, &#39;==&#39;, &#39;nobody&#39;],\n    ],\n  }\n}</code>\n        </deckgo-highlight-code>\n<p>The <code>purge_users</code> fact doesn't exist by default, so I can define it on the go when I need to purge users.\nNow I can run <code>puppet apply</code> on a node and force purging the users with:</p>\n<deckgo-highlight-code   highlight-lines=\"undefined\">\n          <code slot=\"code\">$ FACTER_purge_users=y puppet agent -t</code>\n        </deckgo-highlight-code>\n<p>And all unmanaged users will be removed from the node!</p>\n<p><em>Do you have specific Puppet needs? <a href=\"https://www.camptocamp.com/contact\">Contact us</a>, we can help you!</em></p>"},{"url":"/posts/20200728-simple-secret-sharing-with-gopass-and-summon-40jk/","relativePath":"posts/20200728-simple-secret-sharing-with-gopass-and-summon-40jk.md","relativeDir":"posts","base":"20200728-simple-secret-sharing-with-gopass-and-summon-40jk.md","name":"20200728-simple-secret-sharing-with-gopass-and-summon-40jk","frontmatter":{"title":"Simple secret sharing with gopass and summon","template":"post","date":"2020-07-28T16:34:57Z","excerpt":"Storing and sharing secrets doesn't have to be complex","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgl8147l5s2ky6po9tdfi.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgl8147l5s2ky6po9tdfi.png","canonical_url":"https://dev.to/camptocamp-ops/simple-secret-sharing-with-gopass-and-summon-40jk","devto_url":"https://dev.to/camptocamp-ops/simple-secret-sharing-with-gopass-and-summon-40jk"},"html":"<p>Secrets are a fundamental, yet complex issue in software deployment.</p>\n<p>Solutions such as <a href=\"https://www.keepassx.org/\">KeepassX</a> are simple to use, but quite impractical when it comes to automation.</p>\n<p>More complex options like <a href=\"https://www.vaultproject.io/\">Hashicorp Vault</a> are extremely powerful, but harder to set up and maintain.</p>\n<h1>Pass: a simple solution</h1>\n<p>When it comes to storing securely and sharing passwords in a team, it is hard to come up with a more simple and efficient solution than Git and GnuPG combined.</p>\n<p><a href=\"https://www.passwordstore.org/\">Pass</a> is a shell script that does just that. Inside a Git repository, Pass stores passwords in individual files encrypted for all private GnuPG keys in the team. It features a CLI to manipulate passwords, add new entries, or search through existing passwords.</p>\n<h1>More features</h1>\n<p>However, Pass is quite limited in its features, so another project was born a few years later, to provide a new Go implementation of the Pass standard. Its name: simply <a href=\"https://github.com/gopasspw/gopass\">Gopass</a>.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/1cs4zelpc542tzc2ligm.png\" alt=\"Gopass Logo\"></p>\n<h2>Installing</h2>\n<p>Gopass is provided as binaries you can download from the releases page <a href=\"https://github.com/gopasspw/gopass/releases\">on GitHub</a>.</p>\n<h2>Features</h2>\n<p>Here are some of the features that make Gopass a great tool.</p>\n<h3>Multiple mounts</h3>\n<p>While <code>pass</code> allows you to have a single Git repository with your passwords, Gopass lets you create multiple repositories called \"mounts\", which is very useful when you want to share different secrets with different people:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ gopass mounts\ngopass (/home/raphink/.password-store)\n├── c2c (/home/raphink/.password-store-c2c)\n├── perso (/home/raphink/.password-store-perso)\n└── terraform (/home/raphink/.password-store-terraform)</code>\n        </deckgo-highlight-code>\n<p>Gopass uses a prefix to access secrets in mounts, so <code>terraform/puppet/c2c</code> actually refers to the secret stored in <code>/home/raphink/.password-store-terraform/puppet/c2c.gpg</code>.</p>\n<h3>Multiple users</h3>\n<p>Each Git repository can be set to encrypt passwords for multiple GnuPG keys.</p>\n<p>The <code>.gpg-id</code> file at the root of each repository contains the list of public keys to use for encryption, and the <code>.public-keys/</code> directory keeps a copy of each key, making it easy for collaborators to import them into their keyring before they can start encrypting passwords for the team.</p>\n<h3>Fuzzy search</h3>\n<p>Gopass helps you find entries when the key you gave it doesn't match an exact known path:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ gopass jenkins\n[ 0] c2c/c2c_aws/jenkins-c2c\n[ 1] c2c/c2c_mgmtsrv/freeipa/c2c-jenkins-swarm\n[ 2] c2c/c2c_mgmtsrv/freeipa/jenkins-test-users\n[ 3] perso/Devel/jenkins-ci.org\n[ 4] terraform/aws/jenkins-c2c\n\nFound secrets - Please select an entry [0]: </code>\n        </deckgo-highlight-code>\n<h3>Structured secrets</h3>\n<p>When decrypting a password, Gopass parses the content into two different parts: a password and a YAML document. For example, the content of a secret could look like this:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">foo\n---\nkey1: value1\nanother_key:\n  bar: baz</code>\n        </deckgo-highlight-code>\n<h4>Password</h4>\n<p>The first line of the content is the <code>password</code>. If this is all you're interested in, you can use <code>gopass show --password</code> to retrieve it:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ gopass show --password perso/test\nfoo</code>\n        </deckgo-highlight-code>\n<h4>Querying keys</h4>\n<p>When the second part of the content (lines 2 and following) is a valid YAML document, you can query these values by providing a key, for example:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ gopass show perso/test key1\nvalue1</code>\n        </deckgo-highlight-code>\n<p>Starting with Gopass 1.9.3, you can also query subkeys using either a dot or slash notation:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ gopass show perso/test another_key.bar\nbaz\n$ gopass show perso/test /another_key/bar\nbaz</code>\n        </deckgo-highlight-code>\n<p>This makes it extremely powerful to store several fields in the same secret.</p>\n<h3>TOTP</h3>\n<p>Gopass allows to store TOTP keys alongside passwords. For example, you can have the following secret, stored at <code>terraform/service.io/api</code>:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">WPTmU`&gt;b&lt;Y31\n---\npassword: &#39;WPTmU`&gt;b&lt;Y31&#39;\ntotp: &#39;PIJ6AIHETAHSHOO7SHEI1AEK6IH1SOOCHATUOSH8XUAN0OOTH9XAHRUXO4AHJAEVI&#39;\nurl: https://myservice.io\nusername: jdoe</code>\n        </deckgo-highlight-code>\n<p>In addition to retrieving each field with the corresponding key, you can also generate TOTP tokens with <code>gopass totp</code>:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ gopass totp terraform/service.io/api\n568000 lasts 18s \t|------------==================|</code>\n        </deckgo-highlight-code>\n<h2>Integrations</h2>\n<p>Gopass can be easily integrated into projects for deployments or CI/CD tasks.</p>\n<h3>Summon</h3>\n<p>The easiest way to integrate Gopass is probably to use <a href=\"https://github.com/cyberark/summon\">Summon</a>.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/gy0cw2iqwdb85lobl5jl.png\" alt=\"Summon logo\"></p>\n<p>Summon is a tool which dynamically exposes environment variables with values retrieved from various secret stores. <code>gopass</code> is one of its possible providers.</p>\n<h4>Setup</h4>\n<p>Setting it up to use <code>gopass</code> is rather straightforward. We use a simple wrapper called <code>summon-gopass</code>, which needs to be in your PATH:</p>\n<deckgo-highlight-code language=\"bash\"  >\n          <code slot=\"code\">#!/bin/sh\ngopass show $(echo &quot;${@}&quot;|tr : \\ )</code>\n        </deckgo-highlight-code>\n<p>You can also simply make <code>summon-gopass</code> a symbolic link to your <code>gopass</code> binary, but subkeys won't work in this case.</p>\n<h4>Usage</h4>\n<p>Summon lets you provide a local <code>secrets.yml</code> file which defines which environment variables you wish to define, and how to find the values.</p>\n<p>Here's a simple example of a <code>secrets.yml</code> file using the secret we defined earlier:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">SERVICE_URL: !var terraform/service.io/api url\nUSER: !var terraform/service.io/api username\nSERVICE_PASSWORD: !var terraform/service.io/api password</code>\n        </deckgo-highlight-code>\n<p>You can test this setup by running the following command in the directory containing <code>secrets.yml</code>:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ summon env</code>\n        </deckgo-highlight-code>\n<p>The output should contain the 3 variables with the values stored in Gopass.</p>\n<h4>Exposing files</h4>\n<p>While the format above allows you to expose simple secrets as variables, it is not very practical when you need secrets exposed as files.</p>\n<p>Summon covers this need however, using the <code>file</code> flag. For example:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">SSH_KEY: !var:file terraform/service.io/ssh private_key</code>\n        </deckgo-highlight-code>\n<p>If <code>terraform/service.io/ssh</code> is a secret in Gopass whose <code>private_key</code> YAML field contains an SSH private key, then Summon will extract this secret, place it into a temporary file (in <code>/dev/shm</code> by default) and set the <code>SSH_KEY</code> variable with the path to the file. After the command returns, the temporary file will be delete.</p>\n<p>You could then use such a <code>secrets.yml</code> file with:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">summon sh -c &#39;ssh -i $SSH_KEY user@service&#39;</code>\n        </deckgo-highlight-code>\n<p>Another useful example is to store a Kubernetes cluster configuration in Gopass, e.g.:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">---\napiVersion: v1\nclusters:\n    - cluster:\n        server: https://k8s.example.com\n      name: k8s\ncontexts:\n    - context:\n        cluster: k8s\n        namespace: default\n        user: default-cluster-admin\n      name: default-admin\ncurrent-context: default-admin\nkind: Config\npreferences: {}\nusers:\n    - name: default-cluster-admin\n      user:\n        token: averylongtoken</code>\n        </deckgo-highlight-code>\n<p>With the following <code>secrets.yml</code> file:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">KUBECONFIG: !var:file path/to/secret</code>\n        </deckgo-highlight-code>\n<p>You can then work on the Kubernetes cluster with <code>kubectl</code> using:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">$ summon kubectl &lt;some command&gt;</code>\n        </deckgo-highlight-code>\n<h3>Terraform integration</h3>\n<p>A simple way to pass variables to Terraform is to declare them and use <code>summon</code> to pass the values:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">TF_VAR_var1: !var terraform/project1/secret1 field1</code>\n        </deckgo-highlight-code>\n<p>You can then run <code>summon terraform</code> to dynamically pass these secrets to Terraform.</p>\n<p>Another possibility is to use <a href=\"https://github.com/camptocamp/terraform-provider-pass\">Camptocamp's Terraform Pass Provider</a> which lets you retrieve and set passwords in Gopass natively in Terraform:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">provider &quot;pass&quot; {\n  store_dir = &quot;/srv/password-store&quot;    # defaults to $PASSWORD_STORE_DIR\n  refresh_store = false                # do not call `git pull`\n}\n\n# Store a value into the Gopass store\nresource &quot;pass_password&quot; &quot;test&quot; {\n  path = &quot;secret/foo&quot;\n  password = &quot;0123456789&quot;\n  data = {\n    zip = &quot;zap&quot;\n  }\n}\n\n# Retrieve password at another_secret/bar to be used in Terraform code\ndata &quot;pass_password&quot; &quot;test&quot; {\n  path = &quot;another_secret/bar&quot;\n}</code>\n        </deckgo-highlight-code>\n<p>The provider exposes the secret with the following properties:</p>\n<ul>\n<li><code>path</code>: path to the secret</li>\n<li><code>password</code>: secret password (first line of the content)</li>\n<li><code>data</code>: a structure (map) of the YAML data in the content</li>\n<li><code>body</code>: the content found on lines 2 and following, if it could not be parsed as YAML </li>\n<li><code>full</code>: the full content (all lines) of the secret</li>\n</ul>\n<h3>Hiera Integration</h3>\n<p>The most standard way to store secrets in Hiera is to use <a href=\"https://github.com/voxpupuli/hiera-eyaml\"><code>hiera-eyaml</code></a>, which stores secret values encrypted inside YAML files, using either a PKCS7 key (default) or multiple GnuPG keys (when using <a href=\"https://github.com/voxpupuli/hiera-eyaml-gpg\"><code>hiera-eyaml-gpg</code></a>).</p>\n<p>If your passwords are already stored in Gopass, you might want to integrate that into Hiera instead.</p>\n<p>The <a href=\"https://github.com/camptocamp/hiera-pass\"><code>camptocamp/hiera-pass</code> module</a> provides two Hiera backends to retrieve keys either as full Gopass secrets, or as keys inside the secrets.</p>"},{"url":"/posts/20201111-a-simple-auth-proxy-for-eks-24dh/","relativePath":"posts/20201111-a-simple-auth-proxy-for-eks-24dh.md","relativeDir":"posts","base":"20201111-a-simple-auth-proxy-for-eks-24dh.md","name":"20201111-a-simple-auth-proxy-for-eks-24dh","frontmatter":{"title":"A Simple Auth Proxy for EKS","template":"post","date":"2020-11-11T16:10:48Z","excerpt":"How to easily give access to an EKS cluster using an authentication proxy with a PSK","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F49dhgxfjbeqgo2kxo5sr.jpeg","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F49dhgxfjbeqgo2kxo5sr.jpeg","canonical_url":"https://dev.to/camptocamp-ops/a-simple-auth-proxy-for-eks-24dh","devto_url":"https://dev.to/camptocamp-ops/a-simple-auth-proxy-for-eks-24dh"},"html":"<p><a href=\"https://aws.amazon.com/eks/\">AWS EKS</a> is a great option for a hosted Kubernetes cluster.</p>\n<p>It is in particular easy to use for demos and training sessions.</p>\n<p>However, EKS authentication is based off AWS IAM, which means users need an AWS account. Authenticating to EKS typically involves calling the <code>aws eks get-token</code> command in your <code>.kube/config</code> so as to retrieve an authentication token.</p>\n<p>As we were setting up EKS for Kubernetes training, we needed a simple way for users without an AWS account to access the cluster, so we created a basic proxy service for the EKS <code>get-token</code> action.</p>\n<p><a href=\"https://github.com/camptocamp/aws-iam-authenticator-proxy\">GitHub — camptocamp/aws-iam-authenticator-proxy</a></p>\n<h2>Deploying with Docker</h2>\n<p>The proxy can be deployed using Docker, with AWS credentials, e.g.:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">docker run --rm -p 8080:8080 \\\n             -e AWS_ACCESS_KEY_ID=&lt;AWS_ACCESS_KEY_ID&gt; \\\n             -e AWS_SECRET_ACCESS_KEY=&lt;AWS_SECRET_ACCESS_KEY&gt; \\\n             -e EKS_CLUSTER_ID=&lt;EKS_CLUSTER_ID&gt; \\\n             -e PSK=&quot;mysecretstring&quot; \\\n    camptocamp/aws-iam-authenticator-proxy:latest</code>\n        </deckgo-highlight-code>\n<p>The rights on the cluster will depend on the user you chose to create the access key.</p>\n<p>The PSK is optional, and allows to secure the proxy a little bit.</p>\n<p>Once the proxy is started, you can access it at <a href=\"http://localhost:8080?psk=mysecretstring\">http://localhost:8080?psk=mysecretstring</a>, so you can simply set your <code>~/.kube/config</code> to use <code>curl</code> instead of <code>aws</code>:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">users:\n- name: &lt;cluster_name&gt;\n  user:\n    exec:\n      apiVersion: client.authentication.k8s.io/v1alpha1\n      command: curl\n      args:\n        - -s\n        - &quot;http://&lt;your_ip&gt;:8080/?psk=mysecretstring&quot;</code>\n        </deckgo-highlight-code>\n<h2>Deploying in EKS</h2>\n<p>Since you've got an EKS cluster in the first place, you might as well deploy the proxy in it.</p>\n<p>The repository provides a Helm chart for that, in the <code>k8s</code> directory of the GitHub project.</p>\n<p>You can simply instantiate the chart with the following values:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">eks_cluster_id: &quot;&lt;EKS_CLUSTER_ID&gt;&quot;\npsk: &quot;mysecretstring&quot;\naws:\n  access_key_id: &quot;&lt;AWS_ACCESS_KEY_ID&gt;&quot;\n  secret_access_key: &quot;&lt;AWS_SECRET_ACCESS_KEY&gt;&quot;</code>\n        </deckgo-highlight-code>\n<p>The AWS credentials will be stored in a Kubernetes secret and passed to the container.</p>\n<h3>Using</h3>\n<p>However, since we're in AWS, we can also use <a href=\"https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html\">IAM roles for service accounts</a> and bypass the access keys altogether. This is a much cleaner approach.</p>\n<p>Here's how to do it, using Terraform to create the role and deploy the proxy.</p>\n<p>First, create a role linked to OIDC:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">module &quot;iam_assumable_role_aws_iam_authenticator_proxy&quot; {\n  source                        = &quot;terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc&quot;\n  version                       = &quot;3.3.0&quot;\n  create_role                   = true\n  number_of_role_policy_arns    = 0\n  role_name                     = &quot;aws-iam-authenticator-proxy&quot;\n  provider_url                  = replace(module.cluster.cluster_oidc_issuer_url, &quot;https://&quot;, &quot;&quot;)\n  oidc_fully_qualified_subjects = [&quot;system:serviceaccount:yournamespace:aws-iam-authenticator-proxy&quot;]\n}</code>\n        </deckgo-highlight-code>\n<p>replacing <code>yournamespace</code> with the Kubernetes namespace where you will be deploying the proxy.</p>\n<p>Now we can configure the cluster to use map that role to the Kubernetes role we want (e.g. <code>system::masters</code> to make it cluster admin).</p>\n<p>We'll create a random PSK and generate the Kubeconfig file to use <code>curl</code> with the proxy:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">data &quot;aws_vpc&quot; &quot;this&quot; {\n  id = var.vpc_id\n}\n\ndata &quot;aws_subnet_ids&quot; &quot;private&quot; {\n  vpc_id = data.aws_vpc.this.id\n\n  tags = {\n    &quot;kubernetes.io/role/internal-elb&quot; = &quot;1&quot;\n  }\n}\n\nmodule &quot;cluster&quot; {\n  source  = &quot;terraform-aws-modules/eks/aws&quot;\n  version = &quot;13.1.0&quot;\n\n  cluster_name    = var.cluster_name\n  cluster_version = &quot;1.18&quot;\n\n  subnets          = data.aws_subnet_ids.private.ids\n  vpc_id           = var.vpc_id\n  enable_irsa      = true\n  map_roles        = [\n    {\n      rolearn  = module.iam_assumable_role_aws_iam_authenticator_proxy.this_iam_role_arn,\n      username = module.iam_assumable_role_aws_iam_authenticator_proxy.this_iam_role_name,\n      groups   = [&quot;system:masters&quot;]\n    },\n  ]\n\n  worker_groups = [\n    {\n      instance_type        = &quot;m5a.large&quot;\n      asg_desired_capacity = 2\n      asg_max_size         = 3\n    }\n  ]\n\n  kubeconfig_aws_authenticator_command = &quot;curl&quot;\n  kubeconfig_aws_authenticator_command_args\t= [\n    &quot;-s&quot;,\n    &quot;https://${var.auth_url}/?psk=${random_password.auth_proxy_psk.result}&quot;,\n  ]\n}\n\nresource &quot;random_password&quot; &quot;auth_proxy_psk&quot; {\n  length  = 16\n  special = false\n}</code>\n        </deckgo-highlight-code>\n<p>Finally, we can deploy the proxy in Kubernetes using Helm:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">resource &quot;helm_release&quot; &quot;aws-iam-authenticator-proxy&quot; {\n  name              = &quot;aws-iam-authenticator-proxy&quot;\n  chart             = &quot;https://github.com/camptocamp/aws-iam-authenticator-proxy/tree/master/k8s&quot;\n  namespace         = &quot;aws-iam-authenticator-proxy&quot;\n  dependency_update = true\n  create_namespace  = tr\n\n  values = [\n    &lt;&lt; EOT\neks_cluster_id: &quot;${var.cluster_name}&quot;\npsk: &quot;${random_password.auth_proxy_psk.result}&quot;\nserviceAccount:\n  name: &quot;aws-iam-authenticator-proxy&quot;\n  annotations:\n    eks.amazonaws.com/role-arn: ${module.iam_assumable_role_aws_iam_authenticator_proxy.this_iam_role_arn}\nEOT\n  ]\n\n  depends_on = [\n    module.cluster,\n  ]\n}</code>\n        </deckgo-highlight-code>\n<p>You can add an <code>Ingress</code> or configure the <code>Service</code> to use an L4 <code>LoadBalancer</code> by tuning the Helm values.</p>"},{"url":"/posts/20210318-immutability-loose-coupling-a-match-made-in-heaven-37kl/","relativePath":"posts/20210318-immutability-loose-coupling-a-match-made-in-heaven-37kl.md","relativeDir":"posts","base":"20210318-immutability-loose-coupling-a-match-made-in-heaven-37kl.md","name":"20210318-immutability-loose-coupling-a-match-made-in-heaven-37kl","frontmatter":{"title":"Immutability & loose coupling: a match made in heaven","template":"post","date":"2021-03-18T07:31:29Z","excerpt":"Decoupling in container orchestration enables immutable infrastructure workflows.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzptftzr4jxlblx11pf8n.jpg","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzptftzr4jxlblx11pf8n.jpg","canonical_url":"https://www.camptocamp.com/en/news-events/immutability-and-loose-coupling-a-match-made-in-heaven","devto_url":"https://dev.to/camptocamp-ops/immutability-loose-coupling-a-match-made-in-heaven-37kl"},"html":"<p>When it comes to infrastructure and deployment automation, two opposite approaches share the podium: <a href=\"https://www.digitalocean.com/community/tutorials/what-is-immutable-infrastructure\">mutable vs immutable management</a>.</p>\n<h2>Mutable Systems</h2>\n<p>Mutable systems usually have a long life cycle, typically in the order\nof weeks to years. As their requirements change (new files,\nconfigurations, users, packages, etc.), the systems are modified to\nmatch a new target state. When left unmanaged, mutable systems tend to\ndrift away from their target state, in a <em>divergent</em> dynamic.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11trbxkb1ev9goye0q2k.png\" alt=\"Convergence Models in Mutable Systems\"></p>\n<p>Automating mutable systems is often referred to as Configuration Management, and leverages tools such as <a href=\"https://cfengine.com/\">Cfengine</a>, <a href=\"https://puppet.com/\">Puppet</a>, <a href=\"https://www.chef.io/\">Chef</a>, or <a href=\"https://www.ansible.com/\">Ansible</a>. This tooling uses principles based on the concepts of target state, idempotence, and somewhat related to <a href=\"https://en.wikipedia.org/wiki/Promise_theory\">Mark Burgess’ Promise Theory</a>.  Configuration Management aims to make the system <em>convergent</em>, by running a tool on a regular basis, in order to resynchronize the system with its target state. Some of these tools (e.g.  <a href=\"https://github.com/purpleidea/mgmt\">mgmt</a>) also attempt to reach <em>congruence</em> by adopting a reactive approach, triggering corrective actions on events.</p>\n<h2>Immutable Systems</h2>\n<p>In an immutable system, any change requires a new deployment. Whether it be a change in configuration, new files, or new users, immutability demands that the system be destroyed and rebuilt from scratch.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl5ewtyqpo5offh4p9jh.png\" alt=\"Trash full? Let&#x27;s move to a new house!\"></p>\n<p>An immutable approach can greatly simplify deployments. Avoiding convergence, immutable systems rely on artifacts that are built once, and can be deployed multiple times. These artifacts can increase trust in the ability to rebuild the system from scratch if necessary. It can also ease scalability, since the artifact can be precisely duplicated.</p>\n<p>However, immutability comes with a high cost: in order to be done properly, it must be strict. Any change to the state involves a complete replacement of the artifact. A lack of abiding to that rule results in a convergent system (at best), which cannot be managed in an immutable manner.</p>\n<h2>Mutable vs Immutable</h2>\n<p>If immutable systems are easier to maintain, what are the reasons for not using them?  The system’s complexity is probably the main justification. A traditional mutable system features multiple layers (Operating System, Middleware, Applications) that are usually strongly coupled. For example, the flavor and version of the Operating System define which version of a Middleware (e.g. Tomcat, Apache) is available for installation. In turn, the Middleware version defines which libraries are available for the Application. On most Unix-based systems, shared libraries are at the root of strong links between software versions, based on the underlying ABIs required to run them.</p>\n<p>If such strongly coupled systems are to be managed in an immutable manner, then the <em>whole system</em> is the immutable artifact. In the majority of organizations, this implies managing tens to thousands of artifacts, and rebuilding them from scratch on a regular basis. Such complexity is too much of a cost.</p>\n<p>Enters decoupling technologies: Over the years, new technologies have surfaced, which allow to decouple system components and ease their management in an immutable manner.</p>\n<h1>Virtualization and IaaS</h1>\n<p>With the rise of virtualization in the early years of the 21st century, it became easier to decouple the hardware from the Operating System. You could now size virtual machines as precisely as desired, in terms of CPU, memory, or disk, without adding or replacing any physical device.</p>\n<p>This unlocked access to a first level of automated immutability, using Virtual Machine images as the immutable artifact. Image generators (such as Hashicorp Packer) appeared, easing the generation of VM images.</p>\n<p>Provided the whole target state —including the OS, middleware and application itself— is built into the VM image, an immutable workflow can be used to manage it. In this case, whenever a change is required, the whole image needs to be rebuilt and redeployed to new VMs.</p>\n<figure>\n  <img alt=\"Golden Images are a common approach to divergent templating\" src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0gu5n8xhs57jvxnkn3ay.jpg\" />\n  <figcaption>Golden Images are a common approach to divergent templating</figcaption>\n</figure>\n<p>For a time, this deployment could not be easily automated, as physical nodes still needed to be manually picked before deploying the VM images.  Infrastructure as a Service (IaaS), often referred to as “Cloud”, changed that.</p>\n<p>IaaS provided APIs on top of virtualization, and the IaaS system (e.g.  AWS EC2, OpenStack) would often pick the physical hypervisor node itself, making it possible to fully automate the deployment of new artifacts (such as VM images).</p>\n<p>One important problem remained to achieve immutable infrastructure in most situations: the complexity of the artifact itself.</p>\n<h1>Containers and Orchestrators</h1>\n<p>When setting up applications on existing systems, several issues often arise, among which are: packaging, configuration, and dependencies.</p>\n<p>For years, developers and systems engineers tried to solve the problem of application packaging and deployment using all kinds of package managers, from deb/rpm to homemade systems. This often failed, because these packages didn’t allow for multiple instantiation of the application, were not easy to configure, and were too tightly coupled to the rest of the system.</p>\n<p>Docker containers provided a unified way of packaging applications, in the form of OCI images, a rather unified way of configuring them (using environment variables or mounted files, in the <a href=\"https://12factor.net/\">12 factor app</a> fashion).</p>\n<p>But mainly, containers provided an abstraction level, a decoupling from the system. With containers, it doesn’t matter anymore which OS is running the container engine. Developers can now choose to run any version of Tomcat or Apache, on any node with a container engine. As a corollary, they can also run any combination of Middleware and Applications, regardless of the libraries provided on the underlying system.</p>\n<p>Furthermore, containers were made to be managed in an immutable manner, using OCI images as immutable artifacts. Every time a container needs to be modified, it requires the creation of a new container from a new image.</p>\n<p>The benefits are huge. With this decoupling of the Operating System from the Middleware and Applications, the monolithic immutable artifact that was previously managed as a VM image can now be broken down into many pieces: the application is now an immutable artifact, and so are the middleware components as well.</p>\n<p>Even better: since all the components running on the machines are now immutable, the machines themselves have now become totally neutral; all they require is a container engine in order to run containers.</p>\n<p>One thing can still make a node a snowflake: manually deployed containers, using tools such as Docker or Docker-compose.</p>\n<p>What IaaS did for VMs, Container orchestrators now do for containers: they provide an API to orchestrate the dynamic deployment of containers on a cluster of nodes.</p>\n<figure>\n  <img alt=\"Container Orchestration is to Containers what IaaS is to Virtual Machines\" src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4m11g0j9tcebfi9zlic.png\" />\n  <figcaption>Container Orchestration is to Containers what IaaS is to Virtual Machines.</figcaption>\n</figure>\n<p>The nodes are thus totally neutral now. As a result, their templates are greatly simplified, and they can now easily be managed in an immutable way. This new paradigm opened the way to new Operating Systems specialized for container orchestration, such as <a href=\"https://getfedora.org/en/coreos?stream=stable\">CoreOS</a> or <a href=\"https://rancher.com/docs/os/v1.x/en/\">RancherOS</a>, whose life cycles are meant to be managed with an immutable workflow.</p>\n<h1>Immutability &#x26; Convergence</h1>\n<p>Now that we have a full Immutable System, does it solve the problem of convergence?</p>\n<p>Immutable artifacts in themselves are not supposed to evolve, but their target state does evolve. In this regard, the situation with containers is similar to that of Golden Images for Virtual Machines: though the artifact is immutable, it can easily be used as a template leading to an unmaintained, divergent system.</p>\n<p>There is thus still a need for convergence tools in the container world.  However, this is not because the objects themselves drift from their target state. Rather, the target state evolves while the objects —supposedly— are stuck in their original state.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kyd2lcx01jxcgq1qiq6w.png\" alt=\"Convergence Models in Immutable Systems\"></p>\n<p>Where Configuration Management tools ensure a convergence of states for mutable systems such as VMs, packaging tools such as <a href=\"https://helm.sh/\">Helm</a> and <a href=\"https://github.com/roboll/helmfile\">Helmfile</a> can be used to periodically re-synchronize containers and other immutable objects with their target state.</p>\n<p>Finally, congruence is also achievable, with tools such as <a href=\"https://argoproj.github.io/argo-cd/\">Argo CD</a>. Argo CD not only deploys immutable objects to a Kubernetes cluster, but also keeps them synchronized with their last known target state, ensuring a continuous management.</p>\n<h1>Conclusion</h1>\n<p>Containers and Container Orchestrators enable fully immutable workflows for infrastructure, middleware and applications alike:</p>\n<ul>\n<li>Nodes can be managed as virtual machines, with immutable VM images deployed dynamically using IaaS;</li>\n<li>Middleware and applications can be managed as containers, with immutable OCI images deployed dynamically using Container Orchestrators.</li>\n</ul>\n<p>Are immutable systems always the best answer? As we’ve seen, the cost in artifact management and orchestration is far from negligible. Due to the transient nature that comes with their immutability, containers are better suited for stateless applications that easily scale on clusters of neutral nodes. For this reason, highly stateful applications with long life cycles, such as databases, are still better maintained as mutable systems most of the time.</p>\n<p>Choosing an immutable vs mutable architecture depends a lot on an organization’s software architecture and culture, and is not a light choice to make. Is immutable infrastructure the solution to your automation problems? Contact us, we can help you find out!</p>"},{"url":"/posts/20210401-march-cloud-native-romandie-meetup-o2f/","relativePath":"posts/20210401-march-cloud-native-romandie-meetup-o2f.md","relativeDir":"posts","base":"20210401-march-cloud-native-romandie-meetup-o2f.md","name":"20210401-march-cloud-native-romandie-meetup-o2f","frontmatter":{"title":"March Cloud Native Romandie Meetup","template":"post","date":"2021-04-01T13:44:49Z","excerpt":"The last Cloud Native Romandie Meetup took place on March 25th","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcf6t3h6z8shvv26x7tqr.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcf6t3h6z8shvv26x7tqr.png","canonical_url":"https://dev.to/camptocamp-ops/march-cloud-native-romandie-meetup-o2f","devto_url":"https://dev.to/camptocamp-ops/march-cloud-native-romandie-meetup-o2f"},"html":"<p>Last week, we organized our last Cloud Native Romandie Meetup. Due to the current situation, this was an online event like the previous occurrences.</p>\n<p>The meetup was recorded and can be viewed again on YouTube.</p>\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/21DZ6hD97kM\" title=\"YouTube video\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n<h1>Subjects</h1>\n<p>For this edition, we had presentations by <a href=\"https://www.cloudbees.com/\">CloudBees</a>, <a href=\"https://exoscale.com\">Exoscale</a>, and <a href=\"https://camptocamp.com\">Camptocamp</a>.</p>\n<h2>CloudBees CI</h2>\n<p><a href=\"https://www.linkedin.com/in/fredericgibelin/\">Frédéric Gibelin</a> from <a href=\"https://www.cloudbees.com/\">CloudBees</a> presented <a href=\"https://docs.cloudbees.com/docs/cloudbees-ci/latest/\">CloudBees CI</a>, a solution that helps users scale their Jenkins Enterprise platform in the Cloud.</p>\n<p><a href=\"https://drive.google.com/file/d/1u_pYAMA562a7Rzs2B-4XBImn6WsGHLpn/view\">See the slides</a></p>\n<h2>Exoscale SKS</h2>\n<p>Next <a href=\"https://twitter.com/pyr\">Pierre-Yves Ritschard</a> and <a href=\"https://twitter.com/_mcorbin\">Mathieu Corbin</a> from <a href=\"https://exoscale.com\">Exoscale</a> presented <a href=\"https://community.exoscale.com/documentation/sks/\">SKS</a>, a Kubernetes as-a-Service, operating the user’s cluster on their behalf and taking care of the underlying infrastructure.</p>\n<p><a href=\"https://speakerdeck.com/oembed.json?id=080c0fc92a8a4d37b5d4ef02eb590d19\">Speaker Deck</a></p>\n<h2>Camptocamp DevOps Stack</h2>\n<p>To finish, <a href=\"https://twitter.com/raphink\">Raphaël Pinson</a> presented Camptocamp's <a href=\"https://devops-stack.io\">DevOps Stack</a> project,  a framework to deploy a standardized Kubernetes platform and its ecosystem, using a GitOps approach.</p>\n<p><a href=\"https://www.slideshare.net/slideshow/embed_code/j0zNBoH48aFEO2\">SlideShare</a></p>\n<h1>Future Meetups</h1>\n<p>Don’t miss any more and join the <a href=\"https://www.meetup.com/Cloud-Native-Romandie\">Cloud Native Romandie meetup group</a>. This way, you can be part of a local community and stay up-to-date on different Cloud Native technologies.</p>\n<p>We look forward to meeting and exchanging with you during our next virtual meetup scheduled for Thursday, June 17th.</p>\n<p>Also, if you are keen to present a technology or you would like to see more of a technology, please let us know, we would be happy to support your interest. If it interests you, it also interests the community.</p>"},{"url":"/posts/20201006-colored-wrappers-for-kubectl-2pj1/","relativePath":"posts/20201006-colored-wrappers-for-kubectl-2pj1.md","relativeDir":"posts","base":"20201006-colored-wrappers-for-kubectl-2pj1.md","name":"20201006-colored-wrappers-for-kubectl-2pj1","frontmatter":{"title":"Colored wrappers for kubectl","template":"post","date":"2020-10-06T19:50:58Z","excerpt":"Kubectl commands, but in color","canonical_url":"https://dev.to/raphink/colored-wrappers-for-kubectl-2pj1","devto_url":"https://dev.to/raphink/colored-wrappers-for-kubectl-2pj1"},"html":"<p>When using Kubernetes, <code>kubectl</code> is the command we use the most to visualize and debug objects.</p>\n<p>However, it currently does not support colored output, though there is <a href=\"https://github.com/kubernetes/kubectl/issues/524\">a feature request opened for this</a>.</p>\n<p>Let's see how we can add color support. I'll be using zsh with <a href=\"https://ohmyz.sh/\">oh my zsh</a>.</p>\n<p><em>Edit:</em> this feature was <a href=\"https://github.com/ohmyzsh/ohmyzsh/pull/9316\">merged in oh my zsh</a>, so it is now standard.</p>\n<h1>Zsh plugin</h1>\n<p>Let's make this extension into a zsh plugin called <code>kubectl_color</code>:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">❯ mkdir -p ~/.oh-my-zsh/custom/plugins/kubectl_color\n❯ touch ~/.oh-my-zsh/custom/plugins/kubectl_color/kubectl_color.plugin.zsh</code>\n        </deckgo-highlight-code>\n<p>Now we need to fill in this plugin.</p>\n<h2>JSON colorizing</h2>\n<p>Let's start with JSON, by adding an alias that colorizes JSON output using the infamous <a href=\"https://stedolan.github.io/jq/\"><code>jq</code></a>:</p>\n<deckgo-highlight-code language=\"zsh\"  >\n          <code slot=\"code\">kj() {\n  kubectl &quot;$@&quot; -o json | jq\n}\n\ncompdef kj=kubectl</code>\n        </deckgo-highlight-code>\n<p>The <code>compdef</code> line ensures the <code>kj</code> function gets autocompleted just like <code>kubectl</code>.</p>\n<p><em>Edit:</em> I've added another wrapper for <a href=\"https://github.com/antonmedv/fx\"><code>fx</code></a>, which provides a dynamic way to parse JSON:</p>\n<deckgo-highlight-code language=\"zsh\"  >\n          <code slot=\"code\">kjx() {\n  kubectl &quot;$@&quot; -o json | fx\n}\n\ncompdef kjx=kubectl</code>\n        </deckgo-highlight-code>\n<h2>YAML colorizing</h2>\n<p>Just like for JSON, we can use <a href=\"https://stedolan.github.io/jq/\"><code>yh</code></a> to colorize YAML output:</p>\n<deckgo-highlight-code language=\"zsh\"  >\n          <code slot=\"code\">ky() {\n  kubectl &quot;$@&quot; -o yaml | yh\n}\n\ncompdef ky=kubectl</code>\n        </deckgo-highlight-code>\n<h1>Energize!</h1>\n<p>Our plugin is now ready, we only need to activate it in <code>~/.zshrc</code> by adding it to the list of plugins, e.g.:</p>\n<deckgo-highlight-code language=\"zsh\"  >\n          <code slot=\"code\">plugins=(git ruby kubectl kubectl_color)</code>\n        </deckgo-highlight-code>\n<p><a href=\"https://asciinema.org/a/363827\">asciinema cast 363827</a></p>\n<p>and with <code>fx</code>:</p>\n<p><a href=\"https://asciinema.org/a/364137\">asciinema cast 364137</a></p>"},{"url":"/posts/20210203-open-source-standards-and-technical-debt-2g1/","relativePath":"posts/20210203-open-source-standards-and-technical-debt-2g1.md","relativeDir":"posts","base":"20210203-open-source-standards-and-technical-debt-2g1.md","name":"20210203-open-source-standards-and-technical-debt-2g1","frontmatter":{"title":"Open Source, Standards, and Technical Debt","template":"post","date":"2021-02-03T11:10:41Z","excerpt":"As software needs evolve, technological evolution implies Technical Debt. Open Source can help mitigate Technical Debt by influencing on standards.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa1ozenortfr4pe0d1vad.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa1ozenortfr4pe0d1vad.png","canonical_url":"https://www.camptocamp.com/en/news-events/open-source-standards-and-technical-debt","devto_url":"https://dev.to/camptocamp-ops/open-source-standards-and-technical-debt-2g1"},"html":"<p>Twenty years ago, Camptocamp was a pioneer company in Open Source adoption. Nowadays, <a href=\"https://ieeexplore.ieee.org/document/8880574\">Open Source has become mainstream</a> and the vast majority of the industry agrees on the many benefits of its practices. In fact, the Open Source model has become a <em>de facto</em> standard in some fields such as Web Frontend development.</p>\n<p>Many companies make an increasing use of Open Source software in their infrastructure and development stacks, and there are countless proven reasons for doing so, such as standard formats or <a href=\"https://www.forbes.com/sites/martenmickos/2018/09/26/why-openness-is-the-greatest-path-to-security/?sh=567640cf5f7f\">security by openness</a>, to name just a couple.</p>\n<p>In spite of these benefits, companies openly contributing —let alone Open Sourcing their own projects— are still somehow not very common, and most firms think of Open Source purely as a consumer’s benefit.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/dqdagbzcqm5kgkbcx4qz.png\" alt=\"Open Source Community | © Shutterstock\"></p>\n<h2>So why should you contribute to Open Source software?</h2>\n<p>For years, I used to think the best argument in favor of contributing to existing projects was maintenance and compatibility. If I fork a project and add functionality to it, there is a risk that my changes will become increasingly hard to maintain as time goes by. If the core developers of the program are aware of my changes and actively intend to go along with them, this risk will be greatly reduced.</p>\n<p>So contributing my changes ensures they will stay compatible with the base code as time goes by. There might even be improvements to my code if more people encounter a similar need in the future, and decide to build on top of my changes.</p>\n<p>Today, however, I believe the example I have just given is a specific case of a more general rule, which encompasses more pragmatic reasons to contribute code as Open Source. This more general context is linked to the concept of <a href=\"https://www.linuxfoundation.org/en/resources/publications/solving-technical-debt-with-open-source/\">Technical Debt</a>: the idea that technical decisions imply a hidden cost (a “debt”) that will have to be paid in the future in order to catch up with state-of-the-art technology.</p>\n<h2>So how do I minimize the debt?</h2>\n<p>Minimizing technical debt is a vast —and at times conflicting— subject. However, I think it is safe to assume that one way to reduce its risk is to stick to standards. The closer a project sticks to industry standards, the less likely it will have to be ported to another technological stack in the foreseeable future.</p>\n<h2>What if the standards don’t meet my needs?</h2>\n<p>When faced with a missing feature, most people’s reflex might be to start building a specific component to meet their use case. In the words of <a href=\"https://hiredthought.com/2018/09/01/intro-to-wardley-mapping/\">Strategy Theorist Simon Wardley</a>, they’ll be shifting this component to the Genesis stage, making it more unpredictable —or even erratic—, less standard, and thus more prone to building up technical debt in time.</p>\n<p>There is another way though. If my need is not met, and it is in fact a valid need (which is a very important question to ask in the first place), then other people might have this need in the future. When they do, someone, somewhere, will create a new standard for this need. When this new standard becomes enforced, then will my specific component’s debt become obvious.</p>\n<p>So what if, instead of building a specific component to make up for the lack of standard, I set the new standard myself? Open Source lets you do just that! It gives you the opportunity to be the first one providing an open implementation to a generic need, and the chance to make it into the new standard. If that new standard catches on, you have not only solved your problem, but you also haven’t accumulated technical debt. In fact, you’re ahead of the other users, because you set the new standard.</p>\n<h2>Wait, we’re no FAANG!</h2>\n<p>Obviously, the majority of organizations can't afford to have engineers focusing on IETF RFCs or moving ISO standards to fit our needs.</p>\n<p>However, a standard doesn't have to be that complicated. Let’s say I use this popular CLI tool, but I need to specify an option which doesn’t exist yet. I could hack something around the generation of its configuration file to produce the options I need. Or I could patch that tool and add a new flag for my needs, and contribute that change back to the project. Chances are, if I need this option, some else does too.</p>\n<p>Now, every time someone has the need for that option, they’ll be using my new flag. I’ve contributed a new standard, and I haven’t made any technical debt on my side.</p>\n<p>It’s not the size of the steps that matters, it’s really the direction in which you take them.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/i/8z3stx204q4gut3w4coq.png\" alt=\"Start with Open Source | © Shutterstock\"></p>\n<h2>Where do I start?</h2>\n<p>Open Source is not just a philosophy. It encompasses licensing issues, technology standards, culture, and much more.</p>\n<p>At Camptocamp, we’ve been committed to the Open Source approach for years.</p>\n<p>This means we have a habit of solving problems in generic terms and building new standards.</p>\n<p>It also means we have contacts in many Open Source communities, which allow us to brainstorm ideas and quickly contribute to projects, ensuring a fast feedback loop on our work.</p>\n<p>When we implement Open Source software for our clients, we actively seek to limit technological debt. Because we believe in a world of standards, we don’t want our clients to feel entirely stuck with a technological stack in the future. Or even with us!</p>"},{"url":"/posts/20210511-how-to-allow-dynamic-terraform-provider-configuration-20ik/","relativePath":"posts/20210511-how-to-allow-dynamic-terraform-provider-configuration-20ik.md","relativeDir":"posts","base":"20210511-how-to-allow-dynamic-terraform-provider-configuration-20ik.md","name":"20210511-how-to-allow-dynamic-terraform-provider-configuration-20ik","frontmatter":{"title":"How to allow dynamic Terraform Provider Configuration","template":"post","date":"2021-05-11T11:47:57Z","excerpt":"Terraform providers can be dynamically configured using other resource attributes if their code allows for it","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjkdnrg8gmbjiazvn8l8.jpg","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjkdnrg8gmbjiazvn8l8.jpg","canonical_url":"https://dev.to/camptocamp-ops/how-to-allow-dynamic-terraform-provider-configuration-20ik","devto_url":"https://dev.to/camptocamp-ops/how-to-allow-dynamic-terraform-provider-configuration-20ik"},"html":"<p><a href=\"http://terraform.io/\">Terraform</a> relies heavily on the concept of <a href=\"https://www.terraform.io/docs/providers/index.html\">providers</a>, a base brick which consists of Go plugins enabling the communication with an API.</p>\n<p>Each provider gives access to one or more resource types, and these resources then manage objects on the target API.</p>\n<p>Most of the time, a provider's configuration is static, e.g.</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">provider &quot;aws&quot; {\n  region = &quot;us-east-1&quot;\n}</code>\n        </deckgo-highlight-code>\n<p>However, in some cases, it is useful to configure a provider dynamically, using the attribute values from other resources as input for the provider's configuration.</p>\n<p>I'll use the example of the <a href=\"https://github.com/oboukili/terraform-provider-argocd\">Argo CD provider</a>. <em>In a single Terraform run</em>, we would like to:</p>\n<ul>\n<li>install a Kubernetes cluster (using a <a href=\"https://devops-stack.io\">DevOps Stack</a> K3s Terraform module)</li>\n<li>install Argo CD on the the cluster using the <a href=\"https://registry.terraform.io/providers/hashicorp/helm/latest/docs\">Helm provider</a></li>\n<li>instantiate Argo CD resources (projects, applications, etc.) on this new Argo CD server.</li>\n</ul>\n<p>Our code will look like this:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\"># Install Kubernetes &amp; Argo CD using a local module\n# (from https://devops-stack.io)\nmodule &quot;cluster&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack.git//modules/k3s/docker?ref=master&quot;\n\n  cluster_name = &quot;default&quot;\n  node_count   = 1\n}\n\n# /!\\ Setup the Argo CD provider dynamically\n# based on the cluster module&#39;s output\nprovider &quot;argocd&quot; {\n  server_addr = module.cluster.argocd_server\n  auth_token  = module.cluster.argocd_auth_token\n  insecure    = true\n  grpc_web    = true\n}\n\n# Deploy an Argo CD resource using the provider\nresource &quot;argocd_project&quot; &quot;demo_app&quot; {\n  metadata {\n    name      = &quot;demo-app&quot;\n    namespace = &quot;argocd&quot;\n  }\n\n  spec {\n    description  = &quot;Demo application project&quot;\n    source_repos = [&quot;*&quot;]\n\n    destination {\n      server    = &quot;https://kubernetes.default.svc&quot;\n      namespace = &quot;default&quot;\n    }\n\n    orphaned_resources {\n      warn = true\n    }\n  }\n\n  depends_on = [ module.cluster ]\n}</code>\n        </deckgo-highlight-code>\n<p>This requires to configure Argo CD dynamically, using the output of the Kubernetes cluster's resources.</p>\n<h1>Provider Initialization</h1>\n<p>Providers are initialized early in a Terraform run, as their initialization is required to compute the graph which defines in which order the resources are applied.</p>\n<p>This means it is actually not possible to make a provider initialize after a secondary resource is created.</p>\n<p>Officially, the story stops here, and Terraform has <a href=\"https://github.com/hashicorp/terraform/issues/24055\">a bug report</a> to track the feature allowing to dynamically configure providers.</p>\n<p>So… it's game over then? 🎮 👾\nNot really!</p>\n<h1>Leveraging Pointers</h1>\n<p>When a provider is configured in Terraform, it triggers a configuration function:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">func Provider() *schema.Provider {\n    return &amp;schema.Provider{\n      ConfigureFunc: func(d *schema.ResourceData) (interface{}, error) {\n        // Create someObject\n        return someObject, nil\n      }\n    }\n}</code>\n        </deckgo-highlight-code>\n<p>This <code>ConfigureFunc</code> method is usually used to create a static client for the target API. In the Argo CD provider for example, it returns a <code>ServerInterface</code> structure, with pointers to several clients, instantiated from the provider parameters:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">type ServerInterface struct {                                                   \n    ApiClient            *apiclient.Client                                      \n    ApplicationClient    *application.ApplicationServiceClient                  \n    ClusterClient        *cluster.ClusterServiceClient                          \n    ProjectClient        *project.ProjectServiceClient                          \n    RepositoryClient     *repository.RepositoryServiceClient                    \n    RepoCredsClient      *repocreds.RepoCredsServiceClient                      \n    ServerVersion        *semver.Version                                        \n    ServerVersionMessage *version.VersionMessage                                                                                                              \n}</code>\n        </deckgo-highlight-code>\n<p>The return statement from the <code>ConfigureFunc</code> eventually looks like this:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">return ServerInterface{                                                             \n    &amp;apiClient,                                                                     \n    &amp;applicationClient,                                                             \n    &amp;clusterClient,                                                                 \n    &amp;projectClient,                                                                 \n    &amp;repositoryClient,                                                              \n    &amp;repoCredsClient,                                                               \n    serverVersion,                                                                  \n    serverVersionMessage}, err</code>\n        </deckgo-highlight-code>\n<p>Let's add a new field to the <code>ServerInterface</code> to store the pointer to the provider's <code>ResourceData</code> object, which gives access to the provider's parameters:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">type ServerInterface struct {                                                       \n    ApiClient            *apiclient.Client                                          \n    ApplicationClient    *application.ApplicationServiceClient                      \n    ClusterClient        *cluster.ClusterServiceClient                              \n    ProjectClient        *project.ProjectServiceClient                              \n    RepositoryClient     *repository.RepositoryServiceClient                        \n    RepoCredsClient      *repocreds.RepoCredsServiceClient                          \n    ServerVersion        *semver.Version                                            \n    ServerVersionMessage *version.VersionMessage                                    \n    ProviderData         *schema.ResourceData                                       \n}</code>\n        </deckgo-highlight-code>\n<p>Now in the <code>ConfigureFunc</code>, we'll instantiate the <code>ServerInterface</code>, providing only the <code>ProviderData</code> pointer. The first resource that needs to use the provider will then instantiate the clients, when the provider parameters are available. We'll need the <code>ConfigureFunc</code> method to return a pointer to a <code>ServerInterface</code>, so we can later cache the clients and avoid recreating them for every resource:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">ConfigureFunc: func(d *schema.ResourceData) (interface{}, error) {                  \n    server := ServerInterface{ProviderData: d}                                      \n    return &amp;server, nil                                                             \n},</code>\n        </deckgo-highlight-code>\n<h1>Initialize the Clients</h1>\n<p>Now we need to actually initialize the clients in each resource.</p>\n<p>Each resource method gets the interface returned by the <code>ConfigureFunc</code> function as an empty interface parameter, usually called <code>meta</code>:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">func resourceArgoCDProjectCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {</code>\n        </deckgo-highlight-code>\n<p>These methods currently simply cast the <code>meta</code> parameter as a <code>ServerInterface</code> structure and use the pre-initialized clients:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">server := meta.(ServerInterface)</code>\n        </deckgo-highlight-code>\n<p>We now need to cast <code>meta</code> as a pointer to a <code>ServerInterface</code> structure instead (since we'll need to modify the clients from within the resources), and initialize the clients:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">server := meta.(*ServerInterface)                                                   \nif err := server.initClients(); err != nil {                                        \n    return []diag.Diagnostic{                                                       \n        diag.Diagnostic{                                                            \n            Severity: diag.Error,                                                   \n            Summary:  fmt.Sprintf(&quot;Failed to init clients&quot;),                        \n            Detail:   err.Error(),                                                  \n        },                                                                          \n    }                                                                           \n}</code>\n        </deckgo-highlight-code>\n<p>The <code>initClients()</code> method of the <code>ServerInterface</code> structure will be called, allowing to set up the clients using the current provider parameters.</p>\n<h1>Client Pool Caching</h1>\n<p>In the <code>ServerInterface#initClients()</code> method, we want to make sure we reuse existing clients. This is rather simple, since each client is stored as a pointer in the structure, so it defaults to <code>nil</code>:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">func (p *ServerInterface) initClients() error {                                 \n    d := p.ProviderData                                                         \n                                                                                \n    if p.ApiClient == nil {                                                     \n        apiClient, err := initApiClient(d)                                      \n        if err != nil {                                                         \n            return err                                                          \n        }                                                                       \n        p.ApiClient = &amp;apiClient                                                \n    }\n\n    // etc for all clients\n\n    return nil\n}</code>\n        </deckgo-highlight-code>\n<h1>Conclusion</h1>\n<p>That's it, we're done. With these modifications, <code>terraform plan</code> now works. The resources get applied in the proper order, and the outputs from the <code>cluster</code> module get properly passed as configuration to the Argo CD clients.</p>"},{"url":"/posts/20220206-a-15-year-puppet-journey-4o39/","relativePath":"posts/20220206-a-15-year-puppet-journey-4o39.md","relativeDir":"posts","base":"20220206-a-15-year-puppet-journey-4o39.md","name":"20220206-a-15-year-puppet-journey-4o39","frontmatter":{"title":"A 15-year Puppet Journey","template":"post","date":"2022-02-06T22:59:02Z","excerpt":"Note: this is a personal blog post. It does not concern Camptocamp's partnership with Puppet Inc.,...","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ufldxkli3cw6efd7np6.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ufldxkli3cw6efd7np6.png","canonical_url":"https://dev.to/raphink/a-15-year-puppet-journey-4o39","devto_url":"https://dev.to/raphink/a-15-year-puppet-journey-4o39"},"html":"<p><em>Note: this is a personal blog post. It does not concern Camptocamp's partnership with Puppet Inc., which remains unchanged.</em></p>\n<p>In 2006, I landed my first gig as a Systems Administrator. One of my main roles was taking care of a Cfengine server and repository for about 4000 machines, and finishing its migration from Cfengine 1 to Cfengine 2.</p>\n<p>Like many people in this position at that time, discovering Puppet was akin to a revelation. Leaving aside its slowness (in comparison to Cfengine), its DSL, file templates, and the extensibility of the Resource Abstraction Layer were nothing short of a little revolution.</p>\n<p>Then Augeas was presented to me, and I enjoyed the concept so much I got involved in the project and started writing many lenses.</p>\n<p>I joined Camptocamp in 2012 in large part because of the role the company played in the Puppet community. Together with my colleague <a href=\"https://github.com/mcanevet\">Mickaël</a>, we took to standardising and modernising our Puppet stack and modules, and got deeply involved in the community, writing plugins (puppet-lint plugins, <a href=\"https://github.com/voxpupuli/facterdb\">facterdb</a>/rspec-puppet-facter) and tools (<a href=\"https://www.camptocamp.com/fr/actualites-evenements/news_integrating_prometheus\">prometheus-puppetdb-sd</a>, <a href=\"https://github.com/camptocamp/puppetfile-updater\">puppetfile-updater</a>, <a href=\"https://www.camptocamp.com/fr/actualites-evenements/news_cleaning_up_puppet_code\">puppet-ghostbuster</a>, etc.).</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3bgtu2pv4ftg5suj31rv.jpg\" alt=\"Puppet Community\"></p>\n<p>Over the years, I've had the pleasure of teaching Puppet training courses at all levels and consulting on all sorts of layers (modules, Ruby plugins, TDD, etc.) and stacks (Puppet Enterprise, Foreman, Docker-based, and more…).</p>\n<p>In the last couple of years though, my work has mostly revolved around containers and Kubernetes. I have kept maintaining Puppet- and Augeas-related code, but often without using these projects myself for production needs.</p>\n<p>For that reason, I started donating such projects to <a href=\"https://voxpupuli.org\">Voxpupuli</a>, as I believe they will receive better care than I can currently give them.</p>\n<p>Truth be told, I've delayed all this for months —maybe years— because the Puppet community is awesome and it's always been a pleasure to contribute to it. I've even gotten back quite a bit last year, reviving <a href=\"https://forge.puppet.com/modules/camptocamp/catalog_diff\">puppet-catalog-diff</a> and participating in various Puppet Camps.</p>\n<p>It's been fun, but I need to focus on other projects now, and it's probably better if things are set in a clear manner.</p>\n<p>So farewell Puppet community, keep being an awesome and welcoming place, &#x26; thanks for all the 🐟!</p>"},{"url":"/posts/20241017-how-to-automatically-issue-badges-for-instruqt-labs-18k5/","relativePath":"posts/20241017-how-to-automatically-issue-badges-for-instruqt-labs-18k5.md","relativeDir":"posts","base":"20241017-how-to-automatically-issue-badges-for-instruqt-labs-18k5.md","name":"20241017-how-to-automatically-issue-badges-for-instruqt-labs-18k5","frontmatter":{"title":"How to Automatically Issue Badges for Instruqt Labs","template":"post","date":"2024-10-17T09:00:00Z","excerpt":"Learn how to automate badge issuance with Credly when users complete an Instruqt lab.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fad7mvjs2fjrl1ud4kz62.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fad7mvjs2fjrl1ud4kz62.png","canonical_url":"https://instruqt.com/blog/guest-post-how-to-automatically-issue-badges-for-instruqt-labs","devto_url":"https://dev.to/raphink/how-to-automatically-issue-badges-for-instruqt-labs-18k5"},"html":"<p>In the <a href=\"https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9\">first blog post</a>, we talked about making labs fun and enjoyable by adding elements of gamification. Issuing badges is a fantastic way to motivate learners, giving them a sense of accomplishment for the skills they've gained, and Isovalent issues <a href=\"https://www.credly.com/organizations/isovalent/badges\">hundreds of them</a> every month for the Cilium labs!</p>\n<h1>Issuing Credentials</h1>\n<p>Obviously, issuing badges can be done manually, but this is not scalable or ideal for creating a seamless experience. So, let's automate it!</p>\n<p>Credly is a widely recognized provider of digital badges, so we will be using this solution to issue badges whenever a user finishes an Instruqt lab.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhqgqjpejwb8q0wf12fr.png\" alt=\"Who doesn&#x27;t love earning badges‽\"></p>\n<p>We'll be using Instruqt webhooks, coupled with the Credly API, to automatically issue badges when labs are completed. </p>\n<p>And thanks to Isovalent's open-sourced Go libraries for both <a href=\"https://github.com/isovalent/instruqt-go\">Instruqt</a> and <a href=\"https://github.com/isovalent/credly-go\">Credly</a> APIs, you will find this automation process smooth and straightforward.</p>\n<p><a href=\"https://github.com/isovalent/instruqt-go\">GitHub — isovalent/instruqt-go</a></p>\n<p><a href=\"https://github.com/isovalent/credly-go\">GitHub — isovalent/credly-go</a></p>\n<h1>Overview</h1>\n<p>In this post, we'll take you step by step through the process:</p>\n<ol>\n<li>Setting up the environment and harnessing Google Cloud Functions.</li>\n<li>Initializing imports, constants, and setting up secret environment variables.</li>\n<li>Implementing the webhook and explaining each step.</li>\n<li>Setting up the webhook in Instruqt and adding signature verification to secure it.</li>\n<li>Testing locally using Docker and Docker Compose.</li>\n<li>Deploying the webhook and required secrets to Google Cloud Platform.</li>\n<li>Wrapping up with some final considerations.</li>\n</ol>\n<p>Let's dive in!</p>\n<h2>Pre-requisites</h2>\n<p>As for the first blog post, you will need an Instruqt account (with an API key) and a Google Cloud project.</p>\n<p>In addition, you will also need a Credly account with an API key this time.</p>\n<h2>Setting Up the Environment</h2>\n<p>First, create a directory for your function and initialize the Go environment.</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">mkdir instruqt-webhook\ncd instruqt-webhook\n\ngo mod init example.com/labs</code>\n        </deckgo-highlight-code>\n<p>Just as in the <a href=\"https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9\">first post</a>, we create a <code>cmd</code> directory so we can build and test the function locally:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">mkdir cmd</code>\n        </deckgo-highlight-code>\n<p>Create a <code>main.go</code> file in that directory, with the following content:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">package main\n\nimport (\n    &quot;log&quot;\n    &quot;os&quot;\n\n    // Blank-import the function package so the init() runs\n    // Adapt if you replaced example.com earlier\n    _ &quot;example.com/labs&quot;\n\n    &quot;github.com/GoogleCloudPlatform/functions-framework-go/funcframework&quot;\n)\n\nfunc main() {\n    // Use PORT environment variable, or default to 8080.\n    port := &quot;8080&quot;\n    if envPort := os.Getenv(&quot;PORT&quot;); envPort != &quot;&quot; {\n        port = envPort\n    }\n    if err := funcframework.Start(port); err != nil {\n        log.Fatalf(&quot;funcframework.Start: %v\\n&quot;, err)\n    }\n}</code>\n        </deckgo-highlight-code>\n<p>Back to the <code>instruqt-webhook</code> directory, create a file named <code>webhook.go</code> to contain the function logic. This file will serve as the webhook handler for incoming events from Instruqt.</p>\n<h2>Setting Up the Basics</h2>\n<p>In <code>webhook.go</code>, begin by adding the necessary imports, constants, and initializing the function:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">package labs\n\nimport (\n\t&quot;fmt&quot;\n\t&quot;net/http&quot;\n\t&quot;os&quot;\n\t&quot;strings&quot;\n\n\t&quot;github.com/GoogleCloudPlatform/functions-framework-go/functions&quot;\n\n\t&quot;github.com/isovalent/instruqt-go/instruqt&quot;\n\t&quot;github.com/isovalent/credly-go/credly&quot;\n\n)\n\nfunc init() {\n\tfunctions.HTTP(&quot;InstruqtWebhookCatch&quot;, instruqtWebhookCatch)\n}\n\nconst (\n    instruqtTeam = &quot;yourInstruqtTeam&quot;   // Replace with your own team name\n    credlyOrg    = &quot;yourCredlyOrg&quot;      // Replace with your own credly organization ID\n)</code>\n        </deckgo-highlight-code>\n<h2>Implementing the Webhook Receiver</h2>\n<p>Now, let's write the <code>instruqtWebhookCatch</code> function to receive the event.</p>\n<p>We will take advantage of the methods provided by the Isovalent <code>instruqt-go</code> library to manage the Instruqt webhook:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">func instruqtWebhookCatch(w http.ResponseWriter, r *http.Request) {\n\twebhookSecret := os.Getenv(&quot;INSTRUQT_WEBHOOK_SECRET&quot;)\n \twbHandler := instruqt.HandleWebhook(processWebhook, webhookSecret)\n\twbHandler(w, r)\n}</code>\n        </deckgo-highlight-code>\n<p>This function works as a proxy between the HTTP connection handler provided by the Google Cloud Functions framework and the <code>instruqt.HandleWebhook</code> method provided by Isovalent's library to manage the Svix webhook.</p>\n<p>It allows us to set up a webhook manager by passing the webhook's secret. We will see later where to find the value for the webhook secret.</p>\n<p>The <code>instruqt.HandleWebhook</code> method will automatically:</p>\n<ol>\n<li>Verify the webhook signature using svix.</li>\n<li>Parse the incoming event payload.</li>\n<li>Check if the event is valid.</li>\n<li>Retrieve the information into an instruqt.WebhookEvent structure.</li>\n</ol>\n<h2>Step 4: The <code>processWebhook()</code> Function</h2>\n<p>Next, we need to implement the <code>processWebhook</code> function, where our logic will be placed.</p>\n<p>This function will receive 3 parameters:</p>\n<ul>\n<li>the HTTP connection handlers (<code>http.ResponseWriter</code> and <code>*http.Request</code>) inherited from the GCP Function handler;</li>\n<li>the <code>instruqt.Webhook</code> structure parsed by <code>instruqt.HandleWebhook</code> and passed down to us.</li>\n</ul>\n<p>Here's the complete implementation:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">func processWebhook(w http.ResponseWriter, r *http.Request, webhook instruqt.WebhookEvent) (err error) {\n\t// Return early if the event type is not track.completed\n\tif webhook.Type != &quot;track.completed&quot; {\n\t\tw.WriteHeader(http.StatusNoContent)\n\t\treturn\n\t}\n\n\t// Setup the Instruqt client\n\tinstruqtToken := os.Getenv(&quot;INSTRUQT_TOKEN&quot;)\n\tif instruqtToken == &quot;&quot; {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\tinstruqtClient := instruqt.NewClient(instruqtToken, instruqtTeam)\n\n\t// Setup the Credly client\n\tcredlyToken := os.Getenv(&quot;CREDLY_TOKEN&quot;)\n\tif credlyToken == &quot;&quot; {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\tcredlyClient := credly.NewClient(credlyToken, credlyOrg)\n\n\t// Get user info from Instruqt\n\tuser, err := instruqtClient.GetUserInfo(webhook.UserId)\n\tif err != nil {\n\t\tfmt.Printf(&quot;Failed to get user info: %v&quot;, err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Get track details to extract badge template ID from tags\n\ttrack, err := instruqtClient.GetTrackById(webhook.TrackId)\n\tif err != nil {\n\t\tfmt.Printf(&quot;Failed to get track info: %v&quot;, err)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Extract badge template ID from track tags\n\tvar templateId string\n\tfor _, tag := range track.TrackTags {\n\t\t// Use strings.Split to parse the tag and extract the badge template ID\n\t\tparts := strings.Split(tag.Value, &quot;:&quot;)\n\t\tif len(parts) == 2 &amp;&amp; parts[0] == &quot;badge&quot; {\n\t\t\ttemplateId = parts[1]\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif templateId == &quot;&quot; {\n\t\tfmt.Printf(&quot;No badge template ID found for track %s&quot;, webhook.TrackId)\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Issue badge through Credly\n\t_, badgeErr := credlyClient.IssueBadge(templateId, user.Email, user.FirstName, user.LastName)\n\t// Check if the badge has already been issued\n\tif badgeErr != nil {\n\t\tif strings.Contains(badgeErr.Error(), credly.ErrBadgeAlreadyIssued) {\n\t\t\tfmt.Printf(&quot;Badge already issued for %s&quot;, user.Email)\n\t\t\tw.WriteHeader(http.StatusConflict)\n\t\t\treturn\n\t\t}\n\t\tfmt.Printf(&quot;Failed to issue badge: %v&quot;, badgeErr)\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n\treturn\n}</code>\n        </deckgo-highlight-code>\n<p>This function does the following:</p>\n<ol>\n<li>Check if the event is of type track.completed, exit otherwise.</li>\n<li>Instantiate Instruqt and Credly clients using environment variables for the tokens.</li>\n<li>Retrieve user information from the Instruqt API. This requires to ensure that Instruqt has that information. See the first blog post to find how to do that with a proxy.</li>\n<li>Get track information from Instruqt. We will use set a badge:&#x3C;badge_id> special tag on the track to store the Credly badge ID to issue.</li>\n<li>Parse track tags to find the badge template ID.</li>\n<li>Issue the badge using the Credly library.</li>\n</ol>\n<h2>Setting Up the Webhook on Instruqt</h2>\n<p>To enable Instruqt to call your webhook, navigate to the Instruqt UI, go to Settings -> Webhooks, and click \"Add Endpoint\" to set up a new webhook that points to your Google Cloud Function URL.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/962yseu7n7pgiwbauwyk.png\" alt=\"Create Webhook\"></p>\n<p>Select <code>track.completed</code> in the list of events to fire up this endpoint.</p>\n<p>Since we'll be hosting the function on Google Cloud Functions, the URL will be in the form <code>https://&#x3C;zone>-&#x3C;project>.cloudfunctions.net/&#x3C;name></code>. For example, if your function is called <code>instruqt-webhook</code> and is deployed in the <code>labs</code> GCP project in the <code>europe-west1</code> zone, then the URL will be <code>https://europe-west1-labs.cloudfunctions.net/instruqt-webhook</code>. If in doubt, put a fake URL and you can modify it later.</p>\n<p>Create \"Create\", then locate the \"Signing secret\" field to the right side of the panel and copy its value.</p>\n<p>Export it in your terminal as the <code>INSTRUQT_WEBHOOK_SECRET</code> value:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">export INSTRUQT_WEBHHOOK_SECRET=whsec_v/somevalueCopiedFromUi</code>\n        </deckgo-highlight-code>\n<p>Then use it to create a new GCP secret called <code>instruqt-webhook-secret</code>:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">echo -n &quot;$INSTRUQT_WEBHHOOK_SECRET&quot; | gcloud secrets create instruqt-webhook-secret --data-file=-</code>\n        </deckgo-highlight-code>\n<p>Give it the proper permissions to be usable in your function (see first blog post for details):</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">PROJECT_NUMBER=$(gcloud projects describe $(gcloud config get-value project) --format=&quot;value(projectNumber)&quot;)\ngcloud secrets add-iam-policy-binding instruqt-webhook-secret \\\n    --member=&quot;serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com&quot; \\\n    --role=&quot;roles/secretmanager.secretAccessor&quot;</code>\n        </deckgo-highlight-code>\n<p>Also create a secret for your Credly token:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">export CREDLY_TOKEN=yourCredlyToken\necho -n &quot;$CREDLY_TOKEN&quot; | gcloud secrets create credly-token --data-file=-\ngcloud secrets add-iam-policy-binding credly-token \\\n    --member=&quot;serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com&quot; \\\n    --role=&quot;roles/secretmanager.secretAccessor&quot;</code>\n        </deckgo-highlight-code>\n<h2>Testing the Code</h2>\n<p>Let's check that this function builds and runs fine.</p>\n<p>First, update your <code>go.mod</code> and <code>go.sum</code> files with:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">go get ./...\ngo mod tidy</code>\n        </deckgo-highlight-code>\n<p>Now, run the function:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">FUNCTION_TARGET=InstruqtWebhookCatch go run ./cmd/main.go</code>\n        </deckgo-highlight-code>\n<p>The function should compile and run fine. You can try sending queries to it on <code>localhost:8080</code>:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">curl -i localhost:8080</code>\n        </deckgo-highlight-code>\n<p>Expect to get an error since the Svix webhook authentication is not set up properly in the payload:</p>\n<deckgo-highlight-code   highlight-lines=\"undefined\">\n          <code slot=\"code\">HTTP/1.1 405 Method Not Allowed\nContent-Type: text/plain; charset=utf-8\nX-Content-Type-Options: nosniff\nDate: Tue, 08 Oct 2024 13:20:47 GMT\nContent-Length: 23\n\nInvalid request method</code>\n        </deckgo-highlight-code>\n<p>It would be possible to emulate this, but it's a bit complex, so let's just deploy to GCP now!</p>\n<h2>Alternative testing: using Docker</h2>\n<p>If you'd like to use Docker to test your function locally, you can create a <code>Dockerfile</code> in your current directory:</p>\n<deckgo-highlight-code language=\"dockerfile\"  >\n          <code slot=\"code\">FROM golang:1.23\n\nWORKDIR /app\n\nCOPY . .\n\nRUN go build -o myapp ./cmd/main.go\n\nENV DEV=true\nENV PORT=8080\n\nEXPOSE $PORT\n\nCMD [&quot;./myapp&quot;]</code>\n        </deckgo-highlight-code>\n<p>Add a <code>docker-compose.yaml</code> file:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">version: &#39;3&#39;\nservices:\n  proxy:\n    build: ./\n    ports:\n      - &quot;8080:8080&quot;\n    environment:\n      INSTRUQT_WEBHOOK_SECRET: ${INSTRUQT_WEBHOOK_SECRET}\n      INSTRUQT_TOKEN: ${INSTRUQT_TOKEN}\n      CREDLY_TOKEN: ${CREDLY_TOKEN}\n      FUNCTION_TARGET: InstruqtWebhookCatch</code>\n        </deckgo-highlight-code>\n<p>Finally, build and launch your container:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">docker-compose up --build</code>\n        </deckgo-highlight-code>\n<p>And you can send requests to <code>localhost:8080</code> just the same as before!</p>\n<h2>Deploy the Function</h2>\n<p>You can then deploy the function (adapt the region if needed), giving it access to all three secret values:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">gcloud functions deploy &quot;instruqt-webhook&quot; \\\n  --gen2 --runtime=go122 --region=europe-west1 --source=. \\\n  --entry-point=&quot;InstruqtWebhookCatch&quot; --trigger-http --allow-unauthenticated \\\n  --set-secrets=&quot;INSTRUQT_WEBHOOK_SECRET=instruqt-webhook-secret:latest&quot; \\\n  --set-secrets=&quot;INSTRUQT_TOKEN=instruqt-token:latest&quot; \\\n  --set-secrets=&quot;CREDLY_TOKEN=credly-token:latest&quot;</code>\n        </deckgo-highlight-code>\n<p>This will upload and build your project, and return the URL to access the function.</p>\n<p>If necessary, update the URL in your Instruqt webhook configuration.</p>\n<h2>Testing</h2>\n<p>Now for the moment of truth: testing!</p>\n<ol>\n<li>Create a badge on Credly. Publish it and copy its template ID.</li>\n<li>Add a tag to the Instruqt track you want to associate the badge with. Name the tag <code>badge:&#x3C;template_ID></code>, replacing <code>template_ID</code> with the ID you just copied.</li>\n<li>Publish the track.</li>\n<li>Take the track and complete it!</li>\n</ol>\n<p>You should get the badge in your email!</p>\n<h1>Further Considerations</h1>\n<ul>\n<li><strong>User Information</strong>: Make sure you read the <a href=\"https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9\">first blog post</a> to understand how to send user information to Instruqt.</li>\n<li><strong>Make it worth it!</strong>: Getting badges is fun, but it's better if users deserve them. Consider adding exam steps to your tracks to make earning the badges a challenge.</li>\n<li><strong>Rate Limiting and Retries</strong>: Consider rate limiting incoming webhook requests to prevent abuse and adding retry logic to handle temporary failures when interacting with Credly.</li>\n<li><strong>Manage more Events</strong>: This webhook manager only manages <code>track.completed</code> events. You can extend it to do a lot more things with all the events provided by Instruqt! I typically like to capture lots of events to send them to Slack for better visibility.</li>\n<li><strong>Logs</strong>: Consider adding more logging (for example using the GCP logging library) to the code.</li>\n</ul>"},{"url":"/posts/20241003-streamlining-access-to-embedded-instruqt-labs-4ph9/","relativePath":"posts/20241003-streamlining-access-to-embedded-instruqt-labs-4ph9.md","relativeDir":"posts","base":"20241003-streamlining-access-to-embedded-instruqt-labs-4ph9.md","name":"20241003-streamlining-access-to-embedded-instruqt-labs-4ph9","frontmatter":{"title":"Streamlining Access to Embedded Instruqt Labs","template":"post","date":"2024-10-03T11:58:29Z","excerpt":"Building a Go proxy to simplify access to embedded Instruqt labs","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzua09vi3wcpzll54airu.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzua09vi3wcpzll54airu.png","canonical_url":"https://instruqt.com/blog/guest-post-how-to-build-a-proxy-with-instruqt-go-to-simplify-instruqt-lab-automation","devto_url":"https://dev.to/raphink/streamlining-access-to-embedded-instruqt-labs-4ph9"},"html":"<p>How do you teach a very technical topic to prospects and customers? How do you make it a smooth ride?</p>\n<p>At Isovalent, we're passionate about making the learning experience as seamless as possible for our users. Isovalent are the creators of Cilium, the <em>de facto</em> cloud networking platform for Kubernetes. While we love networking and security, we appreciate folks might find it a difficult topic. We thought we would make learning Kubernetes networking fun, so we make it a point to gamify the learning experience.\nInstruqt provides a great platform to build hands-on labs that can be both technically advanced and engaging for users.</p>\n<p>We also believe the user experience should be smooth and the processes fully automated.\nFortunately, a lot can be done by leveraging the <a href=\"https://api-docs.instruqt.com/\">Instruqt graphQL API</a>.\nTo that purpose, we wrote our own <code>instruqt-go</code> library, which we've decided to open source. The library is designed to help developers automate and integrate with the Instruqt platform with ease.</p>\n<p><a href=\"https://github.com/isovalent/instruqt-go\">GitHub — isovalent/instruqt-go</a></p>\n<p>One of the issues in publishing Instruqt labs is to link user information from Instruqt with that of your own database or CRM.\nIn this first post, we’ll guide you through building a proxy using <code>instruqt-go</code> that:</p>\n<ul>\n<li>collects user identifiers (e.g., HubSpot tokens);</li>\n<li>validates user identity;</li>\n<li>redirects users to a lab with unique access tokens generated via the Instruqt API.</li>\n</ul>\n<p>We will then publish the function on Google Cloud Functions.</p>\n<h1>Why a Proxy</h1>\n<p>There are various reasons to collect user information in labs:</p>\n<ul>\n<li>It is useful to be able to generate badges (and <a href=\"https://www.credly.com/organizations/isovalent/badges\">we love badges</a>) upon completion of the lab (more to come on that in a future post).</li>\n<li>It allows to show users their progress through the labs so they know which ones to take (see for example the <a href=\"https://labs-map.isovalent.com/\">Cilium Labs Map</a>).</li>\n</ul>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5upiqyiiwbr6i5jv9lg8.png\" alt=\"Cilium Labs Map\"></p>\n<h1>How to Pass User Data</h1>\n<p>There are several methods to pass user data to Instruqt tracks.</p>\n<h2>Custom Parameters</h2>\n<p>Instruqt <a href=\"https://docs.instruqt.com/tracks/share/embed-a-track#custom-url-parameters\">custom parameters</a> are very useful to pass any kind of information when starting a track. These fields are simply added to the URL as query parameters, prefixed with <code>icp_</code>. These parameters can also be retrieved in Instruqt webhooks as well as through the Instruqt GraphQL API, making them practical to use.</p>\n<p>Until recently, Instruqt encouraged track developers to pass user information (such as name, email, or token) using custom parameters.</p>\n<p>However, there are a few downsides to using custom parameters:</p>\n<ol>\n<li>They are not standardized, and Instruqt doesn't interpret them. This means user sessions will show as anonymous in Instruqt reports (and the unique user count might be wrong).</li>\n<li>They are not encrypted by default. You can of course encrypt them for your own keys, but Instruqt will show you the encrypted value in play reports.</li>\n<li>I have seen on multiple occasions custom parameters being lost when users restart a lab. I actually started my own cache database to counter this issue.</li>\n</ol>\n<h2>Invites</h2>\n<p>Instruqt invites allow to create a list of tracks and generate an invitation link that can be shared with users for easy access. Invites can be set to collect user data via a form.</p>\n<p>This user data is then added to the user's details on Instruqt (user details are attached to user accounts, but are unique per Instruqt team).</p>\n<p>This is extremely practical for workshops, but there's again a few limitations:</p>\n<ol>\n<li>Using an invite to access all labs means that invite must contain all published labs.</li>\n<li>Invites have their own landing page, so it wouldn't work with our <a href=\"https://labs-map.isovalent.com/\">Cilium Labs map</a> or other kiosk approaches.</li>\n</ol>\n<p>Note: Instruqt recently introduced landing pages, which is a form of invites with a way to tune the landing page, with the same advantages and limitations.</p>\n<h2>Third Party Forms</h2>\n<p>Recently, Instruqt added another way to pass user information, which combines the benefits of both previous methods.</p>\n<p>The <a href=\"https://docs.instruqt.com/tracks/share/embed-a-track#add-user-details-or-gate-access-with-a-third-party-form\">encrypted PII method</a> allows to pass a <code>pii_tpg</code> query parameter to an embed URL. This means:</p>\n<ol>\n<li>The data is encrypted, using a public key provided by Instruqt, so URLs don't contain readable user information.</li>\n<li>Instruqt understands the <code>pii_tpg</code> data and has the private key to decrypt it. The information is used to fill in the user's details, just as if they had acceted an invite.</li>\n<li>This is not linked to invites, so it can be used with any track.</li>\n</ol>\n<p>We're going to use this new method in this example, as it is the most versatile today to pass information to Instruqt in a safe and reliable manner.</p>\n<h1>A Note on Embed Tokens</h1>\n<p>When you visit a track page on Instruqt, there is an option to embed the track.\nThis gives you a URL which contains a token unique to the track.</p>\n<p>While it is perfectly valid to use that URL, it also means that whoever has access to this token can start the track whenever they want.</p>\n<p>Instruqt has recently added an API call to generate one-time tokens for tracks, such that URLs using such tokens can only be used once.</p>\n<p>The proxy we're coding will use one-time tokens, since we have access to the API and can easily generate them.</p>\n<h1>Creating the Proxy</h1>\n<h2>Initial Steps</h2>\n<p>First, create a directory for your function:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">mkdir instruqt-proxy</code>\n        </deckgo-highlight-code>\n<p>Move to this directory and initialize the Go environment:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\"># Replace example.com with the prefix of your choice\ngo mod init example.com/labs</code>\n        </deckgo-highlight-code>\n<h2>Google Cloud Function Harnessing</h2>\n<p>For local testing, create a <code>cmd</code> directory:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">mkdir cmd</code>\n        </deckgo-highlight-code>\n<p>Create a <code>main.go</code> file in that directory, with the following content:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">package main\n\nimport (\n\t&quot;log&quot;\n\t&quot;os&quot;\n\n\t// Blank-import the function package so the init() runs\n  // Adapt if you replaced example.com earlier\n\t_ &quot;example.com/labs&quot;\n\n\t&quot;github.com/GoogleCloudPlatform/functions-framework-go/funcframework&quot;\n)\n\nfunc main() {\n\t// Use PORT environment variable, or default to 8080.\n\tport := &quot;8080&quot;\n\tif envPort := os.Getenv(&quot;PORT&quot;); envPort != &quot;&quot; {\n\t\tport = envPort\n\t}\n\tif err := funcframework.Start(port); err != nil {\n\t\tlog.Fatalf(&quot;funcframework.Start: %v\\n&quot;, err)\n\t}\n}</code>\n        </deckgo-highlight-code>\n<h2>Creating the Function</h2>\n<p>Back to the <code>instruqt-proxy</code> directory, create a <code>proxy.go</code> file and start by adding the <code>init()</code> function to it, along with the Go packages we will be using:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">package labs\n\nimport (\n\t&quot;fmt&quot;\n\t&quot;net/http&quot;\n\t&quot;net/url&quot;\n\t&quot;os&quot;\n\n\t&quot;github.com/GoogleCloudPlatform/functions-framework-go/functions&quot;\n\n\t&quot;github.com/isovalent/instruqt-go/instruqt&quot;\n)\n\nfunc init() {\n\tfunctions.HTTP(&quot;InstruqtProxy&quot;, instruqtProxy)\n}</code>\n        </deckgo-highlight-code>\n<p>This will allow Google Cloud Functions to call the <code>instruqtProxy</code> function when it is initialized.</p>\n<p>Let's write that function:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">const (\n\t// Replace team name with yours\n\tinstruqtTeam = &quot;isovalent&quot;\n)\n\nfunc instruqtProxy(w http.ResponseWriter, r *http.Request) {\n\tinstruqtToken := os.Getenv(&quot;INSTRUQT_TOKEN&quot;)\n\n\tif instruqtToken == &quot;&quot; {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tinstruqtClient := instruqt.NewClient(instruqtToken, instruqtTeam)\n\n\t// Get user from passed token\n\tutk := r.URL.Query().Get(&quot;utk&quot;)\n\tif utk == &quot;&quot; {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tuser, err := getUser(utk)\n\tif err != nil {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tlabSlug := r.URL.Query().Get(&quot;slug&quot;)\n\n\turl, err := getLabURL(instruqtClient, user, labSlug)\n\tif err != nil {\n\t\tw.WriteHeader(http.StatusNotFound)\n\t\treturn\n\t}\n\n\thttp.Redirect(w, r, url, http.StatusFound)\n}</code>\n        </deckgo-highlight-code>\n<p>In this function, we:</p>\n<ol>\n<li>get the Instruqt token from the <code>INSTRUQT_TOKEN</code> environment variable</li>\n<li>initialize the Instruqt API client for the token and team</li>\n<li>retrieve a <code>utk</code> parameter from the URL parameters in order to authenticate the user</li>\n<li>get user information based on that UTK</li>\n<li>get the lab slug from the URL parameters</li>\n<li>retrieve the lab URL for the redirection</li>\n<li>redirect the user using an <code>http.Redirect</code> function</li>\n</ol>\n<h2>Implement getLabURL()</h2>\n<p>The <code>getLabURL</code> function will generate the redirect URL for the lab based on user information, the requested lab slug, and dynamic information from the Instruqt API.</p>\n<p>Let's write it:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">const (\n\t// Replace with your sign-up page format\n\tlabSignupPage = &quot;https://isovalent.com/labs/%s&quot;\n\n\t// Adapt to your values\n\tfinishBtnText = &quot;Try your next Lab!&quot;\n\tfinishBtnURL  = &quot;https://labs-map.isovalent.com/map?lab=%s&amp;showInfo=true&quot;\n)\n\nfunc getLabURL(instruqtClient *instruqt.Client, u user, slug string) (string, error) {\n\ttrack, err := instruqtClient.GetTrackBySlug(slug)\n\tif err != nil {\n\t\treturn &quot;&quot;, err\n\t}\n\n\t// Unknown user\n\tif u.Email == &quot;&quot; {\n\t\turl := fmt.Sprintf(labSignupPage, slug)\n\t\treturn url, nil\n\t}\n\n\t// Get one-time token\n\ttoken, err := instruqtClient.GenerateOneTimePlayToken(track.Id)\n\tif err != nil {\n\t\treturn &quot;&quot;, err\n\t}\n\n\tlabURL, err := url.Parse(fmt.Sprintf(&quot;https://play.instruqt.com/embed/%s/tracks/%s&quot;, instruqtTeam, track.Slug))\n\tif err != nil {\n\t\treturn &quot;&quot;, err\n\t}\n\n\t// Prepare the fields to encrypt\n\tencryptedPII, err := instruqtClient.EncryptUserPII(u.FirstName, u.LastName, u.Email)\n\tif err != nil {\n\t\treturn &quot;&quot;, err\n\t}\n\n\t// Add params\n\tparams := map[string]string{\n\t\t&quot;token&quot;:             token,\n\t\t&quot;pii_tpg&quot;:           encryptedPII,\n\t\t&quot;show_challenges&quot;:   &quot;true&quot;,\n\t\t&quot;finish_btn_target&quot;: &quot;_blank&quot;,\n\t\t&quot;finish_btn_text&quot;:   finishBtnText,\n\t\t&quot;finish_btn_url&quot;:    fmt.Sprintf(finishBtnURL, track.Slug),\n\t}\n\n\tq := labURL.Query()\n\tfor key, value := range params {\n\t\tq.Set(key, value)\n\t}\n\n\t// Encode the parameters\n\tlabURL.RawQuery = q.Encode()\n\n\treturn labURL.String(), nil\n}</code>\n        </deckgo-highlight-code>\n<p>First, note that we have defined some new constants that you can tune:</p>\n<ul>\n<li><code>labSignupPage</code> is the URL on your website where unauthenticated users will be redirected. It contains a variable for the lab slug.</li>\n<li><code>finishBtnText</code> is the text shown on the finish button of the lab.</li>\n<li><code>finishBtnURL</code> is the action of the button at the end of the lab. It also contains a variable for the lab slug.</li>\n</ul>\n<p>Now let's explain the <code>getLabURL()</code> function steps:</p>\n<ol>\n<li>Retrieve track information from the Instruqt API, error if it cannot be found.</li>\n<li>If the user is unknown, redirect to sign-up page.</li>\n<li>Generate a one-time token for the embedded track access.</li>\n<li>Generate the redirect URL.</li>\n<li>Encrypt user information using the PII key from the Instruqt API.</li>\n<li>Add all parameters (one-time token, encrypted user information, finish button options) to the redirect URL.</li>\n<li>Encode the URL.</li>\n<li>Return the resulting URL.</li>\n</ol>\n<h2>The <code>getUser()</code> Function</h2>\n<p>The last piece missing in this proxy is the <code>getUser()</code> function. I can't help you much here, since this part is where you plug your own logic. You might be using a CRM like Hubspot to <a href=\"https://legacydocs.hubspot.com/docs/methods/contacts/get_contact_by_utk\">retrieve contact information from the UTK</a>, or another database, it's up to you!</p>\n<p>The code I'll show you here simply returns a sample user:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">/*\n * This is where you add the logic to get user information from your CRM/database.\n */\ntype user struct {\n\tFirstName string\n\tLastName  string\n\tEmail     string\n}\n\nfunc getUser(utk string) (u user, err error) {\n\t// Implement the logic to get your user information from UTK\n\n\tu = user{\n\t\tFirstName: &quot;John&quot;,\n\t\tLastName:  &quot;Doe&quot;,\n\t\tEmail:     &quot;john@doe.com&quot;,\n\t}\n\n\treturn u, err\n}</code>\n        </deckgo-highlight-code>\n<h1>Testing the code</h1>\n<p>Now that we have our whole <code>proxy.go</code> function, let's test it!</p>\n<p>First, update your <code>go.mod</code> and <code>go.sum</code> files with:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">go get ./...\ngo mod tidy</code>\n        </deckgo-highlight-code>\n<p>In your Instruqt dashboard, go to \"API keys\" and get the value of your API key. Export it as a variable in your shell:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">export INSTRUQT_TOKEN=&lt;your_instruqt_token&gt;</code>\n        </deckgo-highlight-code>\n<p>Next, launch the function on your local machine:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">FUNCTION_TARGET=InstruqtProxy go run ./cmd/main.go</code>\n        </deckgo-highlight-code>\n<p>Finally, in another terminal, make test requests to <code>localhost:8080</code> where your function will be running (you can pass a <code>PORT</code> environment variable above to change the port if necessary):</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">curl  -i &quot;localhost:8080/?slug=cilium-getting-started&amp;utk=someUtkValue&quot;</code>\n        </deckgo-highlight-code>\n<p>Adapt to use a track slug that exists in your Instruqt organization. If the track exists, you should get a <code>302</code> response with the redirect URL containing a one-time token for access, as well as John Doe's information encrypted with the PII key, and a one-time token (starting with <code>ott_</code>)!</p>\n<h1>Alternative testing: using Docker</h1>\n<p>If you'd like to use Docker to test your function locally, you can create a <code>Dockerfile</code> in your current directory:</p>\n<deckgo-highlight-code language=\"dockerfile\"  >\n          <code slot=\"code\">FROM golang:1.23\n\nWORKDIR /app\n\nCOPY . .\n\nRUN go build -o myapp ./cmd/main.go\n\nENV DEV=true\nENV PORT=8080\n\nEXPOSE $PORT\n\nCMD [&quot;./myapp&quot;]</code>\n        </deckgo-highlight-code>\n<p>Add a <code>docker-compose.yaml</code> file:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">version: &#39;3&#39;\nservices:\n  proxy:\n    build: ./\n    ports:\n      - &quot;8080:8080&quot;\n    environment:\n      INSTRUQT_TOKEN: ${INSTRUQT_TOKEN}\n      FUNCTION_TARGET: InstruqtProxy</code>\n        </deckgo-highlight-code>\n<p>Finally, build and launch your container:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">docker-compose up --build</code>\n        </deckgo-highlight-code>\n<p>And you can send requests to <code>localhost:8080</code> just the same as before!</p>\n<h1>Hosting the Proxy on Google Cloud Functions</h1>\n<p>In order to deploy to Google Cloud, first make sure you are logged in to your Google Cloud project:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">gcloud auth application-default login</code>\n        </deckgo-highlight-code>\n<h2>Create a Secret for the API Token</h2>\n<p>Next, let's create a new secret for the Instruqt token:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">echo -n &quot;$INSTRUQT_TOKEN&quot; | gcloud secrets create instruqt-token --data-file=-</code>\n        </deckgo-highlight-code>\n<p>In order to adjust the permissions on this secret, you will need to get your project ID:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">PROJECT_NUMBER=$(gcloud projects describe $(gcloud config get-value project) --format=&quot;value(projectNumber)&quot;)</code>\n        </deckgo-highlight-code>\n<p>Then, add the \"Secret Manager Secret Accessor\" role for the default Compute Engine service account for the project:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">gcloud secrets add-iam-policy-binding instruqt-token \\\n    --member=&quot;serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com&quot; \\\n    --role=&quot;roles/secretmanager.secretAccessor&quot;</code>\n        </deckgo-highlight-code>\n<p>Your secret is now ready to be used by the function!</p>\n<h2>Deploy the Function</h2>\n<p>You can then deploy the function (adapt the region if needed):</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">gcloud functions deploy &quot;instruqt-proxy&quot; \\\n  --gen2 --runtime=go122 --region=europe-west1 --source=. \\\n  --entry-point=&quot;InstruqtProxy&quot; --trigger-http --allow-unauthenticated \\\n  --set-secrets=&quot;INSTRUQT_TOKEN=instruqt-token:latest&quot;</code>\n        </deckgo-highlight-code>\n<p>This will upload and build your project, and return the URL to access the function.</p>\n<p>This URL will look something like <code>https://europe-west1-&#x3C;project>.cloudfunctions.net/instruqt-proxy</code>.\nYou can then test the function using that URL instead of <code>localhost:8080</code>!</p>\n<h1>Further Considerations</h1>\n<p>This is a simplified approach to the lab proxy we use at Isovalent. There are things you might want to consider with this implementation:</p>\n<ul>\n<li>If you have actual user (instead of marketing contact), switch to a proper authentication system (e.g. JWT) instead of UTK.</li>\n<li>The current implementation will give access to any lab in your collection if you know its slug. You might want to filter them out (using for example track tags).</li>\n<li>This implementation manages errors but is very basic in logging. We would recommend using Google Cloud logging to easily audit function runs.</li>\n<li>You might want to pass extra information as <a href=\"https://docs.instruqt.com/tracks/share/embed-a-track#custom-url-parameters\">custom parameters</a>. For example, we like to pass some form of event or campaign ID. These can then be retrieved via the API as part or the Track structure.</li>\n<li>If you're using a custom form and redirecting to the proxy, you might want to be sure your CRM/database has already gotten the user information. You can for example implement a retry logic in the proxy function.</li>\n<li>Invite embed URLs contain the invite ID. If you want to support invites, the proxy could take an optional <code>invite</code> parameter and add it to the URL.</li>\n</ul>\n<h1>Using the Proxy</h1>\n<p>This proxy can typically be used to give access to authenticated users in a safe way, while preserving user information in Instruqt reports and making sure embed URLs are not reusable.</p>\n<p>Here is an example of usage of this proxy:</p>\n<ol>\n<li>Set up lab sign-up pages with the form system of your choice (e.g. using Hubspot forms).</li>\n<li>Retrieve a user identifier from the page context (e.g. a <a href=\"https://knowledge.hubspot.com/privacy-and-consent/what-cookies-does-hubspot-set-in-a-visitor-s-browser\">Hubspot cookie token</a>).</li>\n<li>Redirect users to the proxy, passing the user identifier and lab slug as parameters.</li>\n</ol>\n<p>This can allow you to build a series of public sign-up pages for your labs, similar to what we have built on <a href=\"https://isovalent.com/labs/\">the Isovalent website</a>. It can also be used to build a <a href=\"https://www.youtube.com/watch?v=QEh16xu1xvc&#x26;t=3s\">Kiosk interface</a>, or even a more creative landing page such as the <a href=\"https://labs-map.isovalent.com/\">Cilium Labs map</a>, where clicks on the map redirect to the lab proxy.</p>\n<p>By making a complex networking technology like Cilium fun with our labs, we have made it the experience for users less intimidating and more approachable. Using our proxy can help you provide a similar user experience to your prospects. Please get in touch if you have any questions.</p>"},{"url":"/posts/20260409-improvisation-on-the-spectrum-1k8i/","relativePath":"posts/20260409-improvisation-on-the-spectrum-1k8i.md","relativeDir":"posts","base":"20260409-improvisation-on-the-spectrum-1k8i.md","name":"20260409-improvisation-on-the-spectrum-1k8i","frontmatter":{"title":"Improvisation on the Spectrum","template":"post","date":"2026-04-09T16:36:00Z","excerpt":"A comic strip on autism","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfjs9nojxznk8qumnbuq.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfjs9nojxznk8qumnbuq.png","canonical_url":"https://dev.to/raphink/improvisation-on-the-spectrum-1k8i","devto_url":"https://dev.to/raphink/improvisation-on-the-spectrum-1k8i"},"html":"<p>As you may know, this month is autism awareness month.</p>\n<p>Many things will be posted about autism, and I'd like to share one: a little comic strip I made in July last year, in an attempt to capture of one many invisible quirks in the daily life of an autistic person.</p>\n<p>  <img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfjs9nojxznk8qumnbuq.png\" alt=\"Intro panel\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y1yvgfrz96f761i4do8g.png\" alt=\"Panel 1\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cvz3hyk6xuqvyo6tw9m2.png\" alt=\"Panel 2\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4gmr7dmthjwyyw93khm.png\" alt=\"Panel 3\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbn52o4wqahc4woyfh9l.png\" alt=\"Panel 4\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3ju50fm4epuygnoec0n.png\" alt=\"Panel 5\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2yeiyvw35h3c5ccwnioj.png\" alt=\"Panel 6\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqqn2vaiq2zywmnkyg0s.png\" alt=\"Panel 7\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/12chseq188pv27jnz3ov.png\" alt=\"Panel 8\">\n<img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu90ner1oc2b12okefgl.png\" alt=\"Panel 9\"></p>\n<p>This is the first of a series of posts on autism that I will be posting this month.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_improvisation-on-the-spectrum-activity-7445480530982100992-3HxX\">on LinkedIn on 2026-04-02</a>.</em></p>"},{"url":"/posts/20260409-quirk-or-wiring-5a4b/","relativePath":"posts/20260409-quirk-or-wiring-5a4b.md","relativeDir":"posts","base":"20260409-quirk-or-wiring-5a4b.md","name":"20260409-quirk-or-wiring-5a4b","frontmatter":{"title":"Quirk or wiring?","template":"post","date":"2026-04-09T16:45:00Z","excerpt":"Why autism is neurological, not psychological — and why that distinction changes everything about how we think about support.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4dl9q36j2w1uupn74dx.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4dl9q36j2w1uupn74dx.png","canonical_url":"https://dev.to/raphink/quirk-or-wiring-5a4b","devto_url":"https://dev.to/raphink/quirk-or-wiring-5a4b"},"html":"<p>April is autism awareness month. Yesterday, I posted a comic strip about navigating social situations as an autistic person. I want to spend this month going deeper, with regular posts on experiencing autism.</p>\n<p>A good place to start is the difference between a psychological trait and a neurological one.</p>\n<p>A lot of autistic experiences look like personality traits from the outside. Introversion. Shyness. Social awkwardness. These are things many people relate to, and that relatability is both a bridge and a trap. It creates the impression we're talking about the same thing, just more so. They are also not helped by the common misconception that 'spectrum' means a \"vertical\" scale from normal to autistic, rather than a \"horizontal\" one within the neurological condition of autism.</p>\n<p>My wife has transverse myelitis, a condition where the myelin sheath around the spinal cord degrades, disrupting nerve signals. The result: both motor and sensory pathways are affected, and the automatic reflex arc that normally catches you when you stumble overreacts and causes her to fall. Everybody trips. Most people catch themselves without thinking. She doesn't have that fallback. She can scan the ground, plan every step, and still fall, because the pebble she didn't see hits a system that can no longer compensate automatically. The effort is real, the safety net is not present, and the fall unavoidable.</p>\n<p>Autism works on a different system — social cognition rather than motor control — but the structure is the same. Everybody has awkward social moments. Most people recover instinctively, the automatic social circuitry recalibrates. That fallback is what I don't have reliably. I can prepare, study the signals, pay close attention, and still miss something obvious to everyone else, because it happened in a channel I'm not wired to process automatically.</p>\n<p>The exhaustion isn't from interacting. It's from running manual what most people run on autopilot.</p>\n<p>And here's the point that matters: my wife's myelin isn't going to grow back. Physiotherapy helps. A cane helps with balance. But they're compensations for something that isn't there, not a cure for something that went wrong. Autism is the same. You can learn strategies, build workarounds, develop compensations, and many of us do, invisibly, for decades. But you're not fixing the wiring. You're working around it.</p>\n<p>When relating to autistic people, don't assume similar external signs mean the same internal experience and causes. Ask, and be surprised by what you'll find.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_quirk-or-wiring-april-is-autism-awareness-activity-7445840592963653632-kzMB\">on LinkedIn on 2026-04-03</a>.</em></p>"},{"url":"/posts/20220223-towards-a-modular-devops-stack-257c/","relativePath":"posts/20220223-towards-a-modular-devops-stack-257c.md","relativeDir":"posts","base":"20220223-towards-a-modular-devops-stack-257c.md","name":"20220223-towards-a-modular-devops-stack-257c","frontmatter":{"title":"Towards a Modular DevOps Stack","template":"post","date":"2022-02-23T17:37:45Z","excerpt":"DevOps Stack v1 will be modular","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facg62zfhbt0aqt7g2dk4.jpg","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facg62zfhbt0aqt7g2dk4.jpg","canonical_url":"https://dev.to/camptocamp-ops/towards-a-modular-devops-stack-257c","devto_url":"https://dev.to/camptocamp-ops/towards-a-modular-devops-stack-257c"},"html":"<p>A year and a half ago, our infrastructure team at Camptocamp was faced with an increasingly problematic situation. We were provisioning more and more Kubernetes clusters, on different cloud providers. We used Terraform to deploy the infrastructure itself, and we had started to adopt <a href=\"https://argoproj.github.io/cd/\">Argo CD</a> to deploy applications on top of the cluster.</p>\n<p>We quickly ended up with many projects using similar logic, often borrowed from older projects, and most of these cluster were starting to use divergent code.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vl7hboahfscgsbvfyit4.png\" alt=\"Diverging projects\"></p>\n<p>We thought it was time to put together a standard core in order to:</p>\n<ul>\n<li>provision Kubernetes clusters;</li>\n<li>deploy standard applications sets (monitoring, ingress controller, certificate management, etc.) on them;</li>\n<li>provide an interface for developers to deploy their applications in a GitOps manner;</li>\n<li>ensure all teams used similar approaches.</li>\n</ul>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hl1ncdyg1o7bolr9vy5z.png\" alt=\"DevOps Stack\"></p>\n<p>The <a href=\"https://devops-stack.io/\">DevOps Stack</a> was born!</p>\n<h1>🌟 The Original Design</h1>\n<p>The original DevOps Stack design was monolithic, both for practical and technical reasons.</p>\n<p>After all, we were trying to centralize best practices from many projects into a common core, so it made sense to put everything together behind a unified interface!</p>\n<p>In addition to that, all the Kubernetes applications were created using an <a href=\"https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/#app-of-apps-pattern\">App of Apps pattern</a>, because we had no ApplicationSets and no way to control Argo CD directly from Terraform.</p>\n<p>As a result, the basic interface was very simple, for example:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">module &quot;cluster&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack.git//modules/eks/aws?ref=v0.54.0&quot;\n\n  cluster_name = local.cluster_name\n  vpc_id       = module.vpc.vpc_id\n\n  worker_groups = [\n    {\n      instance_type        = &quot;m5a.large&quot;\n      asg_desired_capacity = 2\n      asg_max_size         = 3\n    }\n  ]\n\n  base_domain     = &quot;example.com&quot;\n\n  cognito_user_pool_id     = aws_cognito_user_pool.pool.id\n  cognito_user_pool_domain = aws_cognito_user_pool_domain.pool_domain.domain\n}</code>\n        </deckgo-highlight-code>\n<p>However, it could get very complex when application settings needed to be tuned!</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/12b9atbhi9iwf0fgoslm.png\" alt=\"DevOps Stack v0 Architecture\"></p>\n<p>With time, this architecture started being problematic for various reasons:</p>\n<ul>\n<li>it was hard to deactivate or replace components (monitoring, ingress controller, etc.), making it hard to extend the core features;</li>\n<li>default YAML values got quite mixed up and hard to understand;</li>\n<li>as a result, overriding default values was unnecessarily complex;</li>\n<li>adding new applications was done using <code>extra_apps</code> and <code>extra_applicationsets</code> parameters, which were monolithic and complex.</li>\n</ul>\n<p>It was time to rethink the design.</p>\n<h1>🐙 Argo CD Provider</h1>\n<p>In order to escape the App of Apps pattern, we started looking into using a <a href=\"https://registry.terraform.io/providers/oboukili/argocd/\">Terraform provider to control Argo CD resources</a>. After various contributions, the provider was ready for us to start using in the DevOps Stack.</p>\n<h1>🗺 The Plan for Modularization</h1>\n<p>Using the Argo CD provider allowed us to split each component into a separate module. In a similar way to DevOps Stack v0, each of these modules would provide Terraform code to set up the component, optionally with:</p>\n<ul>\n<li>cloud resources required to set up the component;</li>\n<li>Helm charts to deploy the application using Argo CD.</li>\n</ul>\n<h2>💡 A Full Example</h2>\n<p>As a result, the user interface is much more verbose. The previous example would thus become:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">module &quot;cluster&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack.git//modules/eks/aws?ref=v1.0.0&quot;\n\n  cluster_name = var.cluster_name\n  base_domain = &quot;demo.camptocamp.com&quot;\n  vpc_id       = module.vpc.vpc_id\n\n  cluster_endpoint_public_access_cidrs = flatten([\n    formatlist(&quot;%s/32&quot;, module.vpc.nat_public_ips),\n    &quot;0.0.0.0/0&quot;,\n  ])\n\n  worker_groups = [\n    {\n      instance_type        = &quot;m5a.large&quot;\n      asg_desired_capacity = 2\n      asg_max_size         = 3\n      root_volume_type     = &quot;gp2&quot;\n    },\n  ]\n}\n\nprovider &quot;argocd&quot; {\n  server_addr = &quot;127.0.0.1:8080&quot;\n  auth_token  = module.cluster.argocd_auth_token\n  insecure = true\n  plain_text = true\n  port_forward = true\n  port_forward_with_namespace = module.cluster.argocd_namespace\n\n  kubernetes {\n    host                   = module.cluster.kubernetes_host\n    cluster_ca_certificate = module.cluster.kubernetes_cluster_ca_certificate\n    token = module.cluster.kubernetes_token\n  }\n}\n\nmodule &quot;ingress&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack-module-traefik.git//modules/eks&quot;\n\n  cluster_name     = var.cluster_name\n  argocd_namespace = module.cluster.argocd_namespace\n  base_domain      = module.cluster.base_domain\n}\n\nmodule &quot;oidc&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack-module-oidc-aws-cognito.git//modules&quot;\n\n  cluster_name     = var.cluster_name\n  argocd_namespace = module.cluster.argocd_namespace\n  base_domain    = module.cluster.base_domain\n\n  cognito_user_pool_id     = aws_cognito_user_pool.pool.id\n  cognito_user_pool_domain = aws_cognito_user_pool_domain.pool_domain.domain\n}\n\nmodule &quot;monitoring&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack-module-kube-prometheus-stack.git//modules&quot;\n\n  cluster_name     = var.cluster_name\n  oidc             = module.oidc.oidc\n  argocd_namespace = module.cluster.argocd_namespace\n  base_domain    = module.cluster.base_domain\n  cluster_issuer = &quot;letsencrypt-prod&quot;\n  metrics_archives = {}\n\n  depends_on = [ module.oidc ]\n}\n\nmodule &quot;loki-stack&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack-module-loki-stack.git//modules/eks&quot;\n\n  cluster_name     = var.cluster_name\n  argocd_namespace = module.cluster.argocd_namespace\n  base_domain      = module.cluster.base_domain\n\n  cluster_oidc_issuer_url = module.cluster.cluster_oidc_issuer_url\n\n  depends_on = [ module.monitoring ]\n}\n\nmodule &quot;cert-manager&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack-module-cert-manager.git//modules/eks&quot;\n\n  cluster_name     = var.cluster_name\n  argocd_namespace = module.cluster.argocd_namespace\n  base_domain      = module.cluster.base_domain\n\n  cluster_oidc_issuer_url = module.cluster.cluster_oidc_issuer_url\n\n  depends_on = [ module.monitoring ]\n}\n\nmodule &quot;argocd&quot; {\n  source = &quot;git::https://github.com/camptocamp/devops-stack-module-argocd.git//modules&quot;\n\n  cluster_name   = var.cluster_name\n  oidc           = module.oidc.oidc\n  argocd         = {\n    namespace = module.cluster.argocd_namespace\n    server_secrhttps://kubernetes.slack.com/archives/C01SQ1TMBSTetkey = module.cluster.argocd_server_secretkey\n    accounts_pipeline_tokens = module.cluster.argocd_accounts_pipeline_tokens\n    server_admin_password = module.cluster.argocd_server_admin_password\n    domain = module.cluster.argocd_domain\n  }\n  base_domain    = module.cluster.base_domain\n  cluster_issuer = &quot;letsencrypt-prod&quot;\n\n  depends_on = [ module.cert-manager, module.monitoring ]\n}</code>\n        </deckgo-highlight-code>\n<h3>☸ The Cluster Module</h3>\n<p>As you can see, the main <code>cluster</code> module —which used to do all the work— is now only responsible for deploying the Kubernetes cluster and a basic Argo CD set up (in bootstrap mode).</p>\n<h3>🐙 The Argo CD Provider</h3>\n<p>We then set up the Argo CD provider using outputs from the <code>cluster</code> module:</p>\n<deckgo-highlight-code language=\"hcl\"  >\n          <code slot=\"code\">provider &quot;argocd&quot; {\n  server_addr = &quot;127.0.0.1:8080&quot;\n  auth_token  = module.cluster.argocd_auth_token\n  insecure = true\n  plain_text = true\n  port_forward = true\n  port_forward_with_namespace = module.cluster.argocd_namespace\n\n  kubernetes {\n    host                   = module.cluster.kubernetes_host\n    cluster_ca_certificate = module.cluster.kubernetes_cluster_ca_certificate\n    token = module.cluster.kubernetes_token\n  }\n}</code>\n        </deckgo-highlight-code>\n<p>This provider is used in all (or most at least) other modules in order to deploy Argo CD applications, without going through a monolithic/centralized App of Apps.</p>\n<h3>🧩 Component Modules</h3>\n<p>Each module provides a single component, using Terraform and the Argo CD provider (optionally).</p>\n<p>There are common interfaces passed as variables between modules. For example, the <code>oidc</code> variable can be provided as an output from various modules: <a href=\"https://github.com/camptocamp/devops-stack-module-keycloak\">Keycloak</a>, <a href=\"https://github.com/camptocamp/devops-stack-module-oidc-aws-cognito\">AWS Cognito</a>, etc. This <code>oidc</code> variable can then be passed to other component modules to configure the component's authentication layer.</p>\n<p>In a similar fashion, the <code>ingress</code> module, which was so far only using Traefik, can now be replaced by another Ingress Controller implementation.</p>\n<h3>🐙 The Argo CD Module</h3>\n<p>The <a href=\"https://github.com/camptocamp/devops-stack-module-argocd\">Argo CD module</a> is a special one. A special entrypoint (called <code>boostrap</code>) is used in the <code>cluster</code> module to set up a basic Argo CD using Helm.</p>\n<p>The user is then responsible for instantiating the Argo CD module a second time at the end of the stack, in order for Argo CD to manage itself and configure itself properly —including monitoring and authentication, which cannot be configured before the corresponding components are deployed.</p>\n<h2>🥾 The Bootstrap Pattern</h2>\n<p>Eventually, other modules might adopt the bootstrap pattern in order to solve chicken-and-egg dependencies.</p>\n<p>For example, <a href=\"https://github.com/camptocamp/devops-stack-module-cert-manager\">cert-manager</a> requires Prometheus Operator CRDs in order to be monitored. However, the <a href=\"https://github.com/camptocamp/devops-stack-module-kube-prometheus-stack\">Kube Prometheus Stack</a> might require valid certificates, thus depending on cert-manager being deployed. This could be solved by deploying a basic cert-manager instance (in a <code>bootstrap</code> endpoint), and finalizing the deploying at the end of the stack.</p>\n<h1>🚀 To Infinity</h1>\n<p>The modular DevOps Stack design is the current target for release 1.0.0.</p>\n<p>While this refactoring is still in beta state, it can be tested by using the <a href=\"https://github.com/camptocamp/devops-stack/tree/v1\"><code>v1</code> branch</a> of the project. You can also find examples in the <a href=\"https://github.com/camptocamp/devops-stack/tree/v1/tests\"><code>tests</code> directory</a> (though not all distributions are ported yet).</p>\n<p>Feedback is welcome, and you can contact us on the <a href=\"https://kubernetes.slack.com/archives/C01SQ1TMBST\"><code>#camptocamp-devops-stack</code> channel</a> of the Kubernetes Slack.</p>"},{"url":"/posts/20260409-the-constant-background-hum-hh7/","relativePath":"posts/20260409-the-constant-background-hum-hh7.md","relativeDir":"posts","base":"20260409-the-constant-background-hum-hh7.md","name":"20260409-the-constant-background-hum-hh7","frontmatter":{"title":"The constant background hum","template":"post","date":"2026-04-09T16:52:00Z","excerpt":"The constant background hum — why the autistic brain never quite stops gathering information, and why that has nothing to do with anxiety as most people understand it. This post covers intolerance of uncertainty, the apartment tour, the actor's name, special interests as solid ground, and why the exhaustion isn't from interacting but from the continuous cost of running a manual process on autopilot.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvxe7u8t83e9v3kxcuh4.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvxe7u8t83e9v3kxcuh4.png","canonical_url":"https://dev.to/raphink/the-constant-background-hum-hh7","devto_url":"https://dev.to/raphink/the-constant-background-hum-hh7"},"html":"<p>This is the fourth post in a series for autism awareness month. Previous posts covered the neurological vs. psychological distinction and what \"spectrum\" actually means. This week: what runs in the background, all the time.</p>\n<p>My mother often reminds me that when I was a child visiting friends, I would tour every room and every corner of their apartment before I could sit down and play. I had no idea I was doing it. I just couldn't settle until I had a complete map.\nThat pattern never went away.</p>\n<p>There's a well-documented phenomenon in autism research called intolerance of uncertainty (IU): the nervous system's difficulty tolerating absent information. The research links it directly to how the autistic brain builds predictions: less automatically, less reliably than in neurotypical brains. The result is a system that needs more explicit data to feel oriented. So it collects data, constantly.</p>\n<p>Everyone experiences some anxiety about the unknown. The difference in autism is in the mechanism and the scale. For me, the information-gathering isn't occasional, it's continuous, and it isn't very selective. Any piece of information feels potentially vital. If someone mentions an actor I don't recognize, I need to know who that is. Not out of curiosity, but because there might be a joke about that actor later. Everyone will laugh. I won't follow. And unlike most social awkwardness, I can't fake it convincingly without the underlying data. The gap isn't uncomfortable, it's a predicted failure.</p>\n<p>This is what produces the constant background hum: not anxiety about anything specific, but the nervous system permanently scanning for gaps in the map, because any gap is a potential trapdoor.</p>\n<p>Special interests are the inverse of this. A domain I know deeply is a domain with no trapdoors. The intensity and relief that come with a special interest aren't enthusiasm, they're the feeling of ground that holds. Talking about it at length isn't ignoring the other person, it's the only moment the scanning stops.</p>\n<p>There is an upside to all this collecting though. A brain that has spent decades gathering fragments from unrelated domains ends up with an unusually dense web of cross-references. When something new appears, it rarely arrives in isolation: the system has already indexed something adjacent, something that rhymes, something from three fields over. Pattern recognition and unexpected correlations come almost automatically, not as a skill but as a byproduct of the constant scan. We'll talk more about that in a following post on STEM and IT.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/feed/update/urn:li:activity:7448263254449131520/\">on LinkedIn on 2026-04-09</a>.</em></p>"},{"url":"/posts/20260409-what-autistic-spectrum-actually-means-4djb/","relativePath":"posts/20260409-what-autistic-spectrum-actually-means-4djb.md","relativeDir":"posts","base":"20260409-what-autistic-spectrum-actually-means-4djb.md","name":"20260409-what-autistic-spectrum-actually-means-4djb","frontmatter":{"title":"What \"autistic spectrum\" actually means","template":"post","date":"2026-04-09T16:48:00Z","excerpt":"What does \"autism spectrum\" actually mean? Most people picture a straight line from \"a little autistic\" to \"very autistic.\" Using the analogy of myopia vs. color blindness, this post explains why autism is a horizontal spectrum of different neurological configurations, not a vertical scale from normal to severe, and why that distinction changes what support actually looks like.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45l6ba29yzghv6mrkysx.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45l6ba29yzghv6mrkysx.png","canonical_url":"https://dev.to/raphink/what-autistic-spectrum-actually-means-4djb","devto_url":"https://dev.to/raphink/what-autistic-spectrum-actually-means-4djb"},"html":"<p>Last week, I wrote about the difference between a psychological trait and a neurological one, and why autism sits firmly in the second category. This week I want to address something that makes that distinction harder to see: a widespread misconception about what \"spectrum\" means in the context of autism.</p>\n<p>Most people hear \"autism spectrum\" and picture a line. At one end, severe autism. At the other, neurotypical. Somewhere in the middle, people like me: a little autistic, but mostly fine. The spectrum as a gradient, a dial you can turn up or down.</p>\n<p>That's not what it means. Here's an analogy that I find clarifying, using vision 👓.</p>\n<p>Most people have some degree of myopia. It's very common, it exists on a continuous scale, and crucially, it's fixable. Glasses, contact lenses, laser surgery. You correct the optics, the problem goes away. Myopia is a vertical spectrum: more or less of the same thing, with a clear correction available.</p>\n<p>Color blindness works differently. It's not that color blind people see less color than everyone else, instead they process color through a different configuration of receptors. There's no dial connecting their vision to standard vision. It's a horizontal spectrum: many different ways of being color blind, each with its own profile, none of them simply \"less\" than normal. And critically, you can't correct it. You can't give someone new cone receptors. What you can do is design the world differently: accessible interfaces, patterns alongside colors, signage that doesn't rely on red-green distinction alone.</p>\n<p>Autism is more like color blindness than myopia.</p>\n<p>The spectrum in autism isn't a scale from \"a bit autistic\" to \"very autistic\" with neurotypical at zero. It's a range of different neurological profiles, all sharing the same underlying difference in how the brain processes certain things — social signals, sensory input, pattern and context — but expressing that difference in very different ways. Some people are overwhelmed by noise; others seek it. Some struggle with eye contact; others make too much. Some have significant language delays; others are relentlessly verbal. Same underlying configuration, very different presentations.</p>\n<p>And like color blindness, it's not correctable — only compensable. You can learn strategies, build workarounds, train yourself to recognize patterns you don't process automatically. Many of us do, invisibly, for years. But you're not fixing the wiring. You're working around it.</p>\n<p>Autism asks for the same shift as color blindness. Not \"how do we correct this person\" but \"how do we design interactions, workplaces, and social environments that don't rely exclusively on automatic social processing that not everyone has.\"</p>\n<p>That's not a lowering of standards. It's a more accurate picture of human variation.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_what-autistic-spectrum-actually-means-activity-7447199002984493056-vzj7\">on LinkedIn on 2026-04-07</a>.</em></p>"},{"url":"/posts/20260410-more-autism-in-it-22l9/","relativePath":"posts/20260410-more-autism-in-it-22l9.md","relativeDir":"posts","base":"20260410-more-autism-in-it-22l9.md","name":"20260410-more-autism-in-it-22l9","frontmatter":{"title":"More autism in IT?","template":"post","date":"2026-04-10T07:34:00Z","excerpt":"Why are there so many autistic people in IT? Or does it just look that way? This post explores the genetics of autism, why certain cognitive profiles cluster in technical fields, and the personal story behind one late diagnosis. Spoiler: the people were always there. The name just arrived later.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0o6jcb3psm8irpexv9n2.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0o6jcb3psm8irpexv9n2.png","canonical_url":"https://dev.to/raphink/more-autism-in-it-22l9","devto_url":"https://dev.to/raphink/more-autism-in-it-22l9"},"html":"<p>This is the fifth post in a series for autism awareness month.</p>\n<p>Three years ago, I created a channel called <code>#autism</code> on my company's Slack. I wasn't sure what to expect. On the first day, about 20 people joined, roughly one in six of the company at the time. Four personal testimonies came in within hours. Dozens more wanted to understand better. I later renamed it <code>#neurodiversity</code> to be more encompassing.</p>\n<p>That wasn't a trend. That was a room full of people who had been waiting for the door to open.\nSo: is there more autism in IT? Or does IT just make it easier to be autistic?\nProbably both, and they're not contradictory.</p>\n<p>My wife worked for years as a psychologist with autistic children. Shortly after our second child was born, she started noticing patterns in him that she recognized professionally. It took ten years to get a formal diagnosis. When the psychiatrist finally explained why our son was on the spectrum, I remember thinking: he's describing me.</p>\n<p>That's not an unusual story. Autism has a well-documented genetic component. Autistic people are more likely to have autistic children, and certain fields, IT among them, have been quietly concentrating people with this neurological profile for decades. Long before most of them had a name for it.</p>\n<p>The cognitive traits that make social navigation harder — needing explicit structure, struggling with unspoken rules, finding small talk costly — are often the same traits that make technical work easier. Pattern recognition, deep focus on narrow problems, a preference for systems that behave predictably, a low tolerance for ambiguity that, in code, is actually a feature.</p>\n<p>IT didn't create more autistic people. It created conditions where autistic people could function, contribute, and sometimes thrive — without anyone necessarily noticing why. The diagnosis rates are rising because awareness is rising, not because something new is happening.</p>\n<p>Next Monday, I'll post about sensory overload and autistic burnout.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_the-constant-background-hum-this-is-the-activity-7447881266655281152-B9nH\">on LinkedIn on 2026-04-10</a>.</em></p>"},{"url":"/posts/20260413-when-the-sensory-threshold-moves-111p/","relativePath":"posts/20260413-when-the-sensory-threshold-moves-111p.md","relativeDir":"posts","base":"20260413-when-the-sensory-threshold-moves-111p.md","name":"20260413-when-the-sensory-threshold-moves-111p","frontmatter":{"title":"When the sensory threshold moves","template":"post","date":"2026-04-13T07:53:00Z","excerpt":"The sensory threshold in autism isn't fixed. It can move from one minute to the next — the same sound, the same touch, fine one moment and unbearable the next. This post explains why through a dinner table scene that will be familiar to many autistic people and their families, and connects it to autistic burnout: what it is, and why it often hits hardest in people who seemed fine for decades.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9luskj7j9jjb3tkunsjx.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9luskj7j9jjb3tkunsjx.png","canonical_url":"https://dev.to/raphink/when-the-sensory-threshold-moves-111p","devto_url":"https://dev.to/raphink/when-the-sensory-threshold-moves-111p"},"html":"<p>This is the sixth post in my autism awareness month series. The previous ones tried to explain mechanisms. This one is harder to write, because it's about a typical evening at the dinner table.</p>\n<p>A reader commented on post 3 that the color blindness analogy has a limit: color blindness doesn't fluctuate with how you're feeling or what's happening around you. She's right, and autism does.</p>\n<p>The sensory threshold isn't fixed. It doesn't move day to day. It can move minute to minute. The same sound that was fine an hour ago becomes unbearable. The same touch from the same person that felt fine yesterday is suddenly intolerable. Not because something changed externally, but because the internal load crossed a line that isn't visible from the outside.</p>\n<p>Here's what that looks like in practice. I'm at dinner with my family. A normal dinner, with normal quiet conversations between two adults and four children. At some point the noise becomes too much: multiple conversations, overlapping voices, a specific pitch. I ask everyone to speak a bit lower, or one at a time. They try to help: they start whispering. This actually makes it worse, because whispering is a different frequency problem, not a volume problem. So I ask again, more precisely this time. Now I'm the one enforcing rules on everyone for what looks like no good reason. I don't want to leave, as I'm genuinely interested in what's happening, and leaving would be rude. So I stay. And eventually I reach a point where I simply can't continue, and I leave the table, not dramatically, but not quietly either, because by that point I've already absorbed more than the system can handle.</p>\n<p>From the outside it looks like an overreaction to nothing. There's no visibly valid reason for me to be upset about the situation. The buildup was invisible.</p>\n<p>The coping strategies most of us have learned — don't leave abruptly, stay engaged, don't make others uncomfortable — are exactly the ones that prevent the only real regulation option available in the moment. The polite thing and the functional thing are in direct conflict. And we've been trained, often the hard way, to choose polite.</p>\n<p>Over time, extended periods of this produce what's called autistic burnout. Not laziness, not mood. A system that has been running above capacity for too long and needs to shut down. For many highly functional autistic people, it often kicks in their 40s, after decades of invisibly costly adjustment.</p>\n<p>Which raises a question: why does the same sound feel fine one moment and unbearable the next? That's what the next post is about.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/feed/update/urn:li:activity:7449362918422253569/\">on LinkedIn on 2026-04-10</a>.</em></p>"},{"url":"/posts/20260417-tasks-that-dont-make-sense-26k3/","relativePath":"posts/20260417-tasks-that-dont-make-sense-26k3.md","relativeDir":"posts","base":"20260417-tasks-that-dont-make-sense-26k3.md","name":"20260417-tasks-that-dont-make-sense-26k3","frontmatter":{"title":"Tasks that don't make sense","template":"post","date":"2026-04-17T08:15:00Z","excerpt":"Some tasks are hard. Some are boring. Some are just tasks you don't feel like doing. And then there are tasks that the brain simply refuses — not out of reluctance, but because the purpose doesn't compute. This post explores the neurological mechanism behind that block, why pushing harder makes it worse, and why the person looking back at you with a calm smile while you wait for them to comply isn't being difficult — their brain has already decided.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dnprgksyeudjwc97jhl.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dnprgksyeudjwc97jhl.png","canonical_url":"https://dev.to/raphink/tasks-that-dont-make-sense-26k3","devto_url":"https://dev.to/raphink/tasks-that-dont-make-sense-26k3"},"html":"<p>This is the eighth post in my autism awareness month series.</p>\n<p>There's a pattern many autistic people recognize but rarely name: the inability to perform tasks that don't make sense. Not tasks that are hard, or unpleasant, or boring, but tasks whose purpose doesn't compute.</p>\n<p>This is different from procrastination. Procrastination is knowing you should do something and not doing it. What happens here is closer to a blank: the brain doesn't engage because it hasn't received a valid reason to.</p>\n<p>I studied medicine for two years. My friends would put in ten-hour study days without question, and I couldn't get myself to do the same, not because I was exhausted or distracted, but because the task simply wouldn't engage. When I asked one of them why she worked so hard, she said: because my parents want me to be a doctor. There was no way my brain would let my body work that hard for that reason. It wasn't laziness, I can work intensely when things make sense. The engine just wouldn't start for this.</p>\n<p>The same pattern shows up anywhere social pressure substitutes for genuine reason: everyone else is doing it, you have no choice. Those don't satisfy the brain's actual question: what is the purpose of this, in terms I can evaluate?</p>\n<p>When that answer isn't there, the block isn't reluctance or stubbornness, it's closer to turning the key in a car with no engine. The action is available, the result isn't. What pushing harder produces is something between frustration and despair: the feeling of wanting to move, making the effort, and finding that nothing responds. The will is there. The compliance isn't available. From the outside it looks exactly like not trying.</p>\n<p>And what the observer sees often makes things worse. The autistic person may just look at you and smile, while you wait for them to do something they know they have to do and simply won't. The smile isn't defiance, it's the combined result of two absent automatic systems: the spontaneous facial mimicry that would normally adjust your expression to match the gravity of the situation, and the conscious control over that expression, which isn't available because the brain is already occupied with the block itself.</p>\n<p>There's a strength in this though. The same trait that makes arbitrary tasks impossible makes unnecessary complexity visible. The person who keeps asking \"why are we doing this?\" in a process review is often the one who finds the actual bottleneck.</p>\n<p>That smile I mentioned above, and what it looks like when authority meets a brain that doesn't have a submission reflex, will be the topic of the next post.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7450818870254358529-jfU3\">on LinkedIn on 2026-04-17</a>.</em></p>"},{"url":"/posts/20260415-the-filter-that-isnt-running-5d6d/","relativePath":"posts/20260415-the-filter-that-isnt-running-5d6d.md","relativeDir":"posts","base":"20260415-the-filter-that-isnt-running-5d6d.md","name":"20260415-the-filter-that-isnt-running-5d6d","frontmatter":{"title":"The filter that isn't running","template":"post","date":"2026-04-15T06:30:00Z","excerpt":"The autistic brain doesn't filter out irrelevant sensory input. Everything arrives at full processing weight, not louder, just unfiltered. This post explains the mechanism, why common fixes don't work, and why the problem isn't at the ear at all.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjawpgmw2jgcknfmdjyeo.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjawpgmw2jgcknfmdjyeo.png","canonical_url":"https://dev.to/raphink/the-filter-that-isnt-running-5d6d","devto_url":"https://dev.to/raphink/the-filter-that-isnt-running-5d6d"},"html":"<p>This is the seventh post in my autism awareness month series.</p>\n<p>Last week, in a shop, the music felt loud. It wasn't painful to my ears at all. But it felt like a violation of my mind, occupying space I needed for thinking. I couldn't form a coherent thought. Part of me wanted to scream for it to stop. Instead I chose to step outside.</p>\n<p>That's not a metaphor. That's what happened.</p>\n<p>Here's why. Neurotypical auditory processing includes an automatic attention filter, the brain selects what to process and suppresses the rest. It's why you can follow one conversation in a noisy room, why background music stays in the background. The cocktail party effect. The filter runs without effort or awareness.</p>\n<p>The autistic brain doesn't apply this filter the same way. Everything comes in at full processing weight. Not louder, just unfiltered. My wife could sit in the same shop and simply not notice the music after a few minutes. Not because she was trying to ignore it. Because her brain filed it as background and moved on. Mine didn't.</p>\n<p>This is why \"just ignore it\" doesn't work as advice. You can't consciously override a central processing response. The sensation isn't at the ear, it's downstream, in what the brain does with the signal. Earplugs help with some sounds but not others, because the problem isn't always volume. Loop earplugs, which I've tried, turned the music into a muffled ASMR mess, a different problem, not a solution.</p>\n<p>The whispering from the previous post fits here too. When I asked my family to speak more quietly, they whispered. That made it worse, because whispering is harder to pay attention to, not easier. The problem wasn't loudness, it was simultaneous signals at full weight, and whispering added effort on top of the load.</p>\n<p>And it's not just sound. The same mechanism applies to any sensory input: touch, light, heat, and less obviously, physical discomfort from hunger, illness, or food sensitivities. My son finds heat the most unbearable. Not because his body overheats differently, but because the thermal signal competes for processing at the same weight as everything else. Once, as a child, he was outside upset because he was hot. My wife told him to get out of the sun. He kept saying he was hot. Moving wouldn't have helped immediately, the signal was already inside, already being processed. The brain doesn't flush on command.</p>\n<p>The same logic applies to diet. Removing dairy helped my son noticeably, not because dairy causes autism, but because a food sensitivity generates internal signals that an unfiltered system has to process. Managing unnecessary inputs is load management, removing them helps with sensory overload. The wiring stays the same. You're just giving it less to handle.</p>\n<p>Next: when the brain won't comply.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7450070803813109760-KbNl/\">on LinkedIn on 2026-04-15</a>.</em></p>"},{"url":"/posts/20260420-when-authority-doesnt-compute-4bi7/","relativePath":"posts/20260420-when-authority-doesnt-compute-4bi7.md","relativeDir":"posts","base":"20260420-when-authority-doesnt-compute-4bi7.md","name":"20260420-when-authority-doesnt-compute-4bi7","frontmatter":{"title":"When authority doesn't compute","template":"post","date":"2026-04-20T06:50:00Z","excerpt":"Most people learn early that you don't look an adult in the eyes when you're being told off. For many autistic people, that reflex simply never installed. This post explores why — and what happens to the emotional cost of all that defiance that wasn't actually defiance at all.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyofsva8h0y5t7p9t2cm0.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyofsva8h0y5t7p9t2cm0.png","canonical_url":"https://dev.to/raphink/when-authority-doesnt-compute-4bi7","devto_url":"https://dev.to/raphink/when-authority-doesnt-compute-4bi7"},"html":"<p>This is the ninth post in my autism awareness month series.</p>\n<p>In the previous post, I described the autistic person who looks at you and smiles while you wait for them to comply with something they know they have to do and simply won't. Part of what produces that smile is the absence of spontaneous facial mimicry. But there's something else behind it: the submission reflex that isn't there.</p>\n<p>For most people, deference to authority is not primarily a conscious decision. It's a conditioned reflex: the body responds before the mind deliberates. Lowered gaze, softened posture, adjusted tone. These happen automatically, as a result of social conditioning that accumulates from early childhood.</p>\n<p>The autistic brain doesn't register this reflex. Not because the social training wasn't there — it often was, extensively. But no amount of training installs a reflex the nervous system doesn't have a slot for. What training produces instead is anxiety: the awareness of doing it wrong, knowing consequences are coming, but no access to the expected behavior. The performance isn't available, only the awareness of its absence is.</p>\n<p>This is why \"just learn to respect authority\" doesn't work as an instruction. The target behavior isn't a choice being withheld — it's a reflex that isn't firing. Authority without reason doesn't register as authority. It registers as an unsupported assertion, and connects directly to the previous post: the brain that won't perform a task without a valid reason also won't defer to a person without one. Same mechanism, different domain.</p>\n<p>There is an emotional cost associated to this, though it surfaces later rather than in the moment. During a high-load confrontation, the autistic brain is fully occupied processing the external situation. Emotional processing gets deferred. When it finally surfaces — sometimes hours later — it attaches to whatever small thing is happening then. A minor frustration, something completely trivial. The reaction looks disproportionate — such as tears or rage. The connection to the original cause is invisible to everyone watching because of the delay.</p>\n<p>This pattern is particularly visible in children. Most high functioning autistic adults learn over time to defer emotional processing to private moments, but the cost is still there. In undiagnosed adults, these accumulated costs are frequently misread as depression or anxiety, and sometimes treated as such, with interventions that can inadvertently make things worse.</p>\n<p>Understanding the underlying mechanism changes what help actually looks like. I'll cover that more fully in the last post of this series. In the meantime, if any of this resonates, feel free to DM me.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/feed/update/urn:li:share:7451883854363201536/\">on LinkedIn on 2026-04-20</a>.</em></p>"},{"url":"/posts/20260421-building-mcp-servers-for-genealogy-ai-powered-historical-research-261p/","relativePath":"posts/20260421-building-mcp-servers-for-genealogy-ai-powered-historical-research-261p.md","relativeDir":"posts","base":"20260421-building-mcp-servers-for-genealogy-ai-powered-historical-research-261p.md","name":"20260421-building-mcp-servers-for-genealogy-ai-powered-historical-research-261p","frontmatter":{"title":"Building MCP Servers for Genealogy: AI-Powered Historical Research","template":"post","date":"2026-04-21T16:58:46Z","excerpt":"Using Claude & MCP servers to enhance genealogy research.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ch2earn53443pwxiifw.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ch2earn53443pwxiifw.png","canonical_url":"https://dev.to/raphink/building-mcp-servers-for-genealogy-ai-powered-historical-research-261p","devto_url":"https://dev.to/raphink/building-mcp-servers-for-genealogy-ai-powered-historical-research-261p"},"html":"<p>For years now, I’ve been writing a book tracing four family branches across Europe, the Middle East, and South Africa. One thread follows Louis Rau, my 3rd great-uncle, who was president of Compagnie Continentale Edison (CCE) in the early 1900s. He was an Edison Pioneer, part of the inner circle that brought Edison's electrical systems to Europe.</p>\n<p>Last year, I found that Thomas Edison's papers were digitized at Rutgers University. So I navigated to <a href=\"http://edisondigital.rutgers.edu\">edisondigital.rutgers.edu</a>, typed \"Louis Rau\" into the search box, and hit enter, and 847 results were returned.</p>\n<p>Somewhere in those 847 documents was the correspondence that would explain Louis Rau's business relationship with Élie Moïse Léon, co-founder of CCE. Somewhere were the letters that traced his movements between Paris and Geneva. Somewhere were the details of CCE's electrical installations across Europe.</p>\n<p>But I'd have to click through them one by one, read the snippets, open promising documents, cross-reference dates, take notes, come back later and forget which ones I'd already checked…</p>\n<p>A few weeks ago, I started feeding genealogy documents to Claude AI, but that was still pretty tedious, and I kept hitting image upload limits in conversations. And then it clicked: why not build an MCP server, so Claude could perform the search directly?</p>\n<p>That question became three MCP servers, a transformed research workflow, and a fundamentally different relationship with historical archives.</p>\n<h1>First Win: The Edison Papers MCP</h1>\n<p>The Edison Papers has an API. I didn't know that initially — I just knew they had a website with a search box. But a quick look at the network tab showed clean REST endpoints returning JSON.</p>\n<p>I opened Claude Code and asked it to build an MCP server that wrapped the Edison Papers API. A few hours of iteration later, I had:</p>\n<ul>\n<li><code>edison_search</code>: Query with field-level precision (<code>creator:\"Rau, Louis\"</code>, <code>recipient:\"Léon, Élie\"</code>)</li>\n<li><code>edison_get_document</code>: Retrieve full metadata and transcriptions</li>\n<li><code>edison_browse_series</code>: Navigate document collections systematically</li>\n<li><code>edison_get_images</code>: Access high-resolution scans</li>\n</ul>\n<p><a href=\"https://github.com/raphink/edison-archive-mcp\">https://github.com/raphink/edison-archive-mcp</a></p>\n<p>Now instead of clicking through 847 results, I could ask Claude:</p>\n<blockquote>\n<p>\"Find correspondence where Louis Rau is the creator, dated 1892-1895, mentioning electrical installations or Paris operations.\"</p>\n</blockquote>\n<p>And Claude would orchestrate the full research pipeline:</p>\n<ol>\n<li><strong>Search</strong>: Call Edison Papers MCP → retrieve all matching results</li>\n<li><strong>Triage</strong>: Read all abstracts, decide which documents warrant full analysis</li>\n<li><strong>Track</strong>: Create a Notion database entry for each document with analysis status</li>\n<li><strong>Prioritize</strong>: Rank documents by relevance</li>\n<li><strong>Deep read</strong>: For priority documents, get high-resolution images and use OCR for full context</li>\n<li><strong>Summary:</strong> Provide a summary of all findings</li>\n</ol>\n<p>What would have taken hours of manual clicking, note-taking, and cross-referencing now happens in one conversation.</p>\n<p>This was immediately useful. But it surfaced a new problem: where do all these findings go?</p>\n<h1>The Organization Problem: Enter Notion MCP</h1>\n<p>I was already using Notion to organize my research: person profiles, document summaries, research questions. And Claude already had an MCP for Notion.</p>\n<p>So now when I asked:</p>\n<blockquote>\n<p>\"Search Edison Papers for Louis Rau correspondence from 1892-1895, create a Notion page summarizing the findings, and link it to Louis Rau's profile.\"</p>\n</blockquote>\n<p>Claude would:</p>\n<ol>\n<li><strong>Search</strong>: Call Edison Papers MCP → retrieve all matching results</li>\n<li><strong>Triage</strong>: Read all abstracts, decide which documents warrant full analysis</li>\n<li><strong>Track</strong>: Create a Notion database entry for each document with analysis status</li>\n<li><strong>Prioritize</strong>: Rank documents by relevance</li>\n<li><strong>Deep read</strong>: For priority documents, get high-resolution images and use OCR for full context</li>\n<li><strong>Document</strong>: Update Notion pages with findings</li>\n<li><strong>Connect</strong>: Update profile pages for people mentioned (Louis Rau, Élie Léon, etc.)</li>\n</ol>\n<p>This was amazing. Structured knowledge, automatically organized, all in one conversation.</p>\n<p>But then Claude started hallucinating.</p>\n<h1>The Hallucination Problem: Claude Needs Ground Truth</h1>\n<p>Claude would find documents mentioning for example Samuel Léon and Élie Léon, and confidently conclude that they that Samuel was Élie's nephew, completely making it up.</p>\n<p>Or it would claim someone was born in 1847 when they were actually born in 1867. Dates off by decades. Family relationships invented wholesale.</p>\n<p>The problem: Claude had access to <em>documents</em> (via Edison Papers MCP) and <em>research notes</em> (via Notion MCP), but not the actual genealogy data. It was inferring family structure from fragmentary mentions in letters and my incomplete notes.</p>\n<p>I needed to give Claude access to the tree itself, the actual source of truth about who's related to whom and when they lived.</p>\n<h1>Attempt 1: GEDCOM MCP (Local)</h1>\n<p>My family tree lives in Geni — a collaborative genealogy platform to build a unique World family tree. Geni has an API, but OAuth kept failing when I tried it and I wanted something working <em>now</em>.</p>\n<p>So I took a shortcut. From time to time, I export data from Geni to GEDCOM (the genealogy standard format), with about 25000 individuals in my export. I used airy10's GEDCOM MCP to make it queryable locally.</p>\n<p><a href=\"https://github.com/airy10/GedcomMCP\">https://github.com/airy10/GedcomMCP</a></p>\n<p>This worked! Now Claude could:</p>\n<ul>\n<li>Search for individuals by name</li>\n<li>Verify relationships (\"Is X related to Y?\")</li>\n<li>Check birth/death dates</li>\n<li>Trace lineage paths</li>\n</ul>\n<p>No more hallucinated family connections. The GEDCOM became a <strong>hypothesis database</strong>, and claims in documents could be verified against known family structure.</p>\n<blockquote>\n<p><strong>Why Geni as my main database?</strong> </p>\n<p>I use Geni instead of maintaining a private tree because genealogy is collaborative research. Multiple people contribute information, sources get peer-reviewed, duplicates get merged. A tree on Geni is a <em>shared</em> knowledge base, not siloed private data that might be duplicated (and wrong) across dozens of individual researchers' files.</p>\n</blockquote>\n<p>But the GEDCOM approach had limitations:</p>\n<ul>\n<li>It only works in Claude Desktop (local MCP)</li>\n<li>It requires manually re-exporting GEDCOM whenever the tree updated</li>\n<li>No access in <a href=\"http://claude.ai\">claude.ai</a> web sessions (or phone)</li>\n</ul>\n<p>I needed the real API.</p>\n<h1>Back to Geni: Tackling OAuth</h1>\n<p>So I went back to the Geni API. A few more hours of iteration with Claude Code, and I had:</p>\n<ul>\n<li>Full OAuth implementation (access tokens, refresh flow)</li>\n<li>13 tools: profile CRUD, relationship pathfinding, merge candidate detection, family traversal</li>\n<li>Search by name, verify relationships, trace lineage paths programmatically</li>\n</ul>\n<p><a href=\"https://github.com/raphink/geni-mcp\">https://github.com/raphink/geni-mcp</a></p>\n<p>Now I could ask mid-conversation: \"Is Samuel Léon related to Élie Moïse Léon?\" and get the relationship path instantly, whether I was in Claude Desktop or <a href=\"http://claude.ai\">claude.ai</a>.</p>\n<p>The tree became <strong>queryable context</strong> accessible anywhere, not just on my local machine with an up-to-date GEDCOM file.</p>\n<h1>Third Server: Newspapers MCP</h1>\n<p>With Edison Papers and Geni working, I could trace business connections and verify family relationships. But I was still missing contemporary context: how did the <em>public</em> see these people? What did newspapers say about CCE's operations? Were there announcements, obituaries, social mentions?</p>\n<p>Historical newspapers are digitized across dozens of national archives. Each has its own interface. Searching them all manually meant opening multiple websites, running the same query in different systems, downloading results individually.</p>\n<p>So I built a newspapers MCP that:</p>\n<ul>\n<li>Aggregates multiple national newspaper archives</li>\n<li>Searches across collections simultaneously</li>\n<li>Returns snippets as base64-encoded images (because OCR quality varies)</li>\n</ul>\n<p><a href=\"https://github.com/raphink/newspapers-mcp\">https://github.com/raphink/newspapers-mcp</a></p>\n<p><strong>Here’s a real example:</strong> </p>\n<p>I asked Claude to search for \"Joseph Dreyfus grain Paris 1895\" (a grain merchant in the family who had a financial collapse). The MCP found the <em>concordataire liquidation</em> announcement in French commercial journals. That single search led to discovering a 90-page Archives de Paris dossier (D14U³/89) I'm still analyzing.</p>\n<p>One search. Ten minutes. What would have been days of archive website navigation.</p>\n<h1>How They Work Together: Finding Solomon Rau in Munich</h1>\n<p>Here's a recent example showing how the MCPs orchestrate together:</p>\n<p>I asked Claude to search for Solomon Rau's activity in Munich newspapers. The newspapers MCP returned various results, including this advertisement:</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ch2earn53443pwxiifw.png\" alt=\"DDSG Announcement\"></p>\n<p>This ad showed Solomon Rau advertising the reimbursement of DDSG (Danube Steam Shipping Company) stock — a discovery that:</p>\n<ul>\n<li>Revealed his business activity (financial/stock trading)</li>\n<li>Connected him to DDSG, a major shipping company</li>\n<li>Provided a concrete date and location (Munich)</li>\n<li>Led to further discoveries about other family members' activities</li>\n</ul>\n<p>Claude then cross-referenced this against the Geni tree to verify Solomon's identity and relationships, and documented the finding in Notion with the newspaper snippet as a source.</p>\n<p>It then correlated it to the DDSG stock that Adolphe Grünberg, Solomon’s son-in-law, had in his post-mortem inventory the next year in 1878, and added another note there.</p>\n<p>Have you built AI integration for research yourself? What were your best findings?</p>"},{"url":"/posts/20260422-autism-and-the-genius-effect-k86/","relativePath":"posts/20260422-autism-and-the-genius-effect-k86.md","relativeDir":"posts","base":"20260422-autism-and-the-genius-effect-k86.md","name":"20260422-autism-and-the-genius-effect-k86","frontmatter":{"title":"Autism and the \"genius\" effect","template":"post","date":"2026-04-22T06:25:00Z","excerpt":"Rain Man or Sheldon Cooper — those are the two faces of autism most people know. But the vast middle is invisible, largely because it masks. This post explores the three dimensions you need to understand autism properly, why the \"genius\" stereotype is largely a visibility problem, and what actually happens when high IQ meets an insatiable information-gathering drive. Spoiler: it's a double-edged sword.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlnse5zq7micyvbiakdc.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlnse5zq7micyvbiakdc.png","canonical_url":"https://dev.to/raphink/autism-and-the-genius-effect-k86","devto_url":"https://dev.to/raphink/autism-and-the-genius-effect-k86"},"html":"<p>This is the tenth post in my autism awareness month series.</p>\n<p>When people think of autism, they often think of Rain Man or Sheldon Cooper. On one end, the severely affected person who needs full-time care. On the other, the socially awkward genius whose extraordinary abilities more than compensate. When I tell friends I am on the spectrum, I often hear: \"But you don't look autistic.\"</p>\n<p>Neither stereotype is wrong exactly. Both exist, but neither is representative. The vast middle is invisible, largely because it masks.</p>\n<p>Before we go further, let's clarify on terminology. \"Asperger's syndrome\" is no longer a clinical diagnosis: it was folded into the single autism spectrum disorder classification in 2013. It was often used as shorthand for \"high-functioning autistic,\" which is itself problematic, because functioning labels measure visibility of difficulty, not actual experience. The DSM-5 support levels measure required support, not intelligence, not severity of internal experience. A level 1 autistic person isn't mildly autistic. They're autistic in a way that currently requires less visible support, often because they've learned to compensate. That compensation has a cost that isn't measured.</p>\n<p>Now, to address the genius question.</p>\n<p>High IQ and autism are independent variables. Autism doesn't cause exceptional intelligence, and most autistic people don't have exceptional IQ. What is true though is that high-IQ autistic people are disproportionately visible: they function in professional environments, get diagnosed later, and are the face of autism in public discourse. The genius stereotype is largely a visibility problem.</p>\n<p>What autism contributes, independently of IQ, is the constant information-gathering drive that I described two weeks ago in my fourth post. That drive never reaches a point of satisfaction. High IQ adds processing power to that endless process, producing denser cross-domain connections and stronger pattern recognition as a byproduct. The IQ doesn't change the drive. It simply amplifies the output.</p>\n<p>But don't be fooled, this is a double-edged sword! The same wiring that produces unusual thinking also produces the exhaustion, the sensory overload, and the social friction described throughout this series. There is no version that keeps the upside and removes the cost. The consequences are real. But they're located in the mismatch between the wiring and the environment, not in a malfunction. And so the autistic people who seem the better socially adapted (through intellectual masking adjustments) are usually the ones experiencing the most anxiety as a result, and most likely to experience a burnout in their 30s or 40s.</p>\n<p>In short: autism is not a cause of genius, but it can work as an amplifier for high throughput brains.</p>\n<p>Next: on maintaining friendships, and what gets misread as manipulation.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7452603793260580865-FQCj\">on LinkedIn on 2026-04-22</a>.</em></p>"},{"url":"/posts/20260424-maintaining-friendships-on-the-spectrum-42lc/","relativePath":"posts/20260424-maintaining-friendships-on-the-spectrum-42lc.md","relativeDir":"posts","base":"20260424-maintaining-friendships-on-the-spectrum-42lc.md","name":"20260424-maintaining-friendships-on-the-spectrum-42lc","frontmatter":{"title":"Maintaining friendships on the spectrum","template":"post","date":"2026-04-24T07:10:00Z","excerpt":"My wife tells me I have friends. She's probably right. But I don't feel like I do — not in the way the cultural template describes. This post explores why maintaining friendships is harder than making them, what gets misread as manipulation, and the unexpected flip side of a system that doesn't store emotional weight.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ac2ylsgvz666e3r99it.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ac2ylsgvz666e3r99it.png","canonical_url":"https://dev.to/raphink/maintaining-friendships-on-the-spectrum-42lc","devto_url":"https://dev.to/raphink/maintaining-friendships-on-the-spectrum-42lc"},"html":"<p>This is the eleventh post in my autism awareness month series.</p>\n<p>My wife tells me I have friends. She's probably right. But I don't feel like I do — not in the way the cultural template describes. The deep reciprocal bond, the person you call when something happens, the relationship that persists without a reason to persist. That version of friendship feels mostly out of reach, and I've spent a long time wondering why.</p>\n<p>The answer isn't that I can't connect with people. I can, and often do, sometimes intensely. The issue is maintenance.</p>\n<p>Most people have something like a social maintenance reflex: an impulse that fires unprompted, that makes you think of someone and reach out, that keeps a connection warm between meetings. I don't have that reliably. Not because I don't care, but because the impulse simply doesn't fire. The friendship is real when the context is there: a shared project, a recurring situation, a work relationship with natural touchpoints. When the context goes, the connection quietly fades. Not from indifference. From the absence of the trigger.</p>\n<p>This produces a pattern that can look, from the outside, like something it isn't. The person who engages intensely when you're in the same context, then goes quiet when you're not. The person who seems fine, then suddenly withdraws. The person who doesn't follow up after a conflict — not because they're stonewalling, but because internally the matter felt resolved, and the follow-up impulse never came. These can get read as manipulation, as using people, as not really caring. None of them are.</p>\n<p>There's a flip side worth naming. The same system that doesn't store the emotional weight of a friendship also doesn't store the emotional weight of a conflict. Grudges don't accumulate. Wounds don't fester. When the context returns, the connection picks up with a freshness that can feel surprising to the other person — and genuine, because it is.</p>\n<p>And when the structure is there — a shared purpose, a recurring reason to engage — the intensity and loyalty that show up are real. It's not that the caring isn't there. It's that it needs something to hang on.</p>\n<p>Next post: on autism as a catalyst</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-activity-7453337458513141760-JWsl\">on LinkedIn on 2026-04-24</a>.</em></p>"},{"url":"/posts/20260427-autism-as-a-catalyst-322e/","relativePath":"posts/20260427-autism-as-a-catalyst-322e.md","relativeDir":"posts","base":"20260427-autism-as-a-catalyst-322e.md","name":"20260427-autism-as-a-catalyst-322e","frontmatter":{"title":"Autism as a catalyst","template":"post","date":"2026-04-27T07:00:00Z","excerpt":"Autism hardly create effects directly: it amplifies whatever is already present, in both directions. The same wiring that produces unusual pattern recognition also produces sensory overload. The genius effect and the burnout are not opposites; they're the same amplifier applied to different inputs. This post synthesizes the series and names the mechanism running through all of it.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcudzh77ojwa9sxqgy40k.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcudzh77ojwa9sxqgy40k.png","canonical_url":"https://dev.to/raphink/autism-as-a-catalyst-322e","devto_url":"https://dev.to/raphink/autism-as-a-catalyst-322e"},"html":"<p>This is the twelfth post in my autism awareness month series.</p>\n<p>Over the past few weeks, I've described a lot of different things: a brain that gathers information constantly, a sensory system that doesn't filter, tasks that won't start without a valid reason, authority that doesn't register, friendships that fade without a maintenance impulse. These aren't separate quirks, they're expressions of the same underlying configuration.</p>\n<p>One way to understand that configuration is through what I'd call the catalyst effect.</p>\n<p>Autism hardly create effects directly. It amplifies whatever is already present, in both directions. High IQ combined with the constant information-gathering drive produces unusual pattern recognition and cross-domain connections. A food sensitivity combined with a sensory system that doesn't suppress signals produces amplified disruption. A structured environment with clear purpose combined with deep focus and low tolerance for ambiguity produces unusual loyalty and output. The same wiring in different conditions produces different amplified results.</p>\n<p>This is why autism resists simple characterization. The genius stereotype and the burnout are not opposites, they're actually the same amplifier applied to different inputs. The person who seems to thrive and the person who is struggling may be running nearly identical hardware. What differs is the load, the environment, and how well the conditions match what the system actually needs.</p>\n<p>It also means the costs and the strengths are inseparable. There is no version of the wiring that keeps the pattern recognition and removes the sensory overload. No version that keeps the deep focus and removes the demand avoidance. The gain is turned up on everything, both the useful and the costly alike.</p>\n<p>Is autism a defect? A defect is a variation with negative consequences. The consequences here are real. But they're located in the mismatch between the wiring and the environment, not in a malfunction. The same configuration that generates friction in one context generates exceptional output in another. The question worth asking isn't how to fix the wiring, but rather how to design conditions where it can actually function.</p>\n<p>In the next post, I'll explore one of the most invisible struggles in autism: what happens when 'I don't know' doesn't mean what people think it means.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-activity-7454424089462702080-LYly\">on LinkedIn on 2026-04-27</a>.</em></p>"},{"url":"/posts/20260428-i-dont-know-the-invisible-crisis-3383/","relativePath":"posts/20260428-i-dont-know-the-invisible-crisis-3383.md","relativeDir":"posts","base":"20260428-i-dont-know-the-invisible-crisis-3383.md","name":"20260428-i-dont-know-the-invisible-crisis-3383","frontmatter":{"title":"'I don't know' — The invisible crisis","template":"post","date":"2026-04-28T09:25:00Z","excerpt":"When 'I don't know' means the ground you're being told to step on doesn't exist","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23he0dl88a6t5s1x5ds5.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23he0dl88a6t5s1x5ds5.png","canonical_url":"https://dev.to/raphink/i-dont-know-the-invisible-crisis-3383","devto_url":"https://dev.to/raphink/i-dont-know-the-invisible-crisis-3383"},"html":"<p>This is the thirteenth post in my autism awareness month series.</p>\n<p>When someone asks me what I want to eat, or which color I prefer, or what I should work on next, something happens at times that's hard to describe to people who don't experience it.</p>\n<p>From the outside, my response might look like indifference. I might say \"I don't know\" and smile quietly. To the person asking, this can read as not caring, not being engaged, not valuing their question or the interaction.</p>\n<p>But inside, something very different is happening.</p>\n<p>Most people think \"I don't know\" just needs an answer, any answer. But to the autistic brain, the question creates a demand for the right answer, and there might not be enough data to determine what that is.</p>\n<p>It's not psychological anxiety. I'm not worried about choosing wrong or disappointing someone. It's more fundamental than that. The question creates a void, an abyss with no foothold. Imagine someone climbing a wall, and another person telling them to put their foot in a hole that should be right there — but the climber cannot see or feel that hole anywhere. It's not that they're afraid to step. The ground they're being told to step on simply doesn't exist for them.</p>\n<p>\"What do you want to eat?\" sounds like a simple preference question. But my processing system receives it as: given all parameters — nutritional needs, energy levels, what I last ate, what's available, time constraints, current sensory state — what is the optimal answer? If those parameters aren't fully specified, or if I can't access my internal state clearly enough to know what my body needs, the equation can't be solved. There's no answer to give because the question, as received, is computationally incomplete.</p>\n<p>And here's where the social trap closes: while experiencing this groundlessness, this inability to locate the answer, I also don't know how to adapt my social behavior to signal what's happening. I might smile (a learned safe default) or keep a blank face (because I have no spare processing capacity for managing expressions while searching for ground that isn't there).</p>\n<p>This gets interpreted as exactly the opposite of what's happening. The person asking sees indifference. But the \"I don't know\" isn't about not caring. It might signal the opposite — needing the right answer rather than just any answer, needing sufficient information rather than being able to approximate casually.</p>\n<p>The person asking has no idea their simple question created this effect. They think they asked for a preference and got apathy in return.</p>\n<p>This is part of why the \"you can't know what you don't know\" principle matters so much. Until I understood that for neurotypical people, \"I don't know what I want to eat\" often just means \"I have no strong preference,\" I didn't realize my processing was different. I thought everyone experienced that demand for the right answer. This will be the topic of the last post tomorrow.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7454821938780123136-ukMM/\">on LinkedIn on 2026-04-28</a>.</em></p>"},{"url":"/posts/20260424-testing-gcp-cloud-functions-locally-with-docker-compose-and-summon-4p2i/","relativePath":"posts/20260424-testing-gcp-cloud-functions-locally-with-docker-compose-and-summon-4p2i.md","relativeDir":"posts","base":"20260424-testing-gcp-cloud-functions-locally-with-docker-compose-and-summon-4p2i.md","name":"20260424-testing-gcp-cloud-functions-locally-with-docker-compose-and-summon-4p2i","frontmatter":{"title":"Testing GCP Cloud Functions Locally with Docker Compose and Summon","template":"post","date":"2026-04-24T09:38:50Z","excerpt":"Testing GCP Cloud Functions locally can be tricky. This setup uses Docker and Summon to easily run them with cloud secrets.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcl6nq9y51kkg0mac08i.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcl6nq9y51kkg0mac08i.png","canonical_url":"https://dev.to/raphink/testing-gcp-cloud-functions-locally-with-docker-compose-and-summon-4p2i","devto_url":"https://dev.to/raphink/testing-gcp-cloud-functions-locally-with-docker-compose-and-summon-4p2i"},"html":"<p>I use GCP Cloud Functions quite a bit, but testing them locally can be challenging.</p>\n<p>Here's how I do it.</p>\n<h1>Package and run with Docker</h1>\n<p>The first step is to build one container per function. I use Docker for this, with two steps.</p>\n<h2>Add an executable entrypoint</h2>\n<p>My functions are written in Go, so I add a <code>cmd/main.go</code> to my codebase to run an HTTP server that calls the function logic:</p>\n<deckgo-highlight-code language=\"go\"  >\n          <code slot=\"code\">package main\n\nimport (\n\t&quot;log&quot;\n\t&quot;os&quot;\n\n\t// Blank-import the function package so the init() runs\n\t_ &quot;yourfunctionpackage&quot;\n\n\t&quot;github.com/GoogleCloudPlatform/functions-framework-go/funcframework&quot;\n)\n\nfunc main() {\n\t// Use PORT environment variable, or default to 8080.\n\tport := &quot;8080&quot;\n\tif envPort := os.Getenv(&quot;PORT&quot;); envPort != &quot;&quot; {\n\t\tport = envPort\n\t}\n\tif err := funcframework.Start(port); err != nil {\n\t\tlog.Fatalf(&quot;funcframework.Start: %v\\n&quot;, err)\n\t}\n}</code>\n        </deckgo-highlight-code>\n<h2>Orchestration</h2>\n<p>I then create a <code>docker-compose.yaml</code> file in my project to easily start and stop the stack, and expose the ports on my machine:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">version: &quot;3&quot;\nservices:\n  myfunction:\n    build: .\n    ports:\n      - &quot;8080:8080&quot;\n    environment:\n      MY_API_KEY: ${MY_API_KEY}\n      DB_PASSWORD: ${DB_PASSWORD}\n      GOOGLE_APPLICATION_CREDENTIALS: &quot;/root/.config/gcloud/application_default_credentials.json&quot;\n      FUNCTION_TARGET: MyFunction\n    volumes:\n      - &quot;~/.config/gcloud:/root/.config/gcloud:ro&quot;</code>\n        </deckgo-highlight-code>\n<h1>Secrets</h1>\n<p>The next issue is secrets. My functions are configured to take secrets as environment variables so I can pass them from GCP Secret Manager.</p>\n<p>But locally? I could be tempted to do one of these:</p>\n<ul>\n<li>Keeping a <code>.env</code> file around (and hoping you didn't commit it)</li>\n<li>Hardcoding values in your shell profile</li>\n<li>Commenting out the secret-fetching logic and replacing it manually</li>\n</ul>\n<p>But all of these create drift between local and prod, as well as --and mainly-- a risk of leaking secrets.</p>\n<p>Fortunately, there's a cleaner way.</p>\n<h2>The Pattern</h2>\n<p>Three tools, working together:</p>\n<ul>\n<li><strong>GCP Secret Manager</strong> — single source of truth for secret values</li>\n<li><strong><a href=\"https://cyberark.github.io/summon/\">Summon</a></strong> — injects secrets as env vars at process startup, without writing them to disk</li>\n<li><strong>Docker Compose</strong> — builds and runs your functions locally</li>\n</ul>\n<p>The result: <code>summon docker compose up</code> — and your containers get the same secrets they'd get in production, pulled live from Secret Manager.</p>\n<h2>What It Looks Like</h2>\n<h3>The summon GCP plugin</h3>\n<p>Summon is a pluggable tool. You can provide your own script to map between your secret vault, the keys you want to pass, and the values you want to retrieve.</p>\n<p>In our case, the vault is GCP Secret Manager, and I want to retrieve secrets by passing the project name and secret name, so I have a plugin that looks like this:</p>\n<deckgo-highlight-code language=\"bash\"  >\n          <code slot=\"code\">#!/bin/bash\n# Install this file in /usr/local/lib/summon/gcloud\n\nread -r PROJECT SECRET VERSION &lt;&lt;&lt;$@\n\ngcloud secrets versions access &quot;$VERSION&quot; --project=&quot;$PROJECT&quot; --secret=&quot;$SECRET&quot;</code>\n        </deckgo-highlight-code>\n<p>Make this script executable and place it in the Summon library (typically <code>/usr/local/lib/summon/</code>), and you're ready for the next step! </p>\n<h3><code>secrets.yml</code></h3>\n<p>This is the only file you maintain. It maps environment variable names to GCP secret paths:</p>\n<deckgo-highlight-code language=\"yaml\"  >\n          <code slot=\"code\">MY_API_KEY: !var my-project my-api-key latest\nDB_PASSWORD: !var my-project db-password latest</code>\n        </deckgo-highlight-code>\n<p>When called, Summon will read this file and resolve each <code>!var</code> entry by calling the configured secret provider we've just configured above.</p>\n<p>Couple this with the Docker Compose file we wrote earlier, and all you need to do is:</p>\n<deckgo-highlight-code language=\"shell\"  >\n          <code slot=\"code\">summon docker compose up</code>\n        </deckgo-highlight-code>\n<p>That's it! Summon resolves the secrets, exports them into the environment, and Docker Compose inherits them. No <code>.env</code> file, no plaintext values on disk.</p>\n<h1>The Deploy Side</h1>\n<p>In production, your deploy script simply needs to map the same secret names as env vars using the <code>--set-secrets</code> flag:</p>\n<deckgo-highlight-code language=\"bash\"  >\n          <code slot=\"code\">gcloud functions deploy my-function \\\n  --set-secrets MY_API_KEY=my-api-key:latest \\\n  --set-secrets DB_PASSWORD=db-password:latest \\\n  ...</code>\n        </deckgo-highlight-code>\n<p>Just make sure the secrets are shared with the compute service account so it's allowed to access them when starting the functions.</p>"},{"url":"/posts/20260429-you-cant-know-what-you-dont-know-37ap/","relativePath":"posts/20260429-you-cant-know-what-you-dont-know-37ap.md","relativeDir":"posts","base":"20260429-you-cant-know-what-you-dont-know-37ap.md","name":"20260429-you-cant-know-what-you-dont-know-37ap","frontmatter":{"title":"You can't know what you don't know","template":"post","date":"2026-04-29T07:30:00Z","excerpt":"This series almost didn't happen. A comic strip, a colleague's question, and thirteen posts later: here's what masking actually is, why the right framework matters more than most people realise, and what to do if any of this sounded familiar. Includes a link to a free RAADS-R screening tool.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flchkputvkzu3wxg4lybf.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flchkputvkzu3wxg4lybf.png","canonical_url":"https://dev.to/raphink/you-cant-know-what-you-dont-know-37ap","devto_url":"https://dev.to/raphink/you-cant-know-what-you-dont-know-37ap"},"html":"<p>This is the fourteenth and final post in my autism awareness month series.</p>\n<p>This series almost didn't happen. I shared a comic strip on April 2nd because it was autism awareness day. A colleague asked a question, I answered, and somehow that turned into thirteen posts.</p>\n<p>That's fitting, actually. The whole premise of the series is that you can't know what you don't know, and the series itself is proof of it. I didn't plan to spend a month writing about autism. I didn't know there was this much to say, or that this many people were waiting to hear it.</p>\n<p>What you've been reading across these posts is what's called masking. Every post described a different aspect of it: the information-gathering that runs constantly, the sensory system that doesn't filter, the tasks that won't start without a valid reason, the submission reflex that isn't there, the friendships that fade without a maintenance impulse. None of it is a character flaw. All of it is compensation: conscious or unconscious strategies developed over a lifetime to navigate a world not designed for this wiring.</p>\n<p>Masking has a cost. Extended periods of running above capacity produce what's called autistic burnout: not laziness, not a mood, a system that needs to shut down. In undiagnosed adults, that burnout is frequently misread as depression or anxiety, and treated as such. The problem: treatments designed for depression can make autistic burnout worse. The framework matters. And most people never get the right one, partly because the system doesn't make it easy — GPs often discourage diagnosis in adults, particularly those who are managing well from the outside.</p>\n<p>But managing well from the outside is exactly the profile most at risk. The higher the masking, the later the crisis, and the more accumulated cost when it arrives.</p>\n<p>Diagnosis changes the framework. Not \"what is wrong with me psychologically\" but \"what does my nervous system actually need.\" That reframe changes what help looks like, which changes whether help actually helps. You don't need the diagnosis today. But at some point, the cost of running above capacity will come due. Having the right framework before that moment is the difference between understanding what's happening and spending years in the wrong treatment.</p>\n<p>If anything in this series resonated, the RAADS-R is a good starting point. It's a clinically validated screening tool — not a diagnosis, but a signal worth taking seriously.</p>\n<p>I built a free tool at <a href=\"https://raphink.github.io/raads-r/\">https://raphink.github.io/raads-r/</a> that walks you through it and generates an AI-enhanced report. It's free to use, though report generation has a cost on my end. Generate ahead if you can, and if it doesn't work, feel free to DM me.</p>\n<p><a href=\"https://github.com/raphink/raads-r\">https://github.com/raphink/raads-r</a></p>\n<p>You can't know what you don't know. But now you have a door.</p>\n<p><em>This is part of my April 2026 autism awareness month series. First published <a href=\"https://www.linkedin.com/posts/raphink_autism-autismawareness-actuallyautistic-share-7451734247188492289-0Dqf\">on LinkedIn on 2026-04-29</a>.</em></p>"},{"url":"/posts/20260522-neurodiversity-and-the-two-layers-of-cognition-42dp/","relativePath":"posts/20260522-neurodiversity-and-the-two-layers-of-cognition-42dp.md","relativeDir":"posts","base":"20260522-neurodiversity-and-the-two-layers-of-cognition-42dp.md","name":"20260522-neurodiversity-and-the-two-layers-of-cognition-42dp","frontmatter":{"title":"Neurodiversity and the two layers of cognition","template":"post","date":"2026-05-22T10:46:47Z","excerpt":"What if the way you think and the output you produce are two completely separate things — and the gap between them has a cost? A post about internal process, expected output, and what autism adds to the picture.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sbnva9jyujcu145d3s5.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1sbnva9jyujcu145d3s5.png","canonical_url":"https://dev.to/raphink/neurodiversity-and-the-two-layers-of-cognition-42dp","devto_url":"https://dev.to/raphink/neurodiversity-and-the-two-layers-of-cognition-42dp"},"html":"<p>About twenty years ago, while studying Gestion Mentale, a pedagogy framework developed by French educator Antoine de la Garanderie, our group was asked to do a simple mental calculation. Something like 47+35. Then explain what happened in our heads.</p>\n<p>The results were staggering.</p>\n<p>One person talked to themselves through it. Another wrote the numbers on an imaginary blackboard, in their own handwriting. Someone else saw their primary school teacher's handwriting instead. One person had visual bars and immediate access to the result, with no intermediate steps they could describe. Ten people, ten different internal processes, one correct answer.</p>\n<p>Nobody had assumed everyone else did it the same way. But nobody had ever questioned it either, because there had never been a reason to ask.</p>\n<p>That exercise made something visible that is almost always invisible: the internal process and the expected output are two separate things, and they don't have to map onto each other in any particular way.\nI saw this confirmed during an internship with a Gestion Mentale practitioner working with a child struggling with long divisions. Over several sessions they had found a method that worked for the child, that he understood and could use reliably. Then the teacher called him to the board, he used his method, and she dismissed it. She hadn't taught it, she didn't recognize it, so as far as she was concerned it wasn't valid.</p>\n<p>The practitioner spent the next session reframing things. There are two layers, he told the child: how you perform a task, and what the world expects as output. Those are separate problems. Let's find a way to convert your method into the expected format.</p>\n<p>I've recently been writing about autism, following my own diagnosis at 43. Autism adds a cost to this picture. When your internal process doesn't map naturally onto what the social world expects as output, there is a translation layer running constantly — reading faces, calibrating tone, tracking when to speak and when to stop. For most people this is automatic, effectively free. For an autistic person it runs consciously, alongside every interaction.</p>\n<p>The result looks the same. The cost doesn't show. Which is why late diagnosis is so common: the output passes inspection, so nobody looks at the process.</p>\n<p>Happy to hear how this lands — particularly from those of you who recognize the translation cost from your own experience, whether or not you have a diagnosis.</p>"},{"url":"/posts/20260525-sharing-is-caring-1bif/","relativePath":"posts/20260525-sharing-is-caring-1bif.md","relativeDir":"posts","base":"20260525-sharing-is-caring-1bif.md","name":"20260525-sharing-is-caring-1bif","frontmatter":{"title":"Sharing is caring","template":"post","date":"2026-05-25T06:45:00Z","excerpt":"Autistic people who talk at length about their interests are often read as self-centered. I think the mechanism is almost the opposite. A follow-up to my series on autism from the inside, on why information has survival-level value, why small talk feels like noise pollution, and why sharing is, quite literally, how a lot of us care.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk18ub0fjm2t6qn78qpl.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk18ub0fjm2t6qn78qpl.png","canonical_url":"https://dev.to/raphink/sharing-is-caring-1bif","devto_url":"https://dev.to/raphink/sharing-is-caring-1bif"},"html":"<p>Last month, I wrote a series on autism from the inside. The response surprised me — several people said it helped them understand someone in their life, or themselves. This is a follow-up on something I've been thinking about since.</p>\n<p>Autistic people who talk at length about their interests are easy to misread. They seem oblivious to whether you're engaged. They circle back to the same subject even after the conversation has moved on. From the outside, it looks like self-centeredness: I want to talk about this, so I will.</p>\n<p>I suggest the mechanism is almost the opposite.</p>\n<p>For an autistic brain, information isn't incidental. It's closer to what navigation instruments are to a pilot: not nice to have, but required to function. I've written before about the constant background scan — the nervous system collecting data continuously, because any gap in the map is a potential trapdoor. That's the context this runs in.</p>\n<p>If information has that level of value — survival-level value, not intellectual curiosity — then sharing information is the most generous thing you can do. When I've spent weeks or months going deep on a subject, and I find something that genuinely matters, the instinct to share it isn't \"let me talk about myself.\" It's closer to \"I found something you need to know.\"</p>\n<p>It's \"I found water.\"</p>\n<p>The social form looks like monologue. The underlying intent is contribution, especially when this knowledge contributes to lowering anxiety, consciously or not.</p>\n<p>This also explains the other side of the equation: small talk. The common framing is that autistic people find it boring, or that we prefer \"meaningful\" conversation. That's not quite it either. The information channel runs in survival mode. Low-signal input — the weather, a filler comment, a pleasantry that carries no new information — doesn't just fail to help. It occupies bandwidth that the system is trying to keep clear. It's not boredom. It's closer to noise pollution in a critical instrument.</p>\n<p>The frustration when no one seems interested in what you're sharing, and the discomfort with small talk, come from the same place: a brain that has assigned extreme functional value to information, operating in a world where most social exchange treats information as incidental.</p>\n<p>None of this makes the social friction disappear. Knowing the mechanism doesn't mean the other person stops feeling talked over. But it changes the question. The question isn't \"why are they so self-absorbed?\" The question is: what does it mean to be generous when your native currency isn't attention, but knowledge?</p>\n<p>For a lot of autistic people, sharing is genuinely how we care. We're just not always aware that the gift doesn't always land the way it was meant.</p>"},{"url":"/posts/20260527-rest-is-not-what-you-think-1mbc/","relativePath":"posts/20260527-rest-is-not-what-you-think-1mbc.md","relativeDir":"posts","base":"20260527-rest-is-not-what-you-think-1mbc.md","name":"20260527-rest-is-not-what-you-think-1mbc","frontmatter":{"title":"Rest is not what you think","template":"post","date":"2026-05-27T08:25:00Z","excerpt":"Rest as low novelty load, not low stimulation — known vs unknown inputs, threat/navigation system, beach vs metal music","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6afefi210ooru3siqw0q.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6afefi210ooru3siqw0q.png","canonical_url":"https://dev.to/raphink/rest-is-not-what-you-think-1mbc","devto_url":"https://dev.to/raphink/rest-is-not-what-you-think-1mbc"},"html":"<p>My wife says she's never seen me rest.</p>\n<p>She's not wrong, exactly. But I think we've been working with different definitions.</p>\n<p>When someone tells me to rest, they usually mean: do something calm. Low stimulation. A walk in nature, a beach, quiet time.</p>\n<p>For a long time I couldn't explain why that didn't work for me. It wasn't that I didn't want to rest. It was that what counted as rest seemed to be defined by other people's nervous systems, not mine.</p>\n<p>Here's what I eventually figured out.</p>\n<p>Rest, for me, is not about low stimulation. It's about low novelty load on the threat and navigation system.</p>\n<p>I can blast metal music at full volume through headphones and come out of it genuinely restored. Not despite the intensity, because of something specific about it: my brain knows every note, every transition, every moment of that record. It keeps registering \"I know this. I know this. I know this.\" No decisions required. No scanning. The system can stop.</p>\n<p>A beach is the opposite. It looks calm. But for my nervous system it's a continuous stream of unclassified inputs: people passing at unpredictable intervals, sounds I don't recognize, movements in peripheral vision, social situations I might need to navigate. My brain doesn't get to stop. It has to keep evaluating, filing, preparing. That's not rest. That's work with good lighting.</p>\n<p>The axis isn't intensity versus calm. It's known versus unknown. More precisely: does this input require a response decision, or not?</p>\n<p>This connects to something I've written about before — the background scan, the nervous system running in a kind of permanent low-level threat detection mode. What that system needs to rest isn't silence. It needs to be released from decision load. Familiar music does that. An unfamiliar environment doesn't, regardless of how peaceful it looks from the outside.</p>\n<p>I think this is why a lot of autistic people have very specific, sometimes surprising rest rituals that look nothing like relaxation to others. It's not quirk or preference. It's load management.</p>\n<p>The well-meaning advice to \"just relax\" or \"get some fresh air\" isn't wrong about rest being necessary. It's using a definition of rest built for a different nervous system.</p>\n<p>What actually restores you might look nothing like what's supposed to.</p>"},{"url":"/posts/20260529-two-survival-systems-two-empathy-modes-5nl/","relativePath":"posts/20260529-two-survival-systems-two-empathy-modes-5nl.md","relativeDir":"posts","base":"20260529-two-survival-systems-two-empathy-modes-5nl.md","name":"20260529-two-survival-systems-two-empathy-modes-5nl","frontmatter":{"title":"Two survival systems, two empathy modes","template":"post","date":"2026-05-29T06:56:49Z","excerpt":"The double empathy problem, made concrete: not a deficit on one side, but two coherent systems that are mutually opaque to each other.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft97sq9zsksudme0uxtep.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft97sq9zsksudme0uxtep.png","canonical_url":"https://dev.to/raphink/two-survival-systems-two-empathy-modes-5nl","devto_url":"https://dev.to/raphink/two-survival-systems-two-empathy-modes-5nl"},"html":"<p>Here are two scenes. They look unrelated. They're not.</p>\n<h1>Scene 1</h1>\n<p>Two people at a café, talking about a restaurant they want to try. A stranger walking past stops: \"That place closed six months ago. The one on the corner is better.\" A brief nod, and they walk on.</p>\n<p>The two people exchange a glance, taken aback. Why did that person stop? What did they want?</p>\n<p>A few steps away, the stranger is also confused. They had useful information. They shared it. Why did these people react so strangely?</p>\n<h1>Scene 2</h1>\n<p>A colleague is visibly stressed, describing a difficult situation at work. One friend pulls their chair closer, puts a hand on their arm: \"That sounds really hard.\" Another opens their laptop: \"I found something that might help — HR has a process for exactly this, I'll send you the link.\"</p>\n<p>The colleague leans into the first. Glances uncertainly at the second.</p>\n<p>The second person doesn't understand why sitting close and saying \"that sounds hard\" counts as helping. You haven't solved anything. The first doesn't understand why anyone would respond to distress with links.</p>\n<hr>\n<p>Both scenes end the same way: people on both sides convinced they did the right thing, confused by the other's reaction. The mismatch is mutual and invisible from the inside.</p>\n<h1>Two survival instincts, two empathy systems</h1>\n<p>For many <em>autistic</em> people, information is a <strong>survival</strong> mechanism. Uncertainty is threat, missing information is a vulnerability, and the drive to correct and share runs below conscious awareness. <strong>Empathy, expressed through that system</strong>, looks like giving someone what keeps you safe: accurate information, solutions, resources. The social preamble before sharing — announcing yourself, softening the approach — doesn't arise as a concept. Why would useful information require an introduction?</p>\n<p>For many <em>neurotypical</em> people, social safety is a <strong>survival</strong> mechanism. Group cohesion and reading others accurately are what keep people safe. <strong>Empathy, expressed through that system</strong>, looks like presence: mirroring distress, making someone feel held, maintaining the social fabric. An uninvited approach from a stranger bypasses the protocol that signals safe intent — and that protocol isn't a nicety, it's the unlock code. Without it, the content can't land regardless of how useful it is.</p>\n<p><img src=\"https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ll3p3bkxfe9auwyvxzoq.png\" alt=\"Image description\"></p>\n<p>The social preamble is as foreign a concept to the autistic person as the direct approach is unsettling to the neurotypical person. The information response is as opaque to the neurotypical person as emotional attunement is to the autistic person. Neither protocol is natural to the other system. The incomprehension runs in both directions, with equal depth.</p>\n<h1>Milton's double empathy problem</h1>\n<p>In 2012, autistic researcher Damian Milton described what he called the double empathy problem: cross-neurotype communication difficulties aren't a deficit on one side, they're a mismatch between two coherent systems that are mutually opaque to each other. Historically, the autistic side has been asked to compensate, the neurotypical system treated as the default rather than as one particular survival logic among two.</p>\n<p>What these two scenes show is that both sides are trying to care for the other, each in the only language their system knows, and neither is being received as care.</p>\n<p>That's not a deficit. That's two survival systems, built for different threats, each expressing empathy in the only currency it has.</p>"},{"url":"/posts/20260530-why-autism-hasnt-disappeared-a-hypothesis-1b6g/","relativePath":"posts/20260530-why-autism-hasnt-disappeared-a-hypothesis-1b6g.md","relativeDir":"posts","base":"20260530-why-autism-hasnt-disappeared-a-hypothesis-1b6g.md","name":"20260530-why-autism-hasnt-disappeared-a-hypothesis-1b6g","frontmatter":{"title":"Why autism hasn't disappeared — a hypothesis","template":"post","date":"2026-05-30T14:38:16Z","excerpt":"Why has autism persisted across all human populations, across cultures, across centuries? A hypothesis: not because of individual advantage, but because groups that contained the profile were more robust against a failure mode the majority profile generates in itself.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc07999xlm6s98n639esf.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc07999xlm6s98n639esf.png","canonical_url":"https://dev.to/raphink/why-autism-hasnt-disappeared-a-hypothesis-1b6g","devto_url":"https://dev.to/raphink/why-autism-hasnt-disappeared-a-hypothesis-1b6g"},"html":"<p>In the <a href=\"https://dev.to/raphink/two-survival-systems-two-empathy-modes-5nl\">previous post</a>, I described two survival systems: one oriented toward information, one toward social cohesion. The autistic information drive produces friction: the café stranger, the colleague with links when you needed a hug. But it also produces something else.</p>\n<p>Picture a tribal council. The leader has spoken. The plan is agreed. Everyone around the fire has nodded. One person says: \"But the herd tracks go the other way.\"</p>\n<p>Silence. Eyes. The weight of having contradicted the chief in front of everyone.</p>\n<p>The tracks went the other way.</p>\n<h1>The puzzle</h1>\n<p>Autism has strong genetic components — spread on many genes, with expression influenced by environmental factors. It runs in families, it clusters, it transmits. It tends to isolate affected individuals, and yet it persists at consistent rates across all human populations, across cultures, and most likely across centuries. This is the kind of pattern that usually means something.</p>\n<p>The conventional explanations for its persistence focus on the individual level: genetic linkage to advantageous traits, cognitive advantages in pattern recognition and systemizing, the so-called \"geek gene\" hypothesis. These are real and worth taking seriously. But they explain individual fitness. What if the answer isn't at the individual level at all, what if the whole group benefited enough from the presence of autistic members that it played in favor of the group's survival?</p>\n<h1>The group-level argument</h1>\n<p>A group where everyone runs the social-harmony calculation before speaking has a specific, predictable blind spot. Information gets suppressed to preserve group comfort. Errors go uncorrected because correcting them would embarrass someone senior. The plan with the structural flaw gets executed because no one wanted to be the one who said so in front of the chief.</p>\n<p>This is not a theoretical failure mode. It has a name —groupthink— with documented consequences. And it operates precisely through the mechanism that makes neurotypical social cohesion work so well in stable conditions: the instinct to read the room, preserve relationships, avoid disruption.</p>\n<p>The autistic information drive doesn't have that brake. Not because of courage or contrarianism, but because the system that would weigh social cost against information value simply doesn't run first. The information is there, it needs to be shared, and the authority of the person in front of you is not a relevant variable.</p>\n<p>The person around the fire says the tracks go the other way. Not to challenge the chief. Not to make a point. Because the tracks go the other way and that is the only thing that matters: to speak the facts.</p>\n<h1>The genetic angle</h1>\n<p>You don't need to resolve the group selection debate in evolutionary biology to find this argument useful. There's a simpler version.</p>\n<p>Autism is heritable. Which means some groups, by genetic chance, would have had more of this profile and some would have had less — either by the higher presence of some autism-related genes, or by the presence of more such biological markers in the population. A group that happened to lack it entirely would have been exposed to the groupthink failure mode with no one to interrupt it. Information suppressed. Errors uncorrected. The tracks followed in the wrong direction.</p>\n<p>Over generations, over crises, over the moments when accuracy mattered more than social harmony, that asymmetry would have had consequences.</p>\n<p>This isn't a claim that autistic individuals were selected for. It's an observation that groups containing the profile were more robust against a specific and recurring failure mode that the majority profile generates in itself.</p>\n<h1>What this changes</h1>\n<p>The usual framing of autism and society runs in one direction: here is a profile that generates difficulties, how do we accommodate it. That framing isn't wrong, but it's incomplete.</p>\n<p>The profile that makes someone say the tracks go the other way, correct the stranger's restaurant recommendation, flag the error in the meeting when everyone has moved on — that profile is costly in stable, low-stakes social conditions. It produces exactly the friction described in the previous post. But a group running only the social cohesion system is vulnerable in a specific direction, and has been throughout human history.</p>\n<p>The autistic people who drove everyone slightly mad in ordinary times may have been the ones who said the thing that needed saying when it mattered most.</p>\n<h1>A note on epistemic honesty</h1>\n<p>This is a hypothesis. The genetic heritability of autism is established. The groupthink failure mode is documented. The observation that cross-neurotype groups might be more robust is plausible and consistent with broader neurodiversity research. But the specific claim — that autism persists in part because groups containing it outperformed groups that didn't — is an original assembly of solid components, not a finding from the literature.</p>\n<p>I'm offering it as a frame worth considering, not a conclusion worth defending. If you work in evolutionary biology or behavioral genetics and think I'm wrong, I'd genuinely like to know why.</p>"},{"url":"/posts/20260611-gen-ai-is-an-amplification-machine-d54/","relativePath":"posts/20260611-gen-ai-is-an-amplification-machine-d54.md","relativeDir":"posts","base":"20260611-gen-ai-is-an-amplification-machine-d54.md","name":"20260611-gen-ai-is-an-amplification-machine-d54","frontmatter":{"title":"Gen AI is an Amplification Machine","template":"post","date":"2026-06-11T08:41:34Z","excerpt":"Most people misunderstand what generative AI actually does. It doesn't add quality — it multiplies what's already there. Which means the people it serves best are the ones who least needed it to begin with.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq4rq8s4q8vzet5us2uc.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq4rq8s4q8vzet5us2uc.png","canonical_url":"https://dev.to/raphink/gen-ai-is-an-amplification-machine-d54","devto_url":"https://dev.to/raphink/gen-ai-is-an-amplification-machine-d54"},"html":"<p>I've been using generative AI pretty much daily for months now. And I've come to think most people fundamentally misunderstand what it does.</p>\n<p>Gen AI is an amplification machine.</p>\n<p>Give it good ideas, and it will help you express them better than you could alone. Give it bad ideas, and it will make them worse, more confidently, with better grammar. Give it nothing — no ideas, no voice, no judgment — and you'll get slop. Averaged mediocrity, the statistical center of human expression, smoothed of anything surprising or true.</p>\n<p>The tool doesn't add quality. It multiplies what's already there, as a catalyst.</p>\n<p>Which is why the people I see getting the most genuine value from it are almost always people who were already creating and thinking seriously before it existed. Not because the tool is gatekept, but because the gate was never the tool. It was always the ideas. The voice. The judgment that knows when something is good and when it only looks good.</p>\n<p>I use it as a mind extension. I drop half-formed thoughts into a conversation, and something comes back that helps me see what I was actually trying to say. It holds more threads simultaneously than I can. It pushes back from angles I hadn't considered. It drafts the sentence I was circling around for twenty minutes.</p>\n<p>But it has never once given me a novel idea I didn't already have in some latent form. It has never surprised me with an insight I couldn't recognize as mine once it appeared. The correlation happens in my head. The tool just removes the friction between having the thought and getting it out of my head in a form others can use.</p>\n<p>I don't judge the tool by how much content it helps me produce. I judge it by whether I think better because of it. Whether a conversation sharpened an argument I'd been carrying half-formed for months. Whether pushing back on a draft helped me find what I actually believed. Whether I understand something today that I didn't yesterday.</p>\n<p>There's a word for what it produces when there's nothing to amplify: mediocre. Not bad — bad can be interesting, even funny; it takes a soul to genuinely produce failure. Mediocre is the absence of soul. Competent enough to look like it tried, empty enough to leave nothing behind.</p>\n<p>If you wouldn't publish that post in your own voice, AI is no excuse to do it. You — and the world — deserve better than statistical word soup.</p>\n<p>Disclaimer: This post was sublimated through a two-hour conversation with Claude.</p>"},{"url":"/posts/20260608-return-to-the-planet-of-the-autistics-447g/","relativePath":"posts/20260608-return-to-the-planet-of-the-autistics-447g.md","relativeDir":"posts","base":"20260608-return-to-the-planet-of-the-autistics-447g.md","name":"20260608-return-to-the-planet-of-the-autistics-447g","frontmatter":{"title":"Return to the Planet of the Autistics","template":"post","date":"2026-06-08T15:33:39Z","excerpt":"A field journal from an imaginary researcher studying a puzzling minority neurological condition called allism, which affects approximately 1% of the population.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis2flk0php5gnfwudtpg.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis2flk0php5gnfwudtpg.png","canonical_url":"https://dev.to/raphink/return-to-the-planet-of-the-autistics-447g","devto_url":"https://dev.to/raphink/return-to-the-planet-of-the-autistics-447g"},"html":"<p><em>Field journal of Dr. E. Rempel, Department of Minority Neurological Studies, University of New Carthage (A work of fiction. \"Allism\" is a real term used by some autistic people to describe the neurological profile of the non-autistic majority.)</em></p>\n<hr>\n<p><em>March 3, 2089</em></p>\n<p>I have now spent three months embedded with an allistic community in the outer provinces. Allism, for those unfamiliar, is a rare neurological variant affecting approximately 1% of our population. My colleagues at the University have long debated its origins and persistence. After direct observation, I am no more certain of the answers, but I have accumulated a remarkable set of field notes.</p>\n<p>The allistic subjects I have observed appear, on the surface, entirely functional. They hold jobs, maintain relationships, raise children. And yet their neurological profile diverges from the norm in ways that are at once fascinating and bewildering.</p>\n<hr>\n<p><em>March 11, 2089</em></p>\n<p>The most immediately striking feature of the allistic profile is their relationship with information. Where a typical individual experiences the sharing of useful knowledge as a basic social reflex, the allistic subject appears to require an elaborate ritual before any information exchange can occur.</p>\n<p>Approach an allistic subject directly with a piece of useful data and observe what happens. Rather than receiving it, they freeze. A threat-assessment process appears to engage, entirely pre-consciously, before the content of the communication can be evaluated at all. One subject described it to me as feeling \"strange\" when a stranger approached with unsolicited information, though she could not articulate why.</p>\n<p>I have learned to preface all information exchanges with what my translator calls \"the preamble ritual\" — a sequence of social signals that appears to deactivate the threat response and allow communication to proceed. The exact form varies, but typically involves eye contact, a softening of posture, and verbal acknowledgment that one is about to speak. Only then can the information be received.</p>\n<p>The evolutionary origins of this ritual remain unclear to me. My working hypothesis is that it functions as a trust-establishment protocol, though why the allistic nervous system requires trust to precede factual information rather than follow its evaluation is a question I have not yet been able to answer satisfactorily.</p>\n<hr>\n<p><em>March 16, 2089</em></p>\n<p>A minor observation, but one I keep returning to. The clothing worn by my allistic subjects would be, for most members of my own population, a source of considerable distress. Rough seams left unfinished. Labels intact at the collar. Fabrics that I would describe as abrasive worn directly against the skin, apparently without a second thought.</p>\n<p>I raised this carefully with one subject, asking whether she found certain textures uncomfortable. She looked at me with what I have come to recognize as polite confusion. \"Not really,\" she said. \"Should I?\"</p>\n<p>I did not pursue the matter further. But I noted that the specialized garment industry my own population has developed over generations — seamless constructions, carefully selected weaves, the near-universal preference for soft natural fibers — would likely strike her as an inexplicable luxury.</p>\n<p>I am not certain she is wrong.</p>\n<hr>\n<p><em>March 19, 2089</em></p>\n<p>The allistic empathy system is, I will admit, remarkable in its own way. Where the typical mind responds to another's distress by immediately searching for relevant information or resources, the allistic subject responds with something altogether different: they synchronize.</p>\n<p>I observed this phenomenon repeatedly. One subject was visibly distressed about a professional difficulty. A colleague sat close, placed a hand on her arm, and said: \"That sounds really hard.\" No information was exchanged. No solution was offered. And yet the distressed subject visibly relaxed.</p>\n<p>I spent some time puzzling over this before my translator explained that the allistic subject was not attempting to solve the problem. They were signaling membership in a shared emotional space. The distress itself was the point of contact.</p>\n<p>I have since come to understand that allistic empathy is cohesion-based rather than information-based. They experience something of another's distress, synchronize with it, and express care by making that synchronization visible. It operates on entirely different principles than the system I grew up with, but I have stopped assuming it is therefore less sophisticated.</p>\n<hr>\n<p><em>March 24, 2089</em></p>\n<p>I attended a social gathering this evening in a venue I would describe as aggressively stimulating. Overhead lighting of the flickering variety my population abandoned some decades ago. Music at a volume that made conversation technically possible but practically taxing. Several simultaneous sound sources competing without apparent hierarchy.</p>\n<p>My allistic companions were energized. One of them leaned toward me at one point — shouting, by any reasonable measure — and asked if I was enjoying myself.</p>\n<p>I said that I was finding it interesting.</p>\n<p>I have since learned that this is an acceptable answer.</p>\n<p>What I could not explain to her is that I spent most of the evening mapping the exits and trying to identify which sound source to filter out first. She, I am fairly certain, was simply having a good time. The difference in our baseline calibration is something I am still trying to understand. I note, however, that not all of my allistic subjects respond to such environments identically — one excused himself early, citing a headache — which suggests the variation within the allistic profile is wider than our diagnostic literature implies.</p>\n<hr>\n<p><em>March 31, 2089</em></p>\n<p>A note on small talk, which I have been attempting to master with limited success.</p>\n<p>The allistic subjects I have observed engage in it fluently and apparently with pleasure. Exchanges about the weather, weekend plans, minor shared observations about the immediate environment — these seem to function as a kind of social maintenance, a low-stakes signal that the relationship is still intact and the parties are still on good terms. My translator describes it as \"keeping the connection warm.\"</p>\n<p>I understand the function intellectually. The practice itself I find effortful in a way I struggle to convey. The content of most small talk exchanges carries very little information, which means I am spending social energy on a transaction that does not obviously repay it. My allistic subjects, conversely, seem genuinely refreshed by it. One described a brief exchange with a neighbor about her garden as \"nice.\" I believe her.</p>\n<hr>\n<p><em>April 2, 2089</em></p>\n<p>The allistic tendency toward group cohesion produces some extraordinary social structures. They maintain large, complex networks of relationships with apparent ease, reading emotional states across a room, navigating implicit hierarchies without explicit rules. At a dinner party I attended as an observer, subjects managed at least four simultaneous conversations, tracking each other's engagement levels and adjusting fluidly. I found it overwhelming. They found it enjoyable.</p>\n<p>I have also observed, however, a recurring pattern in group decision-making contexts. Once a course of action has been agreed upon by the group, information that might complicate or reverse that decision becomes remarkably difficult to introduce. The social fabric of the agreement seems to take on a weight of its own. Subjects who possess relevant corrective information will often hesitate, gauge the room, and on more than one occasion I observed them remain silent altogether.</p>\n<p>I noted this carefully in my records but said nothing. It was not my place.</p>\n<hr>\n<p><em>April 8, 2089</em></p>\n<p>Something I should have noted earlier: the allistic subjects I have observed do not appear to experience the ambient anxiety that most members of my population would describe as a background feature of daily life. The low-level monitoring of potential information gaps, the discomfort of unresolved uncertainty, the particular unease of an incomplete picture — these do not seem to register for them in the same way.</p>\n<p>One subject, when I described this to her, said: \"I think I just assume things will probably work out.\"</p>\n<p>I wrote this down verbatim. I am not sure I have ever assumed things will probably work out.</p>\n<p>I want to be careful not to overstate this. Several of my subjects do describe anxiety — about social situations, about relationships, about the future in general. The allistic nervous system is not without its own forms of worry. But the specific texture of uncertainty-as-threat, the experience of an information gap as something requiring urgent resolution, does not appear to be a central feature of their profile. They seem, on the whole, more comfortable not knowing.</p>\n<hr>\n<p><em>April 14, 2089</em></p>\n<p>I raised the decision-making observation with my translator over dinner. He was quiet for a moment, then said: \"We don't like to make people feel wrong in front of others.\"</p>\n<p>I asked whether they would prefer to proceed with an incorrect plan rather than endure that discomfort.</p>\n<p>He thought about it. \"Sometimes,\" he said. \"If the stakes are low enough.\"</p>\n<p>I found myself wondering how the group determines in advance whether the stakes are low enough.</p>\n<hr>\n<p><em>April 29, 2089</em></p>\n<p>Before I left for the field, a colleague asked me why I had chosen to study allism rather than one of the more prevalent conditions. I told him it was precisely the rarity that interested me. A profile affecting 1% of a population tells you something about the other 99% — about what we consider default, expected, unremarkable.</p>\n<p>I have been sitting with a more unsettling question during these months, though. Our diagnostic manual defines allism as a neurological condition on the basis of its statistical rarity and its divergence from typical functioning. Typical functioning, the manual notes, reflects the profile of the majority.</p>\n<p>I keep returning to that definition. Not to dispute it, exactly. But I find I cannot read it now without wondering what it would say if the numbers were different — with inverted proportions, for example.</p>\n<p>Our society would probably be quite different then, and I wonder what they would think of us, autistics.</p>\n<hr>"},{"url":"/posts/20260612-defining-autism-the-shadow-and-the-object-3ca7/","relativePath":"posts/20260612-defining-autism-the-shadow-and-the-object-3ca7.md","relativeDir":"posts","base":"20260612-defining-autism-the-shadow-and-the-object-3ca7.md","name":"20260612-defining-autism-the-shadow-and-the-object-3ca7","frontmatter":{"title":"Defining Autism: The Shadow and the Object","template":"post","date":"2026-06-12T14:28:09Z","excerpt":"The DSM defines autism by observable symptoms. But a definition and a diagnostic test are not the same thing. One bar has been cleared. The other hasn't. And mistaking the proxy for the definition has real consequences.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53slwsy2tb40waagluuo.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53slwsy2tb40waagluuo.png","canonical_url":"https://dev.to/raphink/defining-autism-the-shadow-and-the-object-3ca7","devto_url":"https://dev.to/raphink/defining-autism-the-shadow-and-the-object-3ca7"},"html":"<p>When I attend tech conferences, people usually know me — if they do — for my work on Cilium and my labs.</p>\n<p>That started changing recently. At the last two tech conferences I attended, visitors came up to me and thanked me for my series on autism. That has been extremely encouraging, especially hearing them say they relate to most of what I shared.</p>\n<p>But their next question is often: \"so how would you define autism, actually?\"</p>\n<p>It's a fair question. And the honest answer is that the official definition — the one in the DSM-5, the handbook clinicians use worldwide — doesn't really answer it.</p>\n<p>The DSM defines autism by its observable symptoms: persistent deficits in social communication, restricted and repetitive behaviors. It's a checklist of what a clinician can see in a room, or what a patient can report about themselves. It describes the shadow, not the object.</p>\n<p>This made sense historically. Autism was identified and named from behavioral observation before its neurological mechanisms were understood. The definition was built on what was available — the same way plague was once defined by buboes and fever, before Yersin identified Yersinia pestis in 1894 and medicine stopped defining the disease by its symptoms and started defining it by its cause.</p>\n<p>We are still, for autism, in the pre-Yersin era. Not because the underlying neurological differences are unknown — neuroimaging studies have consistently identified atypical subcortical connectivity, different sensory processing thresholds, distinct patterns in amygdala and thalamic function. The neurobiological reality is known well enough to begin building definitions grounded in cause rather than presentation. Clinical practice just hasn't caught up to the science yet.</p>\n<p>The goal here is not to settle which neurological mechanism is primary — researchers are still actively debating that. The point is simpler: the definition belongs in that category at all, rather than in the category of observable social behavior.</p>\n<p>So the DSM keeps the symptom-based definition. Not out of dishonesty — out of a category error that was reasonable when it was made and hasn't been corrected since. Mistaking the presentation for the condition. The shadow for the object.</p>\n<p>We may not yet have a complete mechanistic definition. But an incomplete map of the cause is still closer to the object than a detailed map of the shadow.</p>\n<p>The distinction matters because a definition and a diagnostic test are not the same thing. A definition requires knowing what something is. A test requires detecting it consistently. We already know autism is neurological. We know it has a substantial genetic basis. We know many of the brain structures and processes involved. What we do not yet have is enough precision to build a repeatable diagnostic test from that knowledge.</p>\n<p>The first bar has largely been cleared. The second has not. Defining autism by its behavioral presentation conflates the two — building the definition from the available test rather than from the available knowledge. That is the category error.</p>\n<p>The behavioral criteria remain useful as diagnostic proxies — when used as such. The problem is when they become the definition. One of my children was seen by clinicians for years who dismissed the possibility of autism because he presented well in their office. He was a good boy. Not rolling on the floor. What they didn't see was what happened at home. The proxy was working as designed. The clinicians mistook it for reality.</p>\n<p>There's a compounding error here: early support and intervention can improve behavioral presentation. Which means the better the support, the less visible the condition — and the more likely a clinician using a behavioral checklist will miss it. Adjusted behavior gets mistaken for low need. A social outcome gets mistaken for a sufficient one.</p>\n<p>Some would argue that if a child presents well, diagnosis is unnecessary. But diagnosis changed everything — not for the clinicians, but for the people around him. Siblings understood. Teachers adjusted. The bullying stopped, not because he changed, but because the people around him finally had a frame for why he was the way he was. That is what diagnosis is actually for. Not a label. A lens.</p>\n<p>The consequences extend further. A definition built on observable behavior misses anyone whose behavior has been masked, compensated, or trained away. Which is precisely the late-diagnosed adult population — the people most likely to have spent decades performing neurotypicality convincingly enough to fool everyone, including themselves. My own GP discouraged me from seeking a diagnosis twice. I was diagnosed at 43.</p>\n<p>As a suggested definition, for what it's worth: autism is a neurological condition — not a psychological disorder — in which the preconscious processing of sensory input and social information operates with a different filtering baseline, through different mechanisms and different thresholds than the neurotypical baseline. The behavioral symptoms follow from that. They are consequences, not causes.</p>\n<p>That definition is stable. It doesn't shift with age, context, or the quality of someone's masking on a given Tuesday.</p>\n<p>The checklist shifts. The underlying neurology doesn't.</p>\n<p>I'd be curious to hear from researchers, clinicians, or fellow autistics: is there a better definition? One that names the cause rather than the shadow?</p>"},{"url":"/posts/20260615-ai-doesnt-hallucinate-your-architecture-does-32pe/","relativePath":"posts/20260615-ai-doesnt-hallucinate-your-architecture-does-32pe.md","relativeDir":"posts","base":"20260615-ai-doesnt-hallucinate-your-architecture-does-32pe.md","name":"20260615-ai-doesnt-hallucinate-your-architecture-does-32pe","frontmatter":{"title":"AI Doesn't Hallucinate. Your Architecture Does.","template":"post","date":"2026-06-15T08:30:00Z","excerpt":"Hallucination isn't a bug in LLMs — it's the mechanism. The real problem is misallocating non-determinism, and why \"SKILLS.md is enough\" is exactly backwards.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jn7tqw1rv0zxb25aq2g.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jn7tqw1rv0zxb25aq2g.png","canonical_url":"https://dev.to/raphink/ai-doesnt-hallucinate-your-architecture-does-32pe","devto_url":"https://dev.to/raphink/ai-doesnt-hallucinate-your-architecture-does-32pe"},"html":"<p>Everyone is talking about hallucination. That's the wrong diagnosis.</p>\n<p>Hallucination isn't a bug. It's the mechanism. Turn the temperature down far enough and the model stops confabulating, but it also stops being useful. What people call hallucination is what LLMs do when their creativity fails in a context that needed correctness. But all LLM output is hallucination in some form: generated token by token, probabilistically, without ground truth — just with enough structure and guardrails that most of it lands close enough to be acceptable.</p>\n<p>The creativity and the confabulation are the same thing. Different temperature, different context, different guardrails.</p>\n<p>Which means \"reducing hallucination\" is the wrong goal. You can't reduce it without reducing the model. The right goal is routing: giving LLMs only the problems where that generative, probabilistic process is actually what you need.</p>\n<p>Using a non-deterministic tool where a deterministic one would do the job perfectly is what breaks agentic systems.</p>\n<p>A deterministic API call costs microseconds and fractions of a cent. It is correct 100% of the time, by definition. An LLM doing the same task is slower, more expensive, and introduces a failure rate you now have to reason about — not because the model is broken, but because you've asked a creativity engine to act like a lookup table. Chain three of those steps together and you don't have a 10% failure rate, you have 27%. Five steps and you're past 40%. The errors are hard to reproduce and harder to attribute.</p>\n<p>I've been building an agentic genealogy research system. Two tasks, completely different natures.</p>\n<p>Fetching newspaper archive records for a given name and date range: deterministic. The API either returns results or it doesn't. There's no judgment to exercise, no ambiguity to resolve. An LLM here is just an expensive way to call curl — and one that will occasionally invent records that don't exist, because that's what it does.</p>\n<p>Deciding whether the person in that archive record is the same as the one in the birth register — given a different spelling of the surname, a two-year age discrepancy, and a naming convention that shifted at the border: LLM. This is exactly the kind of ill-defined correlation across uncertain evidence where you <em>want</em> the probabilistic reasoning. The hallucination, properly constrained, is the feature.</p>\n<p>This is also why the current wave of \"we don't need MCP anymore — SKILLS.md is enough\" is exactly backwards.</p>\n<p>SKILLS.md is a routing layer. It tells the LLM which tool to use for which class of problem, directing judgment toward genuinely hard problems rather than eliminating it. That's valuable. But SKILLS.md is still natural language processed by a probabilistic model. MCP gives the model actual deterministic tools: APIs with guaranteed behavior, typed inputs, reliable outputs. Replacing MCP with SKILLS.md doesn't simplify your architecture, it replaces a deterministic function call with a probabilistic description of one. You've kept the complexity and removed the reliability.</p>\n<p>The routing layer is where most agentic architectures fail silently. Engineers reach for the LLM because it's faster to prototype, because it removes the need to maintain separate services, because describing a tool in natural language feels easier than building it. What they get instead is unnecessary entropy at every step, and failure modes that look like model problems but are actually architecture problems.</p>\n<p>The question to ask at every step of your pipeline isn't \"can the LLM do this.\" It can, after enough tries. The question is: is this problem actually non-deterministic? Is there genuine ambiguity here that requires judgment? If not — if there's a correct answer a function could return reliably — you've given a creativity engine a job that doesn't need creativity. And you'll pay for it in every run.</p>\n<p>The good news: MCP servers are rather easy to build. Using LLMs.</p>"},{"url":"/posts/20260612-vanity-of-vanities-on-ai-slop-and-the-problem-of-content-for-contents-sake-4e6l/","relativePath":"posts/20260612-vanity-of-vanities-on-ai-slop-and-the-problem-of-content-for-contents-sake-4e6l.md","relativeDir":"posts","base":"20260612-vanity-of-vanities-on-ai-slop-and-the-problem-of-content-for-contents-sake-4e6l.md","name":"20260612-vanity-of-vanities-on-ai-slop-and-the-problem-of-content-for-contents-sake-4e6l","frontmatter":{"title":"Vanity of vanities — on AI slop and the problem of content for content's sake","template":"post","date":"2026-06-12T08:30:00Z","excerpt":"AI didn't create the problem of empty content. It made it cheaper. 3,000 years ago Qoheleth had a word for it.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk56jejevdx91actlycb7.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk56jejevdx91actlycb7.png","canonical_url":"https://dev.to/raphink/vanity-of-vanities-on-ai-slop-and-the-problem-of-content-for-contents-sake-4e6l","devto_url":"https://dev.to/raphink/vanity-of-vanities-on-ai-slop-and-the-problem-of-content-for-contents-sake-4e6l"},"html":"<p>3,000 years ago, Qoheleth surveyed everything done under the sun and called it vanity. Not pride — emptiness. The striving after wind. Labor that produces nothing that lasts, accumulates nothing that matters, leaves nothing behind worth finding.</p>\n<p>He would have recognized LinkedIn immediately.</p>\n<p>The flood of AI-generated content that fills every feed right now is not primarily a quality problem. It is a vanity problem. Content produced not because there was something to say, but because the algorithm rewards posting. Because silence looks like absence. Because everyone else is publishing, so you must too.</p>\n<p>The tool didn't create this. The tool made it cheaper.</p>\n<p>The barrier to publishing was never the writing. It was having something worth saying. Remove the writing barrier without removing the emptiness barrier, and you get an avalanche of nothing — dressed in confident prose, structured in three points, ending with a call to action that leads nowhere.</p>\n<p>Qoheleth's diagnosis was precise: vanity is not about what you produce, it's about why. Work done for its own sake, for appearance, for the striving — that is the problem. The medium is irrelevant. A hand-written empty thought is still empty. An AI-generated profound insight is still profound, if it came from somewhere real.</p>\n<p>The question AI forces us to ask — and that most people are avoiding — is not \"did a human write this.\" It is \"was there anything here worth saying.\" That question predates the tool by at least three thousand years.</p>\n<p><em>A chasing after wind.</em></p>"},{"url":"/posts/20260617-were-at-risk-of-losing-human-dignity-to-machine-productivity-lip/","relativePath":"posts/20260617-were-at-risk-of-losing-human-dignity-to-machine-productivity-lip.md","relativeDir":"posts","base":"20260617-were-at-risk-of-losing-human-dignity-to-machine-productivity-lip.md","name":"20260617-were-at-risk-of-losing-human-dignity-to-machine-productivity-lip","frontmatter":{"title":"We're at Risk of Losing Human Dignity to Machine Productivity","template":"post","date":"2026-06-17T17:13:51Z","excerpt":"The AI debate is forcing us to define intelligence. Every definition proposed, when applied consistently, ends up drawing a line through humanity itself.","thumb_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop4cv1i0id31cvswys4q.png","content_img_path":"https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop4cv1i0id31cvswys4q.png","canonical_url":"https://dev.to/raphink/were-at-risk-of-losing-human-dignity-to-machine-productivity-lip","devto_url":"https://dev.to/raphink/were-at-risk-of-losing-human-dignity-to-machine-productivity-lip"},"html":"<p>This is not an argument against AI. I use it daily, I build with it, and by most measures I'm on the winning side of the system it's creating, which is exactly why this argument needs to be made now, and why I'm in a position to make it.</p>\n<p>The debate about artificial intelligence keeps returning to the same question: is it intelligent? I'm not going to spend much time there. LLMs are not intelligent, not because of what they can or can't do, but because of what they are: statistical pattern matching at scale over human-generated data. They're mimicry, not cognition, and that's not a controversial claim if you understand the architecture.</p>\n<p>But a lot of people are making a different argument. They're pointing at capability failures: LLMs can't reliably count the letter \"r\" in \"strawberry\", therefore they're not intelligent. The problem with that argument is that many humans can't either, because they're illiterate, or because they process language differently. If that's your criterion, you've excluded a significant portion of humanity from intelligence before you've even reached AI, and if you then adjust the criterion to accommodate those humans, you need to explain why the adjusted criterion still excludes LLMs.</p>\n<p>That adjustment — finding a definition of intelligence that cleanly separates humans from machines — is where the danger lives, not because it might accidentally vindicate LLMs, but because every definition proposed, when applied consistently, ends up drawing a line through humanity itself.</p>\n<p>This is not a new problem. It has a history, and the history is not abstract. Defining humanity by capability has been used before to sort people into those who matter and those who don't, and the vocabulary changes, but the structure doesn't.</p>\n<p>What's new is the comparison class. As long as humans were the most productive agents in the economy, tying dignity to output was cruel to those at the bottom but didn't threaten the category as a whole. AI changes that arithmetic, and on an expanding set of measurable tasks, AI outproduces humans, cheaper, faster, without complaint. If your framework measures human worth by productive contribution, that framework now has a benchmark humans will increasingly fail to meet.</p>\n<p>The people most invested in that framework are aware of this. Some of them are building the AI, some of them are funding the political movements that will decide what to do with the humans who don't compete well, and they are not hiding their contempt for what they call low-productivity people, they are writing it in public and funding it into policy.</p>\n<p>The requirement — not the solution, the requirement — is to decorrelate human dignity from productive output, not because work has no value, not because contribution doesn't matter, but because human dignity has to be prior to and independent of what a person produces, or it isn't dignity at all, it's a performance review.</p>\n<p>This principle isn't new. It predates capitalism, predates the nation-state, predates the industrial revolution. The biblical tradition knew it well: the Jubilee year cancelled debts and returned land regardless of what people had produced, gleaning rights fed those who couldn't work without requiring them to justify their hunger, and Sabbath rest was mandatory, even for the productive. In each case, the logic is the same: human dignity primes over efficiency, subsistence is not means-tested, and neither is worth.</p>\n<p>In a world where a small number of people and systems can automate the productive output that once required millions, that principle stops being ancient wisdom and starts being urgent policy, and survival cannot remain a reward for contribution, it has to be a starting point.</p>\n<p>Most serious human rights frameworks rest on the same premise. What's new is that we can no longer afford to pay lip service to it while quietly grounding it in utility, because AI is removing the ambiguity, and the logical conclusion of measuring human worth by output, in a world where AI outproduces humans, is not prosperity, it's a sorting mechanism.</p>\n<p>The AI debate is not asking us whether machines are intelligent, it's asking us whether we meant what we said about humans.</p>"},{"url":"/posts/20100913-blogspot-usborne-books-on-riviera/","relativePath":"posts/20100913-blogspot-usborne-books-on-riviera.md","relativeDir":"posts","base":"20100913-blogspot-usborne-books-on-riviera.md","name":"20100913-blogspot-usborne-books-on-riviera","frontmatter":{"title":"Usborne Books on the Riviera","template":"post","date":"2010-09-13T21:07:00.000+02:00","canonical_url":"https://raphink.blogspot.com/2010/09/usborne-books-on-riviera.html","blogspot_url":"https://raphink.blogspot.com/2010/09/usborne-books-on-riviera.html","tags":["books","riviera","usborne"]},"html":"<p>Today, I am very happy to announce that my wife is starting a business here on the French Riviera. She loves the children books by the British editor Usborne, so she will be selling them at homes in the region.  </p>\n<p>Feel free to drop by <a href=\"http://www.usborne-riviera.fr\">her website</a> and check the next days for Book Parties!</p>"}],"site":{"siteMetadata":{"description":"Raphaël Pinson's personal blog","header":{"title":"Raphaël Pinson","tagline":"Infrastructure Developer working @Isovalent. ","background_img":"images/header-bg.jpg","has_nav":true,"nav_links":[{"label":"Home","url":"https://raphink.info","type":"link"},{"label":"Contact","url":"/contact/","type":"link"}],"has_social":true,"social_links":[{"label":"GitHub","url":"https://github.com/raphink","type":"icon","icon_class":"fa-github","new_window":true},{"label":"DEV","url":"https://dev.to/raphink","type":"icon","icon_class":"fa-dev","new_window":true},{"label":"Bluesky","url":"https://bsky.app/profile/raphink.info","type":"icon","icon_class":"fa-bluesky","new_window":true},{"label":"LinkedIn","url":"https://www.linkedin.com/in/raphink","type":"icon","icon_class":"fa-linkedin","new_window":true},{"label":"Stack Exchange","url":"https://stackexchange.com/users/82664/%e2%84%9daphink","type":"icon","icon_class":"fa-stack-exchange","new_window":true}]},"footer":{"content":"&copy; All rights reserved.","links":[]},"palette":"yellow","title":"Open Source Automation"},"pathPrefix":"","data":null}}},"staticQueryHashes":[]}